feat: add generate schema button (#39751)

## Description

Update JS and Plugin Action Toolbar to add new Schema generation CTA in
them


https://www.figma.com/design/mVEbXXryqv2oBxMcNg8yjC/Anvil-AI?node-id=3891-34025&t=AVP3gbWu07WzPfwc-0

Fixes #39726

## Automation

/ok-to-test tags="@tag.JS, @tag.Datasource"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/13920663021>
> Commit: c0b76039714bf64155c0d41c6f72cda881bcd968
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=13920663021&attempt=2"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.JS, @tag.Datasource`
> Spec:
> <hr>Tue, 18 Mar 2025 11:30:10 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced real-time tracking of schema generation processes with
clear UI indicators and error messaging.
- Expanded actionable events to support improved feedback during schema
creation for plugin actions and JavaScript functions.

- **Refactor**
- Streamlined component exports and updated import paths for enhanced
organization and consistency in the editor toolbars.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Hetu Nandu <hetunandu@gmail.com>
This commit is contained in:
Ilia 2025-03-18 12:41:11 +01:00 committed by GitHub
parent 2b703c9746
commit d494ca4ee1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 119 additions and 16 deletions

View File

@ -3,7 +3,7 @@ export {
PluginActionContextProvider,
usePluginActionContext,
} from "./PluginActionContext";
export { default as PluginActionToolbar } from "./components/PluginActionToolbar";
export { PluginActionToolbar } from "ee/PluginActionEditor/components/PluginActionToolbar";
export { default as PluginActionForm } from "./components/PluginActionForm";
export { default as PluginActionResponse } from "./components/PluginActionResponse";
export type {

View File

@ -20,6 +20,16 @@ export const isActionDirty = (id: string) =>
const getActionRunningState = (state: AppState) =>
state.ui.pluginActionEditor.isRunning;
const getActionSchemaGeneratingState = (state: AppState) =>
state.ui.pluginActionEditor.isSchemaGenerating;
export const isActionSchemaGenerating = (id: string) =>
createSelector(
[getActionSchemaGeneratingState],
(isSchemaGeneratingMap) =>
id in isSchemaGeneratingMap && isSchemaGeneratingMap[id],
);
export const isActionRunning = (id: string) =>
createSelector(
[getActionRunningState],

View File

@ -22,6 +22,7 @@ export interface PluginActionEditorState {
isCreating: boolean;
isRunning: Record<string, boolean>;
isSaving: Record<string, boolean>;
isSchemaGenerating: Record<string, boolean>;
isDeleting: Record<string, boolean>;
isDirty: Record<string, boolean>;
runErrorMessage: Record<string, string>;
@ -34,6 +35,7 @@ const initialState: PluginActionEditorState = {
isCreating: false,
isRunning: {},
isSaving: {},
isSchemaGenerating: {},
isDeleting: {},
isDirty: {},
runErrorMessage: {},
@ -141,6 +143,26 @@ export const handlers = {
set(state, ["isRunning", id], false);
set(state, ["runErrorMessage", id], error.message);
},
[ReduxActionTypes.GENERATE_PLUGIN_ACTION_SCHEMA_REQUEST]: (
state: PluginActionEditorState,
action: ReduxAction<{
id: string;
}>,
) => {
set(state, ["isSchemaGenerating", action.payload.id], true);
},
[ReduxActionTypes.GENERATE_PLUGIN_ACTION_SCHEMA_SUCCESS]: (
state: PluginActionEditorState,
action: ReduxAction<{ id: string }>,
) => {
set(state, ["isSchemaGenerating", action.payload.id], false);
},
[ReduxActionErrorTypes.GENERATE_PLUGIN_ACTION_SCHEMA_ERROR]: (
state: PluginActionEditorState,
action: ReduxAction<{ id: string }>,
) => {
set(state, ["isSchemaGenerating", action.payload.id], false);
},
[ReduxActionTypes.SET_PLUGIN_ACTION_EDITOR_FORM_SELECTED_TAB]: (
state: PluginActionEditorState,
action: ReduxAction<{ selectedTab: string }>,

View File

@ -2,16 +2,16 @@ import React, { useCallback } from "react";
import { IDEToolbar } from "IDE";
import { Button, Tooltip } from "@appsmith/ads";
import { modText } from "utils/helpers";
import { usePluginActionContext } from "../PluginActionContext";
import { usePluginActionContext } from "PluginActionEditor/PluginActionContext";
import {
useBlockExecution,
useHandleRunClick,
useAnalyticsOnRunClick,
} from "../hooks";
} from "PluginActionEditor/hooks";
import { useSelector } from "react-redux";
import { isActionRunning } from "../store";
import PluginActionSettings from "./PluginActionSettings";
import { PluginActionContextMenu } from "./PluginActionContextMenu";
import { isActionRunning } from "PluginActionEditor/store";
import PluginActionSettings from "PluginActionEditor/components/PluginActionSettings";
import { PluginActionContextMenu } from "PluginActionEditor/components/PluginActionContextMenu";
interface PluginActionToolbarProps {
runOptions?: React.ReactNode;
@ -19,7 +19,7 @@ interface PluginActionToolbarProps {
menuContent?: React.ReactNode[] | React.ReactNode;
}
const PluginActionToolbar = (props: PluginActionToolbarProps) => {
export const PluginActionToolbar = (props: PluginActionToolbarProps) => {
const { action } = usePluginActionContext();
const { handleRunClick } = useHandleRunClick();
const { callRunActionAnalytics } = useAnalyticsOnRunClick();
@ -63,5 +63,3 @@ const PluginActionToolbar = (props: PluginActionToolbarProps) => {
</IDEToolbar>
);
};
export default PluginActionToolbar;

View File

@ -710,6 +710,16 @@ const ActionExecutionTypes = {
RUN_ACTION_REQUEST: "RUN_ACTION_REQUEST",
RUN_ACTION_CANCELLED: "RUN_ACTION_CANCELLED",
RUN_ACTION_SUCCESS: "RUN_ACTION_SUCCESS",
GENERATE_JS_FUNCTION_SCHEMA_REQUEST: "GENERATE_JS_FUNCTION_SCHEMA_REQUEST",
GENERATE_JS_FUNCTION_SCHEMA_CANCELLED:
"GENERATE_JS_FUNCTION_SCHEMA_CANCELLED",
GENERATE_JS_FUNCTION_SCHEMA_SUCCESS: "GENERATE_JS_FUNCTION_SCHEMA_SUCCESS",
GENERATE_PLUGIN_ACTION_SCHEMA_REQUEST:
"GENERATE_PLUGIN_ACTION_SCHEMA_REQUEST",
GENERATE_PLUGIN_ACTION_SCHEMA_CANCELLED:
"GENERATE_PLUGIN_ACTION_SCHEMA_CANCELLED",
GENERATE_PLUGIN_ACTION_SCHEMA_SUCCESS:
"GENERATE_PLUGIN_ACTION_SCHEMA_SUCCESS",
CLEAR_ACTION_RESPONSE: "CLEAR_ACTION_RESPONSE",
SHOW_ACTION_MODAL: "SHOW_ACTION_MODAL",
CANCEL_ACTION_MODAL: "CANCEL_ACTION_MODAL",
@ -722,6 +732,8 @@ const ActionExecutionTypes = {
const ActionExecutionErrorTypes = {
RUN_ACTION_ERROR: "RUN_ACTION_ERROR",
GENERATE_JS_FUNCTION_SCHEMA_ERROR: "GENERATE_JS_FUNCTION_SCHEMA_ERROR",
GENERATE_PLUGIN_ACTION_SCHEMA_ERROR: "GENERATE_PLUGIN_ACTION_SCHEMA_ERROR",
EXECUTE_PLUGIN_ACTION_ERROR: "EXECUTE_PLUGIN_ACTION_ERROR",
};

View File

@ -647,6 +647,7 @@ export const EXPORT_DEFAULT_BEGINNING = () =>
`Start object with export default`;
export const ACTION_EXECUTION_FAILED = (actionName: string) =>
`The action "${actionName}" has failed.`;
export const CANNOT_GENERATE_SCHEMA = () => "Can't generate schema";
export const JS_EXECUTION_TRIGGERED = () => "Function triggered";
export const JS_EXECUTION_SUCCESS = () => "Function executed";
export const JS_EXECUTION_FAILURE = () => "Function execution failed";

View File

@ -1,15 +1,18 @@
import React, { useState } from "react";
import { IDEToolbar, ToolbarSettingsPopover } from "IDE";
import { JSFunctionRun } from "./components/JSFunctionRun";
import type { JSActionDropdownOption, OnUpdateSettingsProps } from "./types";
import { JSFunctionRun } from "pages/Editor/JSEditor/JSEditorToolbar/components/JSFunctionRun";
import type {
JSActionDropdownOption,
OnUpdateSettingsProps,
} from "pages/Editor/JSEditor/JSEditorToolbar/types";
import type { SaveActionNameParams } from "PluginActionEditor";
import type { ReduxAction } from "actions/ReduxActionTypes";
import type { JSAction, JSCollection } from "entities/JSCollection";
import type { DropdownOnSelect } from "@appsmith/ads-old";
import { createMessage, JS_EDITOR_SETTINGS } from "ee/constants/messages";
import { JSFunctionSettings } from "./components/JSFunctionSettings";
import { convertJSActionsToDropdownOptions } from "./utils";
import { JSObjectNameEditor } from "./JSObjectNameEditor";
import { JSFunctionSettings } from "pages/Editor/JSEditor/JSEditorToolbar/components/JSFunctionSettings";
import { convertJSActionsToDropdownOptions } from "pages/Editor/JSEditor/JSEditorToolbar/utils";
import { JSObjectNameEditor } from "pages/Editor/JSEditor/JSEditorToolbar/JSObjectNameEditor";
interface Props {
changePermitted: boolean;

View File

@ -0,0 +1 @@
export * from "ce/PluginActionEditor/components/PluginActionToolbar";

View File

@ -0,0 +1 @@
export * from "ce/pages/Editor/JSEditor/JSEditorToolbar/JSEditorToolbar";

View File

@ -8,6 +8,7 @@ export const RUN_BUTTON_DEFAULTS = {
export const testLocators = {
runJSAction: "run-js-action",
runJSActionTestID: "t--run-js-action",
generateSchemaJSActionTestID: "t--generate-schema-js-action",
};
export const NO_FUNCTION_DROPDOWN_OPTION = {
label: "No function available",

View File

@ -1,4 +1,4 @@
export { JSEditorToolbar } from "./JSEditorToolbar";
export { JSEditorToolbar } from "ee/pages/Editor/JSEditor/JSEditorToolbar/JSEditorToolbar";
export {
type OnUpdateSettingsProps,

View File

@ -4,7 +4,7 @@ import {
ReduxActionTypes,
ReduxActionErrorTypes,
} from "ee/constants/ReduxActionConstants";
import type { JSCollection } from "entities/JSCollection";
import type { JSAction, JSCollection } from "entities/JSCollection";
import { ActionExecutionResizerHeight } from "PluginActionEditor/components/PluginActionResponse/constants";
export enum JSEditorTab {
@ -23,6 +23,7 @@ export interface JsPaneReduxState {
isSaving: Record<string, boolean>;
isDeleting: Record<string, boolean>;
isDirty: Record<string, boolean>;
isSchemaGenerating: Record<string, boolean>;
selectedConfigTab: JSEditorTab;
debugger: JSPaneDebuggerState;
}
@ -32,6 +33,7 @@ const initialState: JsPaneReduxState = {
isSaving: {},
isDeleting: {},
isDirty: {},
isSchemaGenerating: {},
selectedConfigTab: JSEditorTab.CODE,
debugger: {
open: false,
@ -175,6 +177,50 @@ const jsPaneReducer = createReducer(initialState, {
isSaving: false,
};
},
[ReduxActionTypes.GENERATE_JS_FUNCTION_SCHEMA_REQUEST]: (
state: JsPaneReduxState,
action: ReduxAction<{
action: JSAction;
}>,
) => {
if (!action.payload.action.collectionId) return state;
return {
...state,
isSchemaGenerating: {
...state.isSchemaGenerating,
[action.payload.action.collectionId]: true,
},
};
},
[ReduxActionTypes.GENERATE_JS_FUNCTION_SCHEMA_SUCCESS]: (
state: JsPaneReduxState,
action: ReduxAction<{ action: JSAction }>,
) => {
if (!action.payload.action.collectionId) return state;
return {
...state,
isSchemaGenerating: {
...state.isSchemaGenerating,
[action.payload.action.collectionId]: false,
},
};
},
[ReduxActionErrorTypes.GENERATE_JS_FUNCTION_SCHEMA_ERROR]: (
state: JsPaneReduxState,
action: ReduxAction<{ action: JSAction }>,
) => {
if (!action.payload.action.collectionId) return state;
return {
...state,
isSchemaGenerating: {
...state.isSchemaGenerating,
[action.payload.action.collectionId]: false,
},
};
},
});
export default jsPaneReducer;

View File

@ -22,3 +22,11 @@ export const getLastJSTab = (state: AppState): FocusEntityInfo | undefined => {
return identifyEntityFromPath(urlWithoutQueryParams);
}
};
export const getIsGeneratingSchema = (state: AppState, collectionId: string) =>
state.ui.jsPane.isSchemaGenerating[collectionId];
export const getIsJSCollectionSaving = (
state: AppState,
collectionId: string,
) => state.ui.jsPane.isSaving[collectionId];