chore: add new ADD_ACTION blueprint operation type (#37078)

## Description
This PR is CE part of main [EE
PR](https://github.com/appsmithorg/appsmith-ee/pull/5368). This PR
implements a new ADD_ACTION blueprint operation type.

## Automation

/ok-to-test tags="@tag.All"

### 🔍 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/11508513702>
> Commit: ef5ed889ab5711bab162c2a947758da3726f64b5
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=11508513702&attempt=3"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Fri, 25 Oct 2024 07:17:20 UTC
<!-- end of auto-generated comment: Cypress test results  -->


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


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

## Summary by CodeRabbit

- **New Features**
	- Introduced a new enum for handling data source creation and updates.
- Enhanced application creation logic with updated naming conventions
based on layout features.
- Improved workspace management functionalities, including application
import and user invitation processes.

- **Bug Fixes**
- Enhanced error handling and state management across various components
and sagas.

- **Documentation**
- Added comments to clarify temporary solutions in CSS for widget
interactions.

- **Chores**
- Removed obsolete Avatar component and related files, streamlining the
design system.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Valera Melnikov 2024-10-25 12:00:34 +03:00 committed by GitHub
parent 56ef4309e1
commit 839e89e8ea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 353 additions and 313 deletions

View File

@ -1 +0,0 @@
export * from "./src";

View File

@ -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 (
<span
className={clsx(styles.avatar, className)}
{...rest}
data-size={size ? size : undefined}
>
{Boolean(src) ? (
<img alt={label} className={styles.avatarImage} src={src} />
) : (
<Text className={getTypographyClassName("body")} fontWeight={500}>
{getLabelInitials(label)}
</Text>
)}
</span>
);
};

View File

@ -1,2 +0,0 @@
export * from "./types";
export * from "./Avatar";

View File

@ -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;
}

View File

@ -1,13 +0,0 @@
import type { HTMLProps } from "react";
export interface AvatarProps extends Omit<HTMLProps<HTMLSpanElement>, "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";
}

View File

@ -1,43 +0,0 @@
import React from "react";
import { Avatar, Flex } from "@appsmith/wds";
import type { Meta, StoryObj } from "@storybook/react";
const meta: Meta<typeof Avatar> = {
title: "WDS/Widgets/Avatar",
component: Avatar,
};
export default meta;
type Story = StoryObj<typeof Avatar>;
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) => (
<Flex gap="spacing-2">
<Avatar {...args} size="small" />
<Avatar {...args} size="medium" />
<Avatar {...args} size="large" />
</Flex>
),
};

View File

@ -19,6 +19,7 @@ const createHeading = (
fontWeight={fontWeight}
ref={ref as Ref<HTMLDivElement>}
size={size}
wordBreak="break-word"
>
{children}
</Text>

View File

@ -10,7 +10,13 @@ export const a = (props: LinkProps) => {
const { children, href } = props;
return (
<Link data-component="a" href={href} rel="noreferrer" target="_blank">
<Link
data-component="a"
href={href}
rel="noreferrer"
target="_blank"
wordBreak="break-word"
>
{children}
</Link>
);

View File

@ -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;
}

View File

@ -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";

View File

@ -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[];
};

View File

@ -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),

View File

@ -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;
}
}

View File

@ -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<Action> & { eventData: any; pluginId: string }
Partial<Action> & { 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));

View File

@ -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<Datasource, unknown, unknown>,
) {
try {

View File

@ -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
*

View File

@ -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,
};
}

View File

@ -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<Datasource>,
): 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;
}