From f390513dc568e97157bd6f643893a48b4b5c0f0f Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Thu, 12 Sep 2024 10:11:54 +0530 Subject: [PATCH] chore: Plugin Action Editor Context (#36187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description - Introduce the PluginActionEditor module structure - Add basic handling and states in the PluginActionContext - Update AppIDE to use the new Editor when the feature flag is active. This will later be updated and the component will be used from the route level itself Fixes #36152 ## Automation /ok-to-test tags="@tag.Datasource" ### :mag: Cypress test results > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: > Commit: 1b8259b32c0df05137b01e25d7ff942b936e8110 > Cypress dashboard. > Tags: `@tag.Datasource` > Spec: >
Wed, 11 Sep 2024 05:53:02 UTC ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [x] No ## Summary by CodeRabbit - **New Features** - Introduced a `PluginActionEditor` component for managing plugin actions. - Added `PluginActionContext` for improved state management of plugin actions. - Implemented feature flag checks to conditionally render the redesigned action editor interface in both the `ApiEditorWrapper` and `QueryEditor` components. - **Documentation** - Added centralized export files for easier access to new components and context providers. - **Bug Fixes** - Enhanced error handling for missing plugin settings and configurations in the `PluginActionEditor`. --- .../PluginActionContext.tsx | 62 +++++++++++++ .../PluginActionEditor/PluginActionEditor.tsx | 89 +++++++++++++++++++ .../PluginActionForm/PluginActionForm.tsx | 7 ++ .../components/PluginActionForm/index.ts | 1 + .../components/PluginActionResponsePane.tsx | 7 ++ .../components/PluginActionToolbar.tsx | 7 ++ app/client/src/PluginActionEditor/index.ts | 8 ++ .../AppPluginActionEditor.tsx | 19 ++++ .../AppPluginActionEditor.tsx | 3 + .../src/pages/Editor/APIEditor/index.tsx | 9 ++ .../Editor/AppPluginActionEditor/index.ts | 1 + .../src/pages/Editor/QueryEditor/index.tsx | 9 ++ 12 files changed, 222 insertions(+) create mode 100644 app/client/src/PluginActionEditor/PluginActionContext.tsx create mode 100644 app/client/src/PluginActionEditor/PluginActionEditor.tsx create mode 100644 app/client/src/PluginActionEditor/components/PluginActionForm/PluginActionForm.tsx create mode 100644 app/client/src/PluginActionEditor/components/PluginActionForm/index.ts create mode 100644 app/client/src/PluginActionEditor/components/PluginActionResponsePane.tsx create mode 100644 app/client/src/PluginActionEditor/components/PluginActionToolbar.tsx create mode 100644 app/client/src/PluginActionEditor/index.ts create mode 100644 app/client/src/ce/pages/Editor/AppPluginActionEditor/AppPluginActionEditor.tsx create mode 100644 app/client/src/ee/pages/Editor/AppPluginActionEditor/AppPluginActionEditor.tsx create mode 100644 app/client/src/pages/Editor/AppPluginActionEditor/index.ts diff --git a/app/client/src/PluginActionEditor/PluginActionContext.tsx b/app/client/src/PluginActionEditor/PluginActionContext.tsx new file mode 100644 index 00000000000..0b99f9dc363 --- /dev/null +++ b/app/client/src/PluginActionEditor/PluginActionContext.tsx @@ -0,0 +1,62 @@ +import React, { + type ReactNode, + createContext, + useContext, + useMemo, +} from "react"; +import type { Action } from "entities/Action"; +import type { Plugin } from "api/PluginApi"; +import type { Datasource } from "entities/Datasource"; + +interface PluginActionContextType { + action: Action; + editorConfig: unknown[]; + settingsConfig: unknown[]; + plugin: Plugin; + datasource?: Datasource; +} + +// No need to export this context to use it. Use the hook defined below instead +const PluginActionContext = createContext(null); + +interface ChildrenProps { + children: ReactNode[]; +} + +export const PluginActionContextProvider = ( + props: ChildrenProps & PluginActionContextType, +) => { + const { action, children, datasource, editorConfig, plugin, settingsConfig } = + props; + + // using useMemo to avoid unnecessary renders + const contextValue = useMemo( + () => ({ + action, + datasource, + editorConfig, + plugin, + settingsConfig, + }), + [action, datasource, editorConfig, plugin, settingsConfig], + ); + + return ( + + {children} + + ); +}; + +// By using this hook, you are guaranteed that the states are correctly +// typed and set. +// Without this, consumers of the context would need to keep doing a null check +export const usePluginActionContext = () => { + const context = useContext(PluginActionContext); + if (!context) { + throw new Error( + "usePluginActionContext must be used within usePluginActionContextProvider", + ); + } + return context; +}; diff --git a/app/client/src/PluginActionEditor/PluginActionEditor.tsx b/app/client/src/PluginActionEditor/PluginActionEditor.tsx new file mode 100644 index 00000000000..d8e48040306 --- /dev/null +++ b/app/client/src/PluginActionEditor/PluginActionEditor.tsx @@ -0,0 +1,89 @@ +import React from "react"; +import { useLocation } from "react-router"; +import { identifyEntityFromPath } from "../navigation/FocusEntity"; +import { useSelector } from "react-redux"; +import { + getActionByBaseId, + getDatasource, + getEditorConfig, + getPlugin, + getPluginSettingConfigs, +} from "ee/selectors/entitiesSelector"; +import { PluginActionContextProvider } from "./PluginActionContext"; +import { get } from "lodash"; +import EntityNotFoundPane from "pages/Editor/EntityNotFoundPane"; +import { getIsEditorInitialized } from "selectors/editorSelectors"; +import Spinner from "components/editorComponents/Spinner"; +import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; +import { Text } from "@appsmith/ads"; + +interface ChildrenProps { + children: React.ReactNode[]; +} + +const PluginActionEditor = (props: ChildrenProps) => { + const { pathname } = useLocation(); + + const isEditorInitialized = useSelector(getIsEditorInitialized); + + const entity = identifyEntityFromPath(pathname); + const action = useSelector((state) => getActionByBaseId(state, entity.id)); + + const pluginId = get(action, "pluginId", ""); + const plugin = useSelector((state) => getPlugin(state, pluginId)); + + const datasourceId = get(action, "datasource.id", ""); + const datasource = useSelector((state) => getDatasource(state, datasourceId)); + + const settingsConfig = useSelector((state) => + getPluginSettingConfigs(state, pluginId), + ); + + const editorConfig = useSelector((state) => getEditorConfig(state, pluginId)); + + if (!isEditorInitialized) { + return ( + + + + ); + } + + if (!action) { + return ; + } + + if (!plugin) { + return ( + + + Plugin not installed! + + + ); + } + + if (!settingsConfig || !editorConfig) { + return ( + + + Editor config not found! + + + ); + } + + return ( + + {props.children} + + ); +}; + +export default PluginActionEditor; diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/PluginActionForm.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/PluginActionForm.tsx new file mode 100644 index 00000000000..17db33e698a --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/PluginActionForm.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const PluginActionForm = () => { + return
; +}; + +export default PluginActionForm; diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/index.ts b/app/client/src/PluginActionEditor/components/PluginActionForm/index.ts new file mode 100644 index 00000000000..bb106d466ee --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/index.ts @@ -0,0 +1 @@ +export { default } from "./PluginActionForm"; diff --git a/app/client/src/PluginActionEditor/components/PluginActionResponsePane.tsx b/app/client/src/PluginActionEditor/components/PluginActionResponsePane.tsx new file mode 100644 index 00000000000..5a0be861970 --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionResponsePane.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const PluginActionResponsePane = () => { + return
; +}; + +export default PluginActionResponsePane; diff --git a/app/client/src/PluginActionEditor/components/PluginActionToolbar.tsx b/app/client/src/PluginActionEditor/components/PluginActionToolbar.tsx new file mode 100644 index 00000000000..64542de3c66 --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionToolbar.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const PluginActionToolbar = () => { + return
; +}; + +export default PluginActionToolbar; diff --git a/app/client/src/PluginActionEditor/index.ts b/app/client/src/PluginActionEditor/index.ts new file mode 100644 index 00000000000..0a58d00bdaa --- /dev/null +++ b/app/client/src/PluginActionEditor/index.ts @@ -0,0 +1,8 @@ +export { default as PluginActionEditor } from "./PluginActionEditor"; +export { + PluginActionContextProvider, + usePluginActionContext, +} from "./PluginActionContext"; +export { default as PluginActionToolbar } from "./components/PluginActionToolbar"; +export { default as PluginActionForm } from "./components/PluginActionForm"; +export { default as PluginActionResponsePane } from "./components/PluginActionResponsePane"; diff --git a/app/client/src/ce/pages/Editor/AppPluginActionEditor/AppPluginActionEditor.tsx b/app/client/src/ce/pages/Editor/AppPluginActionEditor/AppPluginActionEditor.tsx new file mode 100644 index 00000000000..6ed514390c0 --- /dev/null +++ b/app/client/src/ce/pages/Editor/AppPluginActionEditor/AppPluginActionEditor.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import { + PluginActionEditor, + PluginActionToolbar, + PluginActionForm, + PluginActionResponsePane, +} from "PluginActionEditor"; + +const AppPluginActionEditor = () => { + return ( + + + + + + ); +}; + +export default AppPluginActionEditor; diff --git a/app/client/src/ee/pages/Editor/AppPluginActionEditor/AppPluginActionEditor.tsx b/app/client/src/ee/pages/Editor/AppPluginActionEditor/AppPluginActionEditor.tsx new file mode 100644 index 00000000000..2d974cc9858 --- /dev/null +++ b/app/client/src/ee/pages/Editor/AppPluginActionEditor/AppPluginActionEditor.tsx @@ -0,0 +1,3 @@ +import { default as CE_AppPluginActionEditor } from "ce/pages/Editor/AppPluginActionEditor/AppPluginActionEditor"; + +export default CE_AppPluginActionEditor; diff --git a/app/client/src/pages/Editor/APIEditor/index.tsx b/app/client/src/pages/Editor/APIEditor/index.tsx index d7affd3e552..5241a672b2b 100644 --- a/app/client/src/pages/Editor/APIEditor/index.tsx +++ b/app/client/src/pages/Editor/APIEditor/index.tsx @@ -38,6 +38,7 @@ import { resolveIcon } from "../utils"; import { ENTITY_ICON_SIZE, EntityIcon } from "../Explorer/ExplorerIcons"; import { getIDEViewMode } from "selectors/ideSelectors"; import { EditorViewMode } from "ee/entities/IDE/constants"; +import { AppPluginActionEditor } from "pages/Editor/AppPluginActionEditor"; type ApiEditorWrapperProps = RouteComponentProps; @@ -177,6 +178,14 @@ function ApiEditorWrapper(props: ApiEditorWrapperProps) { return ; }, [action?.name, isConverting]); + const isActionRedesignEnabled = useFeatureFlag( + FEATURE_FLAG.release_actions_redesign_enabled, + ); + + if (isActionRedesignEnabled) { + return ; + } + return ( ; @@ -188,6 +189,14 @@ function QueryEditor(props: QueryEditorProps) { ); }, [action?.name, isConverting]); + const isActionRedesignEnabled = useFeatureFlag( + FEATURE_FLAG.release_actions_redesign_enabled, + ); + + if (isActionRedesignEnabled) { + return ; + } + return (