From 3a65d5a4eae10d771d650a91b6b74c4bcc4f3b96 Mon Sep 17 00:00:00 2001 From: Ayush Pahwa Date: Mon, 22 Jan 2024 19:50:22 +0700 Subject: [PATCH] feat: workflows query in apps code split (#30424) --- app/client/src/api/PluginApi.ts | 5 +++++ .../src/ce/selectors/entitiesSelector.ts | 4 +++- .../src/ce/selectors/workflowSelectors.ts | 2 ++ app/client/src/ce/utils/workflowHelpers.ts | 3 +++ .../GlobalSearch/GlobalSearchHooks.tsx | 14 +++++++++++++- .../ActionConstants.tsx | 4 ++++ app/client/src/ee/utils/workflowHelpers.ts | 1 + app/client/src/entities/Action/index.ts | 10 +++++++++- .../pages/Editor/Explorer/Actions/helpers.tsx | 4 +++- .../pages/Editor/Explorer/EntityExplorer.tsx | 4 ++++ .../pages/Editor/Explorer/ExplorerIcons.tsx | 9 +++++++++ .../Explorer/Files/FilesContextProvider.tsx | 4 ++++ .../src/pages/Editor/Explorer/Files/index.tsx | 2 ++ .../pages/Editor/IDE/EditorPane/Query/List.tsx | 3 +++ .../Editor/QueryEditor/EditorJSONtoForm.tsx | 11 +++++++---- .../src/pages/Editor/QueryEditor/index.tsx | 16 ++++++++++------ app/client/src/sagas/FormEvaluationSaga.ts | 13 +++++++++++-- app/client/src/sagas/QueryPaneSagas.ts | 18 ++++++++++++++++-- 18 files changed, 109 insertions(+), 18 deletions(-) create mode 100644 app/client/src/ce/utils/workflowHelpers.ts create mode 100644 app/client/src/ee/utils/workflowHelpers.ts diff --git a/app/client/src/api/PluginApi.ts b/app/client/src/api/PluginApi.ts index 932e8fb5fb..06f6f56849 100644 --- a/app/client/src/api/PluginApi.ts +++ b/app/client/src/api/PluginApi.ts @@ -32,6 +32,8 @@ export interface Plugin { responseType?: "TABLE" | "JSON"; documentationLink?: string; generateCRUDPageComponent?: string; + // We need to know if the plugin requires a datasource (Eg Workflows plugin does not require a datasource to create queries) + requiresDatasource: boolean; } export interface PluginFormPayload { @@ -55,6 +57,9 @@ class PluginsApi extends Api { static defaultDynamicTriggerURL(datasourceId: string): string { return `/v1/datasources/${datasourceId}/trigger`; } + static dynamicTriggerURLForInternalPlugins(pluginId: string): string { + return `/${PluginsApi.url}/${pluginId}/trigger`; + } static async fetchPlugins( workspaceId: string, ): Promise>> { diff --git a/app/client/src/ce/selectors/entitiesSelector.ts b/app/client/src/ce/selectors/entitiesSelector.ts index ba3282090c..bfc2ed6af5 100644 --- a/app/client/src/ce/selectors/entitiesSelector.ts +++ b/app/client/src/ce/selectors/entitiesSelector.ts @@ -1047,6 +1047,9 @@ export const selectFilesForExplorer = createSelector( group = isEmbeddedAIDataSource(file.config.datasource) ? "AI Queries" : datasourceIdToNameMap[file.config.datasource.id] ?? "AI Queries"; + } else if (file.config.pluginType === PluginType.INTERNAL) { + // TODO: Add a group for internal actions, currently only Workflow actions are internal + group = "Workflows"; } else { group = datasourceIdToNameMap[file.config.datasource.id]; } @@ -1059,7 +1062,6 @@ export const selectFilesForExplorer = createSelector( }, [] as Array); const filesSortedByGroupName = sortBy(files, [ - (file) => file.entity.config?.isMainJSCollection, (file) => file.group?.toLowerCase(), (file) => file.entity.config?.name?.toLowerCase(), ]); diff --git a/app/client/src/ce/selectors/workflowSelectors.ts b/app/client/src/ce/selectors/workflowSelectors.ts index 85d02b8b8a..437c98bb1a 100644 --- a/app/client/src/ce/selectors/workflowSelectors.ts +++ b/app/client/src/ce/selectors/workflowSelectors.ts @@ -28,3 +28,5 @@ export const getCurrentWorkflowJSActions = ( // eslint-disable-next-line @typescript-eslint/no-unused-vars state: AppState, ): JSCollectionData[] => []; + +export const getShowWorkflowFeature = () => false; diff --git a/app/client/src/ce/utils/workflowHelpers.ts b/app/client/src/ce/utils/workflowHelpers.ts new file mode 100644 index 0000000000..b7f05145a5 --- /dev/null +++ b/app/client/src/ce/utils/workflowHelpers.ts @@ -0,0 +1,3 @@ +export const useWorkflowOptions = () => { + return []; +}; diff --git a/app/client/src/components/editorComponents/GlobalSearch/GlobalSearchHooks.tsx b/app/client/src/components/editorComponents/GlobalSearch/GlobalSearchHooks.tsx index 2f93095c0e..c532ed2847 100644 --- a/app/client/src/components/editorComponents/GlobalSearch/GlobalSearchHooks.tsx +++ b/app/client/src/components/editorComponents/GlobalSearch/GlobalSearchHooks.tsx @@ -39,22 +39,26 @@ import type { Plugin } from "api/PluginApi"; import { useModuleOptions } from "@appsmith/utils/moduleInstanceHelpers"; import type { ActionParentEntityTypeInterface } from "@appsmith/entities/Engine/actionHelpers"; import { createNewQueryBasedOnParentEntity } from "@appsmith/actions/helpers"; +import { useWorkflowOptions } from "@appsmith/utils/workflowHelpers"; export interface FilterFileOperationsProps { canCreateActions: boolean; query?: string; showModules?: boolean; + showWorkflows?: boolean; } export const useFilteredFileOperations = ({ canCreateActions, query = "", showModules = true, + showWorkflows = true, }: FilterFileOperationsProps) => { const { appWideDS = [], otherDS = [] } = useAppWideAndOtherDatasource(); const plugins = useSelector(getPlugins); const moduleOptions = useModuleOptions(); const showAppsmithAIQuery = useFeatureFlag(FEATURE_FLAG.ab_appsmith_ai_query); + const workflowOptions = useWorkflowOptions(); // helper map for sorting based on recent usage const recentlyUsedDSMap = useRecentlyUsedDSMap(); @@ -84,6 +88,7 @@ export const useFilteredFileOperations = ({ canCreateActions, canCreateDatasource, moduleOptions: showModules ? moduleOptions : [], + workflowOptions: showWorkflows ? workflowOptions : [], plugins, recentlyUsedDSMap, query, @@ -100,14 +105,16 @@ export const useFilteredAndSortedFileOperations = ({ query, recentlyUsedDSMap = {}, showAppsmithAIQuery = false, + workflowOptions = [], }: { allDatasources?: Datasource[]; canCreateActions?: boolean; canCreateDatasource?: boolean; moduleOptions?: ActionOperation[]; plugins?: Plugin[]; - recentlyUsedDSMap?: Record; query: string; + recentlyUsedDSMap?: Record; + workflowOptions?: ActionOperation[]; showAppsmithAIQuery?: boolean; }) => { const fileOperations: ActionOperation[] = []; @@ -119,6 +126,11 @@ export const useFilteredAndSortedFileOperations = ({ ? [...actionOperations, appsmithAIActionOperation] : actionOperations; + // Add Workflow operations + if (workflowOptions.length > 0) { + workflowOptions.map((workflowOp) => fileOperations.push(workflowOp)); + } + /** * Work around to get the rest api cloud image. * We don't have it store as a svg diff --git a/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx b/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx index 92b761c00f..e0c8897ef5 100644 --- a/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx +++ b/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx @@ -171,6 +171,7 @@ export const defaultActionSettings: Record = { [PluginType.REMOTE]: saasActionSettingsConfig, [PluginType.JS]: [], [PluginType.AI]: saasActionSettingsConfig, + [PluginType.INTERNAL]: saasActionSettingsConfig, }; export const defaultActionEditorConfigs: Record = { @@ -180,6 +181,7 @@ export const defaultActionEditorConfigs: Record = { [PluginType.REMOTE]: [], [PluginType.JS]: [], [PluginType.AI]: [], + [PluginType.INTERNAL]: [], }; export const defaultActionDependenciesConfig: Record< @@ -192,6 +194,7 @@ export const defaultActionDependenciesConfig: Record< [PluginType.REMOTE]: {}, [PluginType.JS]: {}, [PluginType.AI]: {}, + [PluginType.INTERNAL]: {}, }; export const defaultDatasourceFormButtonConfig: Record = { @@ -201,4 +204,5 @@ export const defaultDatasourceFormButtonConfig: Record = { [PluginType.REMOTE]: apiActionDatasourceFormButtonConfig.REMOTE, [PluginType.JS]: [], [PluginType.AI]: apiActionDatasourceFormButtonConfig.AI, + [PluginType.INTERNAL]: [], }; diff --git a/app/client/src/ee/utils/workflowHelpers.ts b/app/client/src/ee/utils/workflowHelpers.ts new file mode 100644 index 0000000000..06432d4dae --- /dev/null +++ b/app/client/src/ee/utils/workflowHelpers.ts @@ -0,0 +1 @@ +export * from "ce/utils/workflowHelpers"; diff --git a/app/client/src/entities/Action/index.ts b/app/client/src/entities/Action/index.ts index 74a5df0d19..a0ad208e5d 100644 --- a/app/client/src/entities/Action/index.ts +++ b/app/client/src/entities/Action/index.ts @@ -14,6 +14,7 @@ export enum PluginType { JS = "JS", REMOTE = "REMOTE", AI = "AI", + INTERNAL = "INTERNAL", } export enum PluginPackageName { @@ -30,6 +31,7 @@ export enum PluginPackageName { MS_SQL = "mssql-plugin", SNOWFLAKE = "snowflake-plugin", APPSMITH_AI = "appsmithai-plugin", + WORKFLOW = "workflow-plugin", } // more can be added subsequently. @@ -191,6 +193,11 @@ export interface AIAction extends BaseAction { actionConfiguration: any; datasource: StoredDatasource; } +export interface InternalAction extends BaseAction { + pluginType: PluginType.INTERNAL; + actionConfiguration: any; + datasource: StoredDatasource; +} export interface EmbeddedApiAction extends BaseApiAction { datasource: EmbeddedRestDatasource; @@ -231,7 +238,8 @@ export type Action = | QueryAction | SaaSAction | RemoteAction - | AIAction; + | AIAction + | InternalAction; export enum SlashCommand { NEW_API, diff --git a/app/client/src/pages/Editor/Explorer/Actions/helpers.tsx b/app/client/src/pages/Editor/Explorer/Actions/helpers.tsx index c52a429e0a..c6b8992105 100644 --- a/app/client/src/pages/Editor/Explorer/Actions/helpers.tsx +++ b/app/client/src/pages/Editor/Explorer/Actions/helpers.tsx @@ -60,7 +60,8 @@ export const resolveActionURL = ({ } else if ( pluginType === PluginType.DB || pluginType === PluginType.REMOTE || - pluginType === PluginType.AI + pluginType === PluginType.AI || + pluginType === PluginType.INTERNAL ) { return queryEditorIdURL({ parentEntityId, @@ -83,6 +84,7 @@ export const ACTION_PLUGIN_MAP: Array = [ PluginType.DB, PluginType.REMOTE, PluginType.AI, + PluginType.INTERNAL, ], icon: dbQueryIcon, key: generateReactKey(), diff --git a/app/client/src/pages/Editor/Explorer/EntityExplorer.tsx b/app/client/src/pages/Editor/Explorer/EntityExplorer.tsx index 92d04494d7..293391c7bd 100644 --- a/app/client/src/pages/Editor/Explorer/EntityExplorer.tsx +++ b/app/client/src/pages/Editor/Explorer/EntityExplorer.tsx @@ -41,6 +41,7 @@ import { getHasCreateActionPermission } from "@appsmith/utils/BusinessFeatures/p import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag"; import { ActionParentEntityType } from "@appsmith/entities/Engine/actionHelpers"; +import { getShowWorkflowFeature } from "@appsmith/selectors/workflowSelectors"; const NoEntityFoundSvg = importSvg( async () => import("assets/svg/no_entities_found.svg"), @@ -114,6 +115,8 @@ function EntityExplorer({ isActive }: { isActive: boolean }) { pagePermissions, ); + const showWorkflows = useSelector(getShowWorkflowFeature); + const closeWalkthrough = useCallback(() => { if (isWalkthroughOpened && popFeature) { popFeature("EXPLORER_DATASOURCE_ADD"); @@ -165,6 +168,7 @@ function EntityExplorer({ isActive }: { isActive: boolean }) { editorId={applicationId} parentEntityId={pageId} parentEntityType={ActionParentEntityType.PAGE} + showWorkflows={showWorkflows} > diff --git a/app/client/src/pages/Editor/Explorer/ExplorerIcons.tsx b/app/client/src/pages/Editor/Explorer/ExplorerIcons.tsx index 1741d527fb..a2fbc6d4c6 100644 --- a/app/client/src/pages/Editor/Explorer/ExplorerIcons.tsx +++ b/app/client/src/pages/Editor/Explorer/ExplorerIcons.tsx @@ -286,6 +286,15 @@ export function CurlIconV2() { ); } +// TODO (workflows): replace with actual workflow icon +export function WorkflowIcon() { + return ( + + + + ); +} + // height and width are set to 18px by default. This is to maintain the current icon sizes. // fontSize is set to 56% by default. export function JsFileIconV2( diff --git a/app/client/src/pages/Editor/Explorer/Files/FilesContextProvider.tsx b/app/client/src/pages/Editor/Explorer/Files/FilesContextProvider.tsx index 72dbf55041..11d8e02e48 100644 --- a/app/client/src/pages/Editor/Explorer/Files/FilesContextProvider.tsx +++ b/app/client/src/pages/Editor/Explorer/Files/FilesContextProvider.tsx @@ -26,6 +26,7 @@ interface FilesContextContextProps { parentEntityId: string; // page, workflow or module parentEntityType: ActionParentEntityTypeInterface; showModules?: boolean; + showWorkflows?: boolean; selectFilesForExplorer?: (state: any) => any; } @@ -51,6 +52,7 @@ export const FilesContextProvider = ({ parentEntityType, selectFilesForExplorer, showModules, + showWorkflows, }: FilesContextProviderProps) => { const value = useMemo(() => { return { @@ -61,6 +63,7 @@ export const FilesContextProvider = ({ menuItems: menuItems || defaultMenuItems, selectFilesForExplorer, showModules, + showWorkflows, }; }, [ canCreateActions, @@ -68,6 +71,7 @@ export const FilesContextProvider = ({ parentEntityType, menuItems, showModules, + showWorkflows, selectFilesForExplorer, editorId, ]); diff --git a/app/client/src/pages/Editor/Explorer/Files/index.tsx b/app/client/src/pages/Editor/Explorer/Files/index.tsx index fc2f8334b0..7cd8067288 100644 --- a/app/client/src/pages/Editor/Explorer/Files/index.tsx +++ b/app/client/src/pages/Editor/Explorer/Files/index.tsx @@ -48,6 +48,7 @@ function Files() { parentEntityType, selectFilesForExplorer = default_selectFilesForExplorer, showModules = true, + showWorkflows = true, } = context; const files = useSelector(selectFilesForExplorer); @@ -61,6 +62,7 @@ function Files() { query, canCreateActions, showModules, + showWorkflows, }); const onCreate = useCallback(() => { diff --git a/app/client/src/pages/Editor/IDE/EditorPane/Query/List.tsx b/app/client/src/pages/Editor/IDE/EditorPane/Query/List.tsx index 33a5d7f80e..37f9fe82b9 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/Query/List.tsx +++ b/app/client/src/pages/Editor/IDE/EditorPane/Query/List.tsx @@ -18,6 +18,7 @@ import { FilesContextProvider } from "pages/Editor/Explorer/Files/FilesContextPr import { createMessage, EDITOR_PANE_TEXTS } from "@appsmith/constants/messages"; import { EmptyState } from "../components/EmptyState"; import { useQueryAdd } from "./hooks"; +import { getShowWorkflowFeature } from "@appsmith/selectors/workflowSelectors"; const ListQuery = () => { const pageId = useSelector(getCurrentPageId) as string; @@ -33,6 +34,7 @@ const ListQuery = () => { const applicationId = useSelector(getCurrentApplicationId); const addButtonClickHandler = useQueryAdd(); + const showWorkflows = useSelector(getShowWorkflowFeature); return ( { editorId={applicationId} parentEntityId={pageId} parentEntityType={ActionParentEntityType.PAGE} + showWorkflows={showWorkflows} > {items.map((file) => { return ( diff --git a/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx b/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx index 4f5b0d8c1a..be7445aee6 100644 --- a/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx +++ b/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx @@ -354,7 +354,9 @@ export function EditorJSONtoForm(props: Props) { userWorkspacePermissions, ); - const showSchema = useShowSchema(currentActionConfig?.pluginId || ""); + const showSchema = + useShowSchema(currentActionConfig?.pluginId || "") && + !!plugin?.requiresDatasource; const showRightPane = showSchema || @@ -617,9 +619,10 @@ export function EditorJSONtoForm(props: Props) { ((hasDependencies || !!actionResponse) && !guidedTourEnabled) || currentActionPluginName !== PluginName.SMTP; - // Datasource selection is hidden for Appsmith AI Plugin - // TODO: @Diljit Remove this condition when knowledge retrieval for Appsmith AI is implemented - const showDatasourceSelector = !isAppsmithAIPlugin(plugin?.packageName); + // Datasource selection is hidden for Appsmith AI Plugin and for plugins that don't require datasource + // TODO: @Diljit Remove this condition when knowledge retrieval for Appsmith AI is implemented (Only remove the AI Condition) + const showDatasourceSelector = + !isAppsmithAIPlugin(plugin?.packageName) && !!plugin?.requiresDatasource; // when switching between different redux forms, make sure this redux form has been initialized before rendering anything. // the initialized prop below comes from redux-form. diff --git a/app/client/src/pages/Editor/QueryEditor/index.tsx b/app/client/src/pages/Editor/QueryEditor/index.tsx index e4cbe2abec..fe158fb591 100644 --- a/app/client/src/pages/Editor/QueryEditor/index.tsx +++ b/app/client/src/pages/Editor/QueryEditor/index.tsx @@ -36,6 +36,7 @@ import Disabler from "pages/common/Disabler"; import ConvertToModuleInstanceCTA from "@appsmith/pages/Editor/EntityEditor/ConvertToModuleInstanceCTA"; import { MODULE_TYPE } from "@appsmith/constants/ModuleConstants"; import ConvertEntityNotification from "@appsmith/pages/common/ConvertEntityNotification"; +import { PluginType } from "entities/Action"; type QueryEditorProps = RouteComponentProps; @@ -83,12 +84,15 @@ function QueryEditor(props: QueryEditorProps) { name={action?.name || ""} pageId={pageId} /> - + {action?.pluginType !== PluginType.INTERNAL && ( + // Need to remove this check once workflow query is supported in module + + )} ), [ diff --git a/app/client/src/sagas/FormEvaluationSaga.ts b/app/client/src/sagas/FormEvaluationSaga.ts index 13f5fd25cd..7f02644f27 100644 --- a/app/client/src/sagas/FormEvaluationSaga.ts +++ b/app/client/src/sagas/FormEvaluationSaga.ts @@ -16,7 +16,7 @@ import type { Action, ActionConfig } from "entities/Action"; import type { FormConfigType } from "components/formControls/BaseControl"; import PluginsApi from "api/PluginApi"; import type { ApiResponse } from "api/ApiResponses"; -import { getAction } from "@appsmith/selectors/entitiesSelector"; +import { getAction, getPlugin } from "@appsmith/selectors/entitiesSelector"; import { getDataTreeActionConfigPath } from "entities/Action/actionProperties"; import { getDataTree } from "selectors/dataTreeSelectors"; import { getDynamicBindings, isDynamicValue } from "utils/DynamicBindingUtils"; @@ -29,6 +29,7 @@ import { } from "./helper"; import type { DatasourceConfiguration } from "entities/Datasource"; import { buffers } from "redux-saga"; +import type { Plugin } from "api/PluginApi"; export interface FormEvalActionPayload { formId: string; @@ -166,8 +167,14 @@ function* fetchDynamicValueSaga( dynamicFetchedValues.hasStarted = true; + const plugin: Plugin = yield select(getPlugin, pluginId); + let url = PluginsApi.defaultDynamicTriggerURL(datasourceId); + if (!!plugin && !plugin.requiresDatasource) { + url = PluginsApi.dynamicTriggerURLForInternalPlugins(pluginId); + } + if ( "url" in evaluatedConfig && !!evaluatedConfig.url && @@ -183,6 +190,7 @@ function* fetchDynamicValueSaga( let substitutedParameters = {}; const action: Action = yield select(getAction, actionId); + const { workspaceId } = action; const dataTree: DataTree = yield select(getDataTree); if (!!action) { @@ -199,8 +207,9 @@ function* fetchDynamicValueSaga( const dataTreeActionConfigPath = getDataTreeActionConfigPath(dynamicBindingValue); // then we get the value of the current parameter from the evaluatedValues in the action object stored in the dataTree. + // TODOD: Find a better way to pass the workspaceId const evaluatedValue = get( - evalAction?.__evaluation__?.evaluatedValues, + { ...evalAction?.__evaluation__?.evaluatedValues, workspaceId }, dataTreeActionConfigPath, ); // if it exists, we store it in the substituted params object. diff --git a/app/client/src/sagas/QueryPaneSagas.ts b/app/client/src/sagas/QueryPaneSagas.ts index 6a70f75cab..6e1371e46b 100644 --- a/app/client/src/sagas/QueryPaneSagas.ts +++ b/app/client/src/sagas/QueryPaneSagas.ts @@ -289,6 +289,13 @@ function* formValueChangeSaga( ) { currentEnvironment = Object.keys(datasourceStorages)[0]; } + let dsConfig = { + url: "", + }; + + if (plugin?.requiresDatasource) { + dsConfig = datasourceStorages[currentEnvironment].datasourceConfiguration; + } const postEvalActions = uiComponent === UIComponentTypes.UQIDbEditorForm ? [ @@ -299,7 +306,7 @@ function* formValueChangeSaga( values.pluginId, field, hasRouteChanged, - datasourceStorages[currentEnvironment]?.datasourceConfiguration, + dsConfig, ), ] : []; @@ -346,7 +353,14 @@ function* formValueChangeSaga( function* handleQueryCreatedSaga(actionPayload: ReduxAction) { const { actionConfiguration, id, pageId, pluginId, pluginType } = actionPayload.payload; - if (![PluginType.DB, PluginType.REMOTE, PluginType.AI].includes(pluginType)) + if ( + ![ + PluginType.DB, + PluginType.REMOTE, + PluginType.AI, + PluginType.INTERNAL, + ].includes(pluginType) + ) return; const pluginTemplates: Record = yield select(getPluginTemplates);