diff --git a/app/client/packages/design-system/widgets/src/components/Avatar/index.ts b/app/client/packages/design-system/widgets/src/components/Avatar/index.ts deleted file mode 100644 index 3bd16e178a..0000000000 --- a/app/client/packages/design-system/widgets/src/components/Avatar/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./src"; diff --git a/app/client/packages/design-system/widgets/src/components/Avatar/src/Avatar.tsx b/app/client/packages/design-system/widgets/src/components/Avatar/src/Avatar.tsx deleted file mode 100644 index d6c7dc28ed..0000000000 --- a/app/client/packages/design-system/widgets/src/components/Avatar/src/Avatar.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from "react"; -import { clsx } from "clsx"; -import { Text } from "@appsmith/wds"; - -import styles from "./styles.module.css"; -import type { AvatarProps } from "./types"; -import { getTypographyClassName } from "@appsmith/wds-theming"; - -export const Avatar = (props: AvatarProps) => { - const { className, label, size, src, ...rest } = props; - - const getLabelInitials = (label: string) => { - const names = label.split(" "); - - if (names.length === 1) { - return `${names[0].charAt(0)}`; - } - - return `${names[0].charAt(0)}${names[1]?.charAt(0)}`; - }; - - return ( - - {Boolean(src) ? ( - {label} - ) : ( - - {getLabelInitials(label)} - - )} - - ); -}; diff --git a/app/client/packages/design-system/widgets/src/components/Avatar/src/index.ts b/app/client/packages/design-system/widgets/src/components/Avatar/src/index.ts deleted file mode 100644 index 3a8b659aee..0000000000 --- a/app/client/packages/design-system/widgets/src/components/Avatar/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./types"; -export * from "./Avatar"; diff --git a/app/client/packages/design-system/widgets/src/components/Avatar/src/styles.module.css b/app/client/packages/design-system/widgets/src/components/Avatar/src/styles.module.css deleted file mode 100644 index abeb0956b1..0000000000 --- a/app/client/packages/design-system/widgets/src/components/Avatar/src/styles.module.css +++ /dev/null @@ -1,31 +0,0 @@ -.avatar { - display: inline-flex; - align-items: center; - justify-content: center; - width: var(--icon-size-3); - height: var(--icon-size-3); -} - -.avatar[data-size="small"] { - width: var(--icon-size-2); - height: var(--icon-size-2); -} - -.avatar[data-size="large"] { - width: var(--icon-size-4); - height: var(--icon-size-4); -} - -/* if the avatar has div, that means no source is provided. For this case, we want to add a background color and border radius */ -.avatar:has(div) { - background-color: var(--color-bg-assistive); - color: var(--color-fg-on-assistive); - border-radius: var(--border-radius-elevation-3); -} - -.avatarImage { - border-radius: inherit; - height: 100%; - object-fit: contain; - aspect-ratio: 1 / 1; -} diff --git a/app/client/packages/design-system/widgets/src/components/Avatar/src/types.ts b/app/client/packages/design-system/widgets/src/components/Avatar/src/types.ts deleted file mode 100644 index 3731a231d9..0000000000 --- a/app/client/packages/design-system/widgets/src/components/Avatar/src/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { HTMLProps } from "react"; - -export interface AvatarProps extends Omit, "size"> { - /** The label of the avatar */ - label: string; - /** The image source of the avatar */ - src?: string; - /** The size of the avatar - * - * @default "medium" - */ - size?: "small" | "medium" | "large"; -} diff --git a/app/client/packages/design-system/widgets/src/components/Avatar/stories/Avatar.stories.tsx b/app/client/packages/design-system/widgets/src/components/Avatar/stories/Avatar.stories.tsx deleted file mode 100644 index 10ca22796e..0000000000 --- a/app/client/packages/design-system/widgets/src/components/Avatar/stories/Avatar.stories.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from "react"; -import { Avatar, Flex } from "@appsmith/wds"; -import type { Meta, StoryObj } from "@storybook/react"; - -const meta: Meta = { - title: "WDS/Widgets/Avatar", - component: Avatar, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - label: "John Doe", - }, -}; - -export const WithImage: Story = { - args: { - label: "Jane Smith", - src: "https://assets.appsmith.com/integrations/25720743.png", - }, -}; - -export const SingleInitial: Story = { - args: { - label: "Alice", - }, -}; - -export const Sizes: Story = { - args: { - label: "Alice", - }, - render: (args) => ( - - - - - - ), -}; diff --git a/app/client/packages/design-system/widgets/src/components/Markdown/src/mdComponents/Heading.tsx b/app/client/packages/design-system/widgets/src/components/Markdown/src/mdComponents/Heading.tsx index 6f439aa2ac..55ff67d953 100644 --- a/app/client/packages/design-system/widgets/src/components/Markdown/src/mdComponents/Heading.tsx +++ b/app/client/packages/design-system/widgets/src/components/Markdown/src/mdComponents/Heading.tsx @@ -19,6 +19,7 @@ const createHeading = ( fontWeight={fontWeight} ref={ref as Ref} size={size} + wordBreak="break-word" > {children} diff --git a/app/client/packages/design-system/widgets/src/components/Markdown/src/mdComponents/Link.tsx b/app/client/packages/design-system/widgets/src/components/Markdown/src/mdComponents/Link.tsx index 29582c3a0c..b90e52e110 100644 --- a/app/client/packages/design-system/widgets/src/components/Markdown/src/mdComponents/Link.tsx +++ b/app/client/packages/design-system/widgets/src/components/Markdown/src/mdComponents/Link.tsx @@ -10,7 +10,13 @@ export const a = (props: LinkProps) => { const { children, href } = props; return ( - + {children} ); diff --git a/app/client/packages/design-system/widgets/src/components/Markdown/src/styles.module.css b/app/client/packages/design-system/widgets/src/components/Markdown/src/styles.module.css index 96036ed4d1..7b60c6ee7e 100644 --- a/app/client/packages/design-system/widgets/src/components/Markdown/src/styles.module.css +++ b/app/client/packages/design-system/widgets/src/components/Markdown/src/styles.module.css @@ -97,7 +97,7 @@ background-color: var(--color-bg-elevation-2); border-radius: var(--border-radius-elevation-3); outline: var(--border-width-1) solid var(--color-bg-neutral-subtle); - margin-bottom: var(--spacing-2); + margin-bottom: var(--inner-spacing-2); overflow: auto; } diff --git a/app/client/packages/design-system/widgets/src/index.ts b/app/client/packages/design-system/widgets/src/index.ts index 911e15b14c..277f078a90 100644 --- a/app/client/packages/design-system/widgets/src/index.ts +++ b/app/client/packages/design-system/widgets/src/index.ts @@ -29,7 +29,6 @@ export * from "./components/Radio"; export * from "./components/ListBox"; export * from "./components/ListBoxItem"; export * from "./components/MenuItem"; -export * from "./components/Avatar"; export * from "./components/Markdown"; export * from "./utils"; diff --git a/app/client/src/WidgetProvider/constants.ts b/app/client/src/WidgetProvider/constants.ts index 0627b51862..cb0837d608 100644 --- a/app/client/src/WidgetProvider/constants.ts +++ b/app/client/src/WidgetProvider/constants.ts @@ -208,6 +208,10 @@ export enum BlueprintOperationTypes { UPDATE_CREATE_PARAMS_BEFORE_ADD = "UPDATE_CREATE_PARAMS_BEFORE_ADD", } +export enum BlueprintOperationActionTypes { + CREATE_OR_UPDATE_DATASOURCE_WITH_ACTION = "CREATE_OR_UPDATE_DATASOURCE_WITH_ACTION", +} + export type FlattenedWidgetProps = WidgetProps & { children?: string[]; }; diff --git a/app/client/src/ce/pages/Applications/index.tsx b/app/client/src/ce/pages/Applications/index.tsx index 4bbdf51459..df8d6176a5 100644 --- a/app/client/src/ce/pages/Applications/index.tsx +++ b/app/client/src/ce/pages/Applications/index.tsx @@ -752,7 +752,7 @@ export function ApplicationsSection(props: any) { ) { createNewApplication( getNextEntityName( - "Untitled application ", + isAnvilEnabled ? "AI app " : "Untitled application ", // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any applications.map((el: any) => el.name), diff --git a/app/client/src/layoutSystems/anvil/editor/styles.module.css b/app/client/src/layoutSystems/anvil/editor/styles.module.css index 1e40e18533..3d2ccdd43a 100644 --- a/app/client/src/layoutSystems/anvil/editor/styles.module.css +++ b/app/client/src/layoutSystems/anvil/editor/styles.module.css @@ -3,4 +3,12 @@ & > * { pointer-events: none; } + + /* + This is a temporary solution. According to the product requirements, we need to make AI chat widget interactive. + This code can be deleted when full-fledged inline editing feature is implemented. + */ + & [data-widget-name*="AIChat"] > * { + pointer-events: all; + } } diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index 60c375e769..92e2a26ef9 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -1,3 +1,56 @@ +import { toast } from "@appsmith/ads"; +import { objectKeys } from "@appsmith/utils"; +import { fetchDatasourceStructure } from "actions/datasourceActions"; +import { + setIdeEditorViewMode, + setShowQueryCreateNewModal, +} from "actions/ideActions"; +import { + closeQueryActionTab, + closeQueryActionTabSuccess, + copyActionError, + copyActionSuccess, + createActionInit, + createActionSuccess, + createNewApiAction, + createNewQueryAction, + deleteActionSuccess, + fetchActionsForPage, + fetchActionsForPageSuccess, + type FetchActionsPayload, + moveActionError, + moveActionSuccess, + type SetActionPropertyPayload, + updateAction, + updateActionData, + updateActionProperty, + updateActionSuccess, +} from "actions/pluginActionActions"; +import { setSnipingMode as setSnipingModeAction } from "actions/propertyPaneActions"; +import type { ActionCreateUpdateResponse } from "api/ActionAPI"; +import ActionAPI from "api/ActionAPI"; +import type { ApiResponse } from "api/ApiResponses"; +import type { FetchPageRequest, FetchPageResponse } from "api/PageApi"; +import PageApi from "api/PageApi"; +import type { Plugin } from "api/PluginApi"; +import { EditorModes } from "components/editorComponents/CodeEditor/EditorConfig"; +import { + fixActionPayloadForMongoQuery, + getConfigInitialValues, +} from "components/formControls/utils"; +import { INTEGRATION_TABS } from "constants/routes"; +import { + API_EDITOR_FORM_NAME, + QUERY_EDITOR_FORM_NAME, +} from "ee/constants/forms"; +import { + ACTION_COPY_SUCCESS, + ACTION_MOVE_SUCCESS, + createMessage, + ERROR_ACTION_COPY_FAIL, + ERROR_ACTION_MOVE_FAIL, + ERROR_ACTION_RENAME_FAIL, +} from "ee/constants/messages"; import type { EvaluationReduxAction, ReduxAction, @@ -6,6 +59,61 @@ import { ReduxActionErrorTypes, ReduxActionTypes, } from "ee/constants/ReduxActionConstants"; +import { ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils"; +import { CreateNewActionKey } from "ee/entities/Engine/actionHelpers"; +import { EditorViewMode, IDE_TYPE } from "ee/entities/IDE/constants"; +import { getIDETypeByUrl } from "ee/entities/IDE/utils"; +import type { ActionData } from "ee/reducers/entityReducers/actionsReducer"; +import { + apiEditorIdURL, + builderURL, + integrationEditorURL, + queryEditorIdURL, + saasEditorApiIdURL, +} from "ee/RouteBuilder"; +import { updateActionAPICall } from "ee/sagas/ApiCallerSagas"; +import { + generateDestinationIdInfoForQueryDuplication, + resolveParentEntityMetadata, +} from "ee/sagas/helpers"; +import { updateCanvasWithDSL } from "ee/sagas/PageSagas"; +import { + getAction, + getCurrentPageNameByActionId, + getDatasource, + getDatasources, + getDatasourceStructureById, + getEditorConfig, + getNewEntityName, + getPageNameByPageId, + getPlugin, + getSettingConfig, +} from "ee/selectors/entitiesSelector"; +import AnalyticsUtil from "ee/utils/AnalyticsUtil"; +import type { + Action, + ActionViewMode, + ApiAction, + ApiActionConfig, + BaseAction, + CreateActionDefaultsParams, + SlashCommandPayload, +} from "entities/Action"; +import { + ActionCreationSourceTypeEnum, + isAPIAction, + isGraphqlPlugin, + PluginPackageName, + PluginType, + SlashCommand, +} from "entities/Action"; +import LOG_TYPE from "entities/AppsmithConsole/logtype"; +import type { Datasource, DatasourceStructure } from "entities/Datasource"; +import { get, isEmpty, merge } from "lodash"; +import { DEFAULT_API_ACTION_CONFIG } from "PluginActionEditor/constants/ApiEditorConstants"; +import { DEFAULT_GRAPHQL_ACTION_CONFIG } from "PluginActionEditor/constants/GraphQLEditorConstants"; +import { transformRestAction } from "PluginActionEditor/transformers/RestActionTransformer"; +import { getFormValues } from "redux-form"; import { all, call, @@ -18,137 +126,28 @@ import { takeEvery, takeLatest, } from "redux-saga/effects"; -import type { Datasource, DatasourceStructure } from "entities/Datasource"; -import type { ActionCreateUpdateResponse } from "api/ActionAPI"; -import ActionAPI from "api/ActionAPI"; -import type { ApiResponse } from "api/ApiResponses"; -import type { FetchPageRequest, FetchPageResponse } from "api/PageApi"; -import PageApi from "api/PageApi"; -import { updateCanvasWithDSL } from "ee/sagas/PageSagas"; -import { - closeQueryActionTab, - closeQueryActionTabSuccess, - createNewApiAction, - createNewQueryAction, - type FetchActionsPayload, - type SetActionPropertyPayload, -} from "actions/pluginActionActions"; -import { - copyActionError, - copyActionSuccess, - createActionInit, - createActionSuccess, - deleteActionSuccess, - fetchActionsForPage, - fetchActionsForPageSuccess, - moveActionError, - moveActionSuccess, - updateAction, - updateActionData, - updateActionProperty, - updateActionSuccess, -} from "actions/pluginActionActions"; -import { getDynamicBindingsChangesSaga } from "utils/DynamicBindingUtils"; -import { validateResponse } from "./ErrorSagas"; -import { transformRestAction } from "PluginActionEditor/transformers/RestActionTransformer"; import { getCurrentBasePageId, getCurrentPageId, } from "selectors/editorSelectors"; -import AnalyticsUtil from "ee/utils/AnalyticsUtil"; -import type { - Action, - ActionViewMode, - ApiAction, - ApiActionConfig, - BaseAction, - CreateActionDefaultsParams, - SlashCommandPayload, -} from "entities/Action"; -import { isGraphqlPlugin, ActionCreationSourceTypeEnum } from "entities/Action"; -import { - isAPIAction, - PluginPackageName, - PluginType, - SlashCommand, -} from "entities/Action"; -import type { ActionData } from "ee/reducers/entityReducers/actionsReducer"; -import { - getAction, - getCurrentPageNameByActionId, - getDatasource, - getDatasourceStructureById, - getDatasources, - getEditorConfig, - getPageNameByPageId, - getPlugin, - getSettingConfig, - getNewEntityName, -} from "ee/selectors/entitiesSelector"; -import history from "utils/history"; -import { INTEGRATION_TABS } from "constants/routes"; -import { - ACTION_COPY_SUCCESS, - ACTION_MOVE_SUCCESS, - createMessage, - ERROR_ACTION_COPY_FAIL, - ERROR_ACTION_MOVE_FAIL, - ERROR_ACTION_RENAME_FAIL, -} from "ee/constants/messages"; -import { get, isEmpty, merge } from "lodash"; -import { - fixActionPayloadForMongoQuery, - getConfigInitialValues, -} from "components/formControls/utils"; +import { getIsSideBySideEnabled } from "selectors/ideSelectors"; +import { convertToBaseParentEntityIdSelector } from "selectors/pageListSelectors"; import AppsmithConsole from "utils/AppsmithConsole"; -import { ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils"; -import LOG_TYPE from "entities/AppsmithConsole/logtype"; -import type { Plugin } from "api/PluginApi"; +import { getDynamicBindingsChangesSaga } from "utils/DynamicBindingUtils"; +import { getDefaultTemplateActionConfig } from "utils/editorContextUtils"; import { shouldBeDefined } from "utils/helpers"; +import history from "utils/history"; +import { setAIPromptTriggered } from "utils/storage"; +import { sendAnalyticsEventSaga } from "./AnalyticsSaga"; +import { validateResponse } from "./ErrorSagas"; +import FocusRetention from "./FocusRetentionSaga"; import { - apiEditorIdURL, - builderURL, - integrationEditorURL, - queryEditorIdURL, - saasEditorApiIdURL, -} from "ee/RouteBuilder"; -import { - RequestPayloadAnalyticsPath, checkAndLogErrorsIfCyclicDependency, enhanceRequestPayloadWithEventData, getFromServerWhenNoPrefetchedResult, + RequestPayloadAnalyticsPath, } from "./helper"; -import { setSnipingMode as setSnipingModeAction } from "actions/propertyPaneActions"; -import { toast } from "@appsmith/ads"; -import { getFormValues } from "redux-form"; -import { - API_EDITOR_FORM_NAME, - QUERY_EDITOR_FORM_NAME, -} from "ee/constants/forms"; -import { DEFAULT_GRAPHQL_ACTION_CONFIG } from "PluginActionEditor/constants/GraphQLEditorConstants"; -import { DEFAULT_API_ACTION_CONFIG } from "PluginActionEditor/constants/ApiEditorConstants"; -import { fetchDatasourceStructure } from "actions/datasourceActions"; -import { setAIPromptTriggered } from "utils/storage"; -import { getDefaultTemplateActionConfig } from "utils/editorContextUtils"; -import { sendAnalyticsEventSaga } from "./AnalyticsSaga"; -import { EditorModes } from "components/editorComponents/CodeEditor/EditorConfig"; -import { updateActionAPICall } from "ee/sagas/ApiCallerSagas"; -import FocusRetention from "./FocusRetentionSaga"; -import { - generateDestinationIdInfoForQueryDuplication, - resolveParentEntityMetadata, -} from "ee/sagas/helpers"; import { handleQueryEntityRedirect } from "./IDESaga"; -import { EditorViewMode, IDE_TYPE } from "ee/entities/IDE/constants"; -import { getIDETypeByUrl } from "ee/entities/IDE/utils"; -import { - setIdeEditorViewMode, - setShowQueryCreateNewModal, -} from "actions/ideActions"; -import { getIsSideBySideEnabled } from "selectors/ideSelectors"; -import { CreateNewActionKey } from "ee/entities/Engine/actionHelpers"; -import { objectKeys } from "@appsmith/utils"; -import { convertToBaseParentEntityIdSelector } from "selectors/pageListSelectors"; export const DEFAULT_PREFIX = { QUERY: "Query", @@ -283,9 +282,7 @@ export function* getPluginActionDefaultValues(pluginId: string) { */ export function* createActionRequestSaga( actionPayload: ReduxAction< - // TODO: Fix this the next time the file is edited - // eslint-disable-next-line @typescript-eslint/no-explicit-any - Partial & { eventData: any; pluginId: string } + Partial & { eventData?: unknown; pluginId: string } >, ) { const payload = { ...actionPayload.payload }; @@ -314,13 +311,11 @@ export function* createActionRequestSaga( DEFAULT_PREFIX.QUERY; } - const name: string = yield select(getNewEntityName, { + payload.name = yield select(getNewEntityName, { prefix, parentEntityId, parentEntityKey, }); - - payload.name = name; } yield put(createActionInit(payload)); diff --git a/app/client/src/sagas/DatasourcesSagas.ts b/app/client/src/sagas/DatasourcesSagas.ts index 40cff1e2c2..a7496c2b5d 100644 --- a/app/client/src/sagas/DatasourcesSagas.ts +++ b/app/client/src/sagas/DatasourcesSagas.ts @@ -33,7 +33,11 @@ import { getCurrentBasePageId, getCurrentPageId, } from "selectors/editorSelectors"; -import type { DatasourceGroupByPluginCategory } from "ee/selectors/entitiesSelector"; +import { + type DatasourceGroupByPluginCategory, + getActions, + getDatasourceByPluginId, +} from "ee/selectors/entitiesSelector"; import { getDatasource, getDatasourceActionRouteInfo, @@ -80,7 +84,6 @@ import type { import { AuthenticationStatus, FilePickerActionStatus, - ToastMessageType, } from "entities/Datasource"; import { INTEGRATION_TABS, @@ -93,6 +96,10 @@ import { DATASOURCE_DB_FORM, DATASOURCE_REST_API_FORM, } from "ee/constants/forms"; +import type { ActionDataState } from "ee/reducers/entityReducers/actionsReducer"; +import { setIdeEditorViewMode } from "../actions/ideActions"; +import { EditorViewMode } from "ee/entities/IDE/constants"; +import { createActionRequestSaga } from "./ActionSagas"; import { validateResponse } from "./ErrorSagas"; import AnalyticsUtil from "ee/utils/AnalyticsUtil"; import type { GetFormData } from "selectors/formSelectors"; @@ -121,7 +128,7 @@ import localStorage from "utils/localStorage"; import log from "loglevel"; import { APPSMITH_TOKEN_STORAGE_KEY } from "pages/Editor/SaaSEditor/constants"; import { checkAndGetPluginFormConfigsSaga } from "sagas/PluginSagas"; -import { PluginPackageName, PluginType } from "entities/Action"; +import { type Action, PluginPackageName, PluginType } from "entities/Action"; import LOG_TYPE from "entities/AppsmithConsole/logtype"; import { isDynamicValue } from "utils/DynamicBindingUtils"; import { getQueryParams } from "utils/URLUtils"; @@ -149,12 +156,10 @@ import { saasEditorDatasourceIdURL, } from "ee/RouteBuilder"; import { - DATASOURCE_NAME_DEFAULT_PREFIX, GOOGLE_SHEET_FILE_PICKER_OVERLAY_CLASS, GOOGLE_SHEET_SPECIFIC_SHEETS_SCOPE, TEMP_DATASOURCE_ID, } from "constants/Datasource"; -import { getUntitledDatasourceSequence } from "utils/DatasourceSagaUtils"; import { toast } from "@appsmith/ads"; import { fetchPluginFormConfig } from "actions/pluginActions"; import { addClassToDocumentRoot } from "pages/utils"; @@ -175,7 +180,11 @@ import { getCurrentGitBranch } from "selectors/gitSyncSelectors"; import FocusRetention from "./FocusRetentionSaga"; import { identifyEntityFromPath } from "../navigation/FocusEntity"; import { MAX_DATASOURCE_SUGGESTIONS } from "constants/DatasourceEditorConstants"; -import { getFromServerWhenNoPrefetchedResult } from "./helper"; +import { + getFromServerWhenNoPrefetchedResult, + getInitialActionPayload, + getInitialDatasourcePayload, +} from "./helper"; import { executeGoogleApi } from "./loadGoogleApi"; import type { ActionParentEntityTypeInterface } from "ee/entities/Engine/actionHelpers"; import { getCurrentModuleId } from "ee/selectors/modulesSelector"; @@ -1061,9 +1070,6 @@ function* createTempDatasourceFromFormSaga( ); const initialValues: unknown = yield call(getConfigInitialValues, formConfig); - const dsList: Datasource[] = yield select(getDatasources); - const sequence = getUntitledDatasourceSequence(dsList); - let datasourceType = actionPayload?.payload?.type; if (!actionPayload?.payload.type) { @@ -1076,25 +1082,11 @@ function* createTempDatasourceFromFormSaga( } const defaultEnvId = getDefaultEnvId(); + const initialPayload: Datasource = yield getInitialDatasourcePayload( + actionPayload.payload.pluginId, + datasourceType, + ); - const initialPayload = { - id: TEMP_DATASOURCE_ID, - name: DATASOURCE_NAME_DEFAULT_PREFIX + sequence, - type: datasourceType, - pluginId: actionPayload.payload.pluginId, - new: false, - datasourceStorages: { - [defaultEnvId]: { - datasourceId: TEMP_DATASOURCE_ID, - environmentId: defaultEnvId, - isValid: false, - datasourceConfiguration: { - properties: [], - }, - toastMessage: ToastMessageType.EMPTY_TOAST_MESSAGE, - }, - }, - }; const payload = merge(initialPayload, actionPayload.payload); payload.datasourceStorages[defaultEnvId] = merge( @@ -1128,7 +1120,60 @@ function* createTempDatasourceFromFormSaga( ); } -function* createDatasourceFromFormSaga( +/** + * Verifies whether a datasource for the specified plugin exists. If it does not, creates one. + * Then, creates an action for the datasource based on passed action configuration. + * @returns Action - return the created Action + * @param pluginPackageName - determine whether a datasource exists by its pluginPackageName. + * @param actionConfig - configuration for action creation + * @param datasourceName - name with which the datasource will be created + */ +export function* createOrUpdateDataSourceWithAction( + pluginPackageName: PluginPackageName, + actionConfig: Action, + datasourceName?: string, +) { + const plugin: Plugin = yield select( + getPluginByPackageName, + pluginPackageName, + ); + const datasources: Datasource[] = yield select( + getDatasourceByPluginId, + plugin.id, + ); + const pageId: string = yield select(getCurrentPageId); + const datasourcePayload: Datasource = yield getInitialDatasourcePayload( + plugin.id, + plugin.type, + datasourceName, + ); + + if (datasources.length === 0) { + yield createDatasourceFromFormSaga({ + payload: datasourcePayload, + type: ReduxActionTypes.CREATE_DATASOURCE_FROM_FORM_INIT, + }); + } + + const actionPayload: Datasource = yield getInitialActionPayload( + pageId, + plugin.id, + actionConfig, + ); + + yield createActionRequestSaga({ + payload: actionPayload, + type: ReduxActionTypes.CREATE_ACTION_REQUEST, + }); + + yield put(setIdeEditorViewMode(EditorViewMode.SplitScreen)); + + const actions: ActionDataState = yield select(getActions); + + return actions[actions.length - 1]; +} + +export function* createDatasourceFromFormSaga( actionPayload: ReduxActionWithCallbacks, ) { try { diff --git a/app/client/src/sagas/WidgetBlueprintSagas.ts b/app/client/src/sagas/WidgetBlueprintSagas.ts index 10d36ba137..6e2cd0c6db 100644 --- a/app/client/src/sagas/WidgetBlueprintSagas.ts +++ b/app/client/src/sagas/WidgetBlueprintSagas.ts @@ -1,4 +1,8 @@ -import type { WidgetBlueprint } from "WidgetProvider/constants"; +import type { ActionData } from "ee/reducers/entityReducers/actionsReducer"; +import { + BlueprintOperationActionTypes, + type WidgetBlueprint, +} from "WidgetProvider/constants"; import type { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer"; import type { WidgetProps } from "widgets/BaseWidget"; import { generateReactKey } from "utils/generators"; @@ -13,6 +17,8 @@ import * as log from "loglevel"; import { toast } from "@appsmith/ads"; import type { LayoutSystemTypes } from "layoutSystems/types"; import { getLayoutSystemType } from "selectors/layoutSystemSelectors"; +import type { Action, PluginPackageName } from "../entities/Action"; +import { createOrUpdateDataSourceWithAction } from "./DatasourcesSagas"; function buildView(view: WidgetBlueprint["view"], widgetId: string) { const children = []; @@ -60,12 +66,15 @@ export interface UpdatePropertyArgs { // eslint-disable-next-line @typescript-eslint/no-explicit-any propertyValue: any; } -export type BlueprintOperationAddActionFn = () => void; +export type BlueprintOperationAddActionFn = ( + widget: WidgetProps & { children?: WidgetProps[] }, +) => Generator; export type BlueprintOperationModifyPropsFn = ( widget: WidgetProps & { children?: WidgetProps[] }, widgets: { [widgetId: string]: FlattenedWidgetProps }, parent?: WidgetProps, layoutSystemType?: LayoutSystemTypes, + addActionResult?: ActionData, ) => UpdatePropertyArgs[] | undefined; export interface ChildOperationFnResponse { @@ -97,10 +106,20 @@ export type BlueprintOperationFunction = | BlueprintBeforeOperationsFn; export type BlueprintOperationType = keyof typeof BlueprintOperationTypes; +export type BlueprintOperationActionType = + keyof typeof BlueprintOperationActionTypes; + +export interface BlueprintOperationActionPayload { + pluginPackageName: PluginPackageName; + actionConfig: Action; + datasourceName?: string; +} export interface BlueprintOperation { type: BlueprintOperationType; fn: BlueprintOperationFunction; + actionType?: BlueprintOperationActionType; + payload?: BlueprintOperationActionPayload; } export function* executeWidgetBlueprintOperations( @@ -109,13 +128,19 @@ export function* executeWidgetBlueprintOperations( widgetId: string, ) { const layoutSystemType: LayoutSystemTypes = yield select(getLayoutSystemType); + let addActionResult: ActionData = {} as ActionData; - operations.forEach((operation: BlueprintOperation) => { + for (const operation of operations) { const widget: WidgetProps & { children?: string[] | WidgetProps[] } = { ...widgets[widgetId], }; switch (operation.type) { + case BlueprintOperationTypes.ADD_ACTION: + addActionResult = + yield executeWidgetBlueprintAddActionOperations(operation); + + break; case BlueprintOperationTypes.MODIFY_PROPS: if (widget.children && widget.children.length > 0) { widget.children = (widget.children as string[]).map( @@ -130,6 +155,7 @@ export function* executeWidgetBlueprintOperations( widgets, get(widgets, widget.parentId || "", undefined), layoutSystemType, + addActionResult, ); updatePropertyPayloads && @@ -139,13 +165,44 @@ export function* executeWidgetBlueprintOperations( }); break; } - }); + } const result: { [widgetId: string]: FlattenedWidgetProps } = yield widgets; return result; } +/** + * this saga executes the blueprint add action operation + * @param operation + */ +function* executeWidgetBlueprintAddActionOperations( + operation: BlueprintOperation, +) { + switch (operation.actionType) { + case BlueprintOperationActionTypes.CREATE_OR_UPDATE_DATASOURCE_WITH_ACTION: + if ( + !operation.payload?.pluginPackageName || + !operation.payload?.actionConfig + ) + return; + + const { actionConfig, datasourceName, pluginPackageName } = + operation.payload; + + // TODO Add the event to the watcher to avoid importing it and the associated cyclic dependencies. + // https://github.com/appsmithorg/appsmith-ee/pull/5368#discussion_r1804419760 + const createdAction: ActionData = + yield createOrUpdateDataSourceWithAction( + pluginPackageName, + actionConfig, + datasourceName, + ); + + return createdAction; + } +} + /** * this saga executes the blueprint child operation * diff --git a/app/client/src/sagas/helper.ts b/app/client/src/sagas/helper.ts index c720022a3f..bdb6cfc740 100644 --- a/app/client/src/sagas/helper.ts +++ b/app/client/src/sagas/helper.ts @@ -1,9 +1,14 @@ import { createMessage } from "ee/constants/messages"; import type { LayoutOnLoadActionErrors } from "constants/AppsmithActionConstants/ActionConstants"; +import type { + ActionData, + ActionDataState, +} from "ee/reducers/entityReducers/actionsReducer"; import type { FormEvalOutput, ConditionalOutput, } from "reducers/evaluationReducers/formEvaluationReducer"; +import { select } from "redux-saga/effects"; import AppsmithConsole from "utils/AppsmithConsole"; import LOG_TYPE from "entities/AppsmithConsole/logtype"; import type { Log } from "entities/AppsmithConsole"; @@ -22,6 +27,18 @@ import { isPlainObject, isString } from "lodash"; import { DATA_BIND_REGEX_GLOBAL } from "constants/BindingsConstants"; import { apiFailureResponseInterceptor } from "api/interceptors"; import { klonaLiteWithTelemetry } from "utils/helpers"; +import { getDefaultEnvId } from "ee/api/ApiUtils"; +import { + getActions, + getDatasourceByPluginId, + getDatasources, +} from "ee/selectors/entitiesSelector"; +import { + DATASOURCE_NAME_DEFAULT_PREFIX, + TEMP_DATASOURCE_ID, +} from "../constants/Datasource"; +import { type Datasource, ToastMessageType } from "../entities/Datasource"; +import { getNextEntityName } from "utils/AppsmithUtils"; // function to extract all objects that have dynamic values export const extractFetchDynamicValueFormConfigs = ( @@ -249,3 +266,63 @@ export function* getFromServerWhenNoPrefetchedResult( return yield apiEffect(); } + +export function* getInitialDatasourcePayload( + pluginId: string, + pluginType?: string, + defaultDatasourceName: string = DATASOURCE_NAME_DEFAULT_PREFIX, +) { + const dsList: Datasource[] = yield select(getDatasources); + const datasourceName = getNextEntityName( + defaultDatasourceName, + dsList.map((el: Datasource) => el.name), + ); + const defaultEnvId = getDefaultEnvId(); + + return { + id: TEMP_DATASOURCE_ID, + name: datasourceName, + type: pluginType, + pluginId: pluginId, + new: false, + datasourceStorages: { + [defaultEnvId]: { + datasourceId: TEMP_DATASOURCE_ID, + environmentId: defaultEnvId, + isValid: false, + datasourceConfiguration: { + url: "", + properties: [], + }, + toastMessage: ToastMessageType.EMPTY_TOAST_MESSAGE, + }, + }, + }; +} + +export function* getInitialActionPayload( + pageId: string, + pluginId: string, + actionConfig: Action, +) { + const updatedAiDatasources: Datasource[] = yield select( + getDatasourceByPluginId, + pluginId, + ); + + const actions: ActionDataState = yield select(getActions); + const actionName = getNextEntityName( + actionConfig.name, + actions.map((el: ActionData) => el.config.name), + ); + + return { + pageId, + pluginId: updatedAiDatasources[0].pluginId, + datasource: { + id: updatedAiDatasources[0].id, + }, + name: actionName, + actionConfiguration: actionConfig.actionConfiguration, + }; +} diff --git a/app/client/src/utils/DatasourceSagaUtils.tsx b/app/client/src/utils/DatasourceSagaUtils.tsx deleted file mode 100644 index c5cd4943b4..0000000000 --- a/app/client/src/utils/DatasourceSagaUtils.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { DATASOURCE_NAME_DEFAULT_PREFIX } from "constants/Datasource"; -import type { Datasource } from "entities/Datasource"; - -/** - * - * @param datasoures Array of datasource objects - * @returns next sequence number for untitled datasources - */ -export function getUntitledDatasourceSequence( - dsList: Array, -): number { - let maxSeq = Number.MIN_VALUE; - - dsList - .filter((ele) => ele.name.includes(DATASOURCE_NAME_DEFAULT_PREFIX)) - .forEach((ele) => { - const seq = parseInt(ele.name.split(" ")[2]); - - if (!isNaN(seq) && maxSeq < seq) { - maxSeq = seq; - } - }); - - return maxSeq === Number.MIN_VALUE ? 1 : maxSeq + 1; -}