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, PluginActionContextProvider,
usePluginActionContext, usePluginActionContext,
} from "./PluginActionContext"; } 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 PluginActionForm } from "./components/PluginActionForm";
export { default as PluginActionResponse } from "./components/PluginActionResponse"; export { default as PluginActionResponse } from "./components/PluginActionResponse";
export type { export type {

View File

@ -20,6 +20,16 @@ export const isActionDirty = (id: string) =>
const getActionRunningState = (state: AppState) => const getActionRunningState = (state: AppState) =>
state.ui.pluginActionEditor.isRunning; 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) => export const isActionRunning = (id: string) =>
createSelector( createSelector(
[getActionRunningState], [getActionRunningState],

View File

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

View File

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

View File

@ -710,6 +710,16 @@ const ActionExecutionTypes = {
RUN_ACTION_REQUEST: "RUN_ACTION_REQUEST", RUN_ACTION_REQUEST: "RUN_ACTION_REQUEST",
RUN_ACTION_CANCELLED: "RUN_ACTION_CANCELLED", RUN_ACTION_CANCELLED: "RUN_ACTION_CANCELLED",
RUN_ACTION_SUCCESS: "RUN_ACTION_SUCCESS", 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", CLEAR_ACTION_RESPONSE: "CLEAR_ACTION_RESPONSE",
SHOW_ACTION_MODAL: "SHOW_ACTION_MODAL", SHOW_ACTION_MODAL: "SHOW_ACTION_MODAL",
CANCEL_ACTION_MODAL: "CANCEL_ACTION_MODAL", CANCEL_ACTION_MODAL: "CANCEL_ACTION_MODAL",
@ -722,6 +732,8 @@ const ActionExecutionTypes = {
const ActionExecutionErrorTypes = { const ActionExecutionErrorTypes = {
RUN_ACTION_ERROR: "RUN_ACTION_ERROR", 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", EXECUTE_PLUGIN_ACTION_ERROR: "EXECUTE_PLUGIN_ACTION_ERROR",
}; };

View File

@ -647,6 +647,7 @@ export const EXPORT_DEFAULT_BEGINNING = () =>
`Start object with export default`; `Start object with export default`;
export const ACTION_EXECUTION_FAILED = (actionName: string) => export const ACTION_EXECUTION_FAILED = (actionName: string) =>
`The action "${actionName}" has failed.`; `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_TRIGGERED = () => "Function triggered";
export const JS_EXECUTION_SUCCESS = () => "Function executed"; export const JS_EXECUTION_SUCCESS = () => "Function executed";
export const JS_EXECUTION_FAILURE = () => "Function execution failed"; export const JS_EXECUTION_FAILURE = () => "Function execution failed";

View File

@ -1,15 +1,18 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { IDEToolbar, ToolbarSettingsPopover } from "IDE"; import { IDEToolbar, ToolbarSettingsPopover } from "IDE";
import { JSFunctionRun } from "./components/JSFunctionRun"; import { JSFunctionRun } from "pages/Editor/JSEditor/JSEditorToolbar/components/JSFunctionRun";
import type { JSActionDropdownOption, OnUpdateSettingsProps } from "./types"; import type {
JSActionDropdownOption,
OnUpdateSettingsProps,
} from "pages/Editor/JSEditor/JSEditorToolbar/types";
import type { SaveActionNameParams } from "PluginActionEditor"; import type { SaveActionNameParams } from "PluginActionEditor";
import type { ReduxAction } from "actions/ReduxActionTypes"; import type { ReduxAction } from "actions/ReduxActionTypes";
import type { JSAction, JSCollection } from "entities/JSCollection"; import type { JSAction, JSCollection } from "entities/JSCollection";
import type { DropdownOnSelect } from "@appsmith/ads-old"; import type { DropdownOnSelect } from "@appsmith/ads-old";
import { createMessage, JS_EDITOR_SETTINGS } from "ee/constants/messages"; import { createMessage, JS_EDITOR_SETTINGS } from "ee/constants/messages";
import { JSFunctionSettings } from "./components/JSFunctionSettings"; import { JSFunctionSettings } from "pages/Editor/JSEditor/JSEditorToolbar/components/JSFunctionSettings";
import { convertJSActionsToDropdownOptions } from "./utils"; import { convertJSActionsToDropdownOptions } from "pages/Editor/JSEditor/JSEditorToolbar/utils";
import { JSObjectNameEditor } from "./JSObjectNameEditor"; import { JSObjectNameEditor } from "pages/Editor/JSEditor/JSEditorToolbar/JSObjectNameEditor";
interface Props { interface Props {
changePermitted: boolean; 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 = { export const testLocators = {
runJSAction: "run-js-action", runJSAction: "run-js-action",
runJSActionTestID: "t--run-js-action", runJSActionTestID: "t--run-js-action",
generateSchemaJSActionTestID: "t--generate-schema-js-action",
}; };
export const NO_FUNCTION_DROPDOWN_OPTION = { export const NO_FUNCTION_DROPDOWN_OPTION = {
label: "No function available", label: "No function available",

View File

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

View File

@ -4,7 +4,7 @@ import {
ReduxActionTypes, ReduxActionTypes,
ReduxActionErrorTypes, ReduxActionErrorTypes,
} from "ee/constants/ReduxActionConstants"; } from "ee/constants/ReduxActionConstants";
import type { JSCollection } from "entities/JSCollection"; import type { JSAction, JSCollection } from "entities/JSCollection";
import { ActionExecutionResizerHeight } from "PluginActionEditor/components/PluginActionResponse/constants"; import { ActionExecutionResizerHeight } from "PluginActionEditor/components/PluginActionResponse/constants";
export enum JSEditorTab { export enum JSEditorTab {
@ -23,6 +23,7 @@ export interface JsPaneReduxState {
isSaving: Record<string, boolean>; isSaving: Record<string, boolean>;
isDeleting: Record<string, boolean>; isDeleting: Record<string, boolean>;
isDirty: Record<string, boolean>; isDirty: Record<string, boolean>;
isSchemaGenerating: Record<string, boolean>;
selectedConfigTab: JSEditorTab; selectedConfigTab: JSEditorTab;
debugger: JSPaneDebuggerState; debugger: JSPaneDebuggerState;
} }
@ -32,6 +33,7 @@ const initialState: JsPaneReduxState = {
isSaving: {}, isSaving: {},
isDeleting: {}, isDeleting: {},
isDirty: {}, isDirty: {},
isSchemaGenerating: {},
selectedConfigTab: JSEditorTab.CODE, selectedConfigTab: JSEditorTab.CODE,
debugger: { debugger: {
open: false, open: false,
@ -175,6 +177,50 @@ const jsPaneReducer = createReducer(initialState, {
isSaving: false, 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; export default jsPaneReducer;

View File

@ -22,3 +22,11 @@ export const getLastJSTab = (state: AppState): FocusEntityInfo | undefined => {
return identifyEntityFromPath(urlWithoutQueryParams); 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];