## Description Added ESLint rule to force blank lines between statements. Fixes #`Issue Number` _or_ Fixes `Issue URL` > [!WARNING] > _If no issue exists, please create an issue first, and check with the maintainers if the issue is valid._ ## Automation /ok-to-test tags="@tag.All" ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!CAUTION] > 🔴 🔴 🔴 Some tests have failed. > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/10924926728> > Commit: 34f57714a1575ee04e94e03cbcaf95e57a96c86c > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=10924926728&attempt=1&selectiontype=test&testsstatus=failed&specsstatus=fail" target="_blank">Cypress dashboard</a>. > Tags: @tag.All > Spec: > The following are new failures, please fix them before merging the PR: <ol> > <li>cypress/e2e/Regression/ClientSide/Anvil/AnvilModal_spec.ts > <li>cypress/e2e/Regression/ClientSide/Anvil/Widgets/AnvilButtonWidgetSnapshot_spec.ts > <li>cypress/e2e/Regression/ClientSide/Anvil/Widgets/AnvilCheckboxGroupWidgetSnapshot_spec.ts > <li>cypress/e2e/Regression/ClientSide/Anvil/Widgets/AnvilCurrencyInputWidgetSnapshot_spec.ts > <li>cypress/e2e/Regression/ClientSide/Anvil/Widgets/AnvilIconButtonWidgetSnapshot_spec.ts > <li>cypress/e2e/Regression/ClientSide/Anvil/Widgets/AnvilInlineButtonWidgetSnapshot_spec.ts > <li>cypress/e2e/Regression/ClientSide/Anvil/Widgets/AnvilInputWidgetSnapshot_spec.ts > <li>cypress/e2e/Regression/ClientSide/Anvil/Widgets/AnvilParagraphWidgetSnapshot_spec.ts > <li>cypress/e2e/Regression/ClientSide/Anvil/Widgets/AnvilPhoneInputWidgetSnapshot_spec.ts > <li>cypress/e2e/Regression/ClientSide/Anvil/Widgets/AnvilStatsWidgetSnapshot_spec.ts > <li>cypress/e2e/Regression/ClientSide/Anvil/Widgets/AnvilSwitchGroupWidgetSnapshot_spec.ts > <li>cypress/e2e/Regression/ClientSide/Anvil/Widgets/AnvilSwitchWidgetSnapshot_spec.ts > <li>cypress/e2e/Regression/ClientSide/Anvil/Widgets/AnvilTableWidgetSnapshot_spec.ts > <li>cypress/e2e/Regression/ClientSide/Anvil/Widgets/AnvilToolbarButtonWidgetSnapshot_spec.ts > <li>cypress/e2e/Regression/ClientSide/Anvil/Widgets/AnvilZoneSectionWidgetSnapshot_spec.ts</ol> > <a href="https://internal.appsmith.com/app/cypress-dashboard/identified-flaky-tests-65890b3c81d7400d08fa9ee3?branch=master" target="_blank">List of identified flaky tests</a>. > <hr>Wed, 18 Sep 2024 16:33:36 UTC <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No --------- Co-authored-by: Valera Melnikov <valera@appsmith.com>
411 lines
12 KiB
TypeScript
411 lines
12 KiB
TypeScript
import type { ReduxAction } from "ee/constants/ReduxActionConstants";
|
|
import {
|
|
ReduxActionErrorTypes,
|
|
ReduxActionTypes,
|
|
} from "ee/constants/ReduxActionConstants";
|
|
import type { Plugin } from "api/PluginApi";
|
|
import {
|
|
ActionCreationSourceTypeEnum,
|
|
ActionExecutionContext,
|
|
PluginType,
|
|
type Action,
|
|
type QueryActionConfig,
|
|
} from "entities/Action";
|
|
import type { Datasource } from "entities/Datasource";
|
|
import { invert, merge, omit, partition } from "lodash";
|
|
import { all, call, put, select, take, takeLatest } from "redux-saga/effects";
|
|
import {
|
|
getCurrentApplicationId,
|
|
getCurrentPageId,
|
|
} from "selectors/editorSelectors";
|
|
import {
|
|
getActions,
|
|
getCurrentPageNameByActionId,
|
|
getDatasource,
|
|
getPlugin,
|
|
} from "ee/selectors/entitiesSelector";
|
|
import { createNewApiName, createNewQueryName } from "utils/AppsmithUtils";
|
|
import WidgetQueryGeneratorRegistry from "utils/WidgetQueryGeneratorRegistry";
|
|
import {
|
|
createDefaultActionPayloadWithPluginDefaults,
|
|
getPluginActionDefaultValues,
|
|
} from "./ActionSagas";
|
|
import "../WidgetQueryGenerators";
|
|
import type { ActionDataState } from "ee/reducers/entityReducers/actionsReducer";
|
|
import "WidgetQueryGenerators";
|
|
import { getWidgetByID } from "./selectors";
|
|
import type {
|
|
WidgetQueryConfig,
|
|
WidgetQueryGenerationFormConfig,
|
|
} from "WidgetQueryGenerators/types";
|
|
import { QUERY_TYPE } from "WidgetQueryGenerators/types";
|
|
import type { WidgetProps } from "widgets/BaseWidget";
|
|
import type { ApiResponse } from "api/ApiResponses";
|
|
import type { ActionCreateUpdateResponse } from "api/ActionAPI";
|
|
import ActionAPI from "api/ActionAPI";
|
|
import { validateResponse } from "./ErrorSagas";
|
|
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
|
|
import AppsmithConsole from "utils/AppsmithConsole";
|
|
import { ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils";
|
|
import { fetchActions, runAction } from "actions/pluginActionActions";
|
|
import { toast } from "@appsmith/ads";
|
|
import WidgetFactory from "WidgetProvider/factory";
|
|
|
|
export function* createActionsForOneClickBindingSaga(
|
|
payload: Partial<Action> & { eventData: unknown; pluginId: string },
|
|
) {
|
|
try {
|
|
// Indicates that source of action creation is one click binding
|
|
payload.source = ActionCreationSourceTypeEnum.ONE_CLICK_BINDING;
|
|
|
|
const response: ApiResponse<ActionCreateUpdateResponse> | undefined =
|
|
yield ActionAPI.createAction(payload);
|
|
|
|
if (!response) {
|
|
return false;
|
|
}
|
|
|
|
const isValidResponse: boolean = yield validateResponse(response);
|
|
|
|
if (isValidResponse) {
|
|
const pageName: string = yield select(
|
|
getCurrentPageNameByActionId,
|
|
response.data.id,
|
|
);
|
|
|
|
AnalyticsUtil.logEvent("CREATE_ACTION", {
|
|
id: response.data.id,
|
|
// @ts-expect-error: name does not exists on type ActionCreateUpdateResponse
|
|
actionName: response.data.name,
|
|
pageName: pageName,
|
|
...payload.eventData,
|
|
});
|
|
|
|
AppsmithConsole.info({
|
|
text: `Action created from one click binding`,
|
|
source: {
|
|
type: ENTITY_TYPE.ACTION,
|
|
id: response.data.id,
|
|
// @ts-expect-error: name does not exists on type ActionCreateUpdateResponse
|
|
name: response.data.name,
|
|
},
|
|
});
|
|
|
|
return response.data;
|
|
}
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function* BindWidgetToDatasource(
|
|
action: ReduxAction<WidgetQueryGenerationFormConfig>,
|
|
) {
|
|
const { datasourceId, widgetId } = action.payload;
|
|
|
|
const pageId: string = yield select(getCurrentPageId);
|
|
|
|
const actions: ActionDataState = yield select(getActions);
|
|
|
|
const datasource: Datasource = yield select(getDatasource, datasourceId);
|
|
|
|
const plugin: Plugin = yield select(getPlugin, datasource?.pluginId);
|
|
|
|
const widget: WidgetProps = yield select(getWidgetByID(widgetId));
|
|
|
|
const applicationId: string = yield select(getCurrentApplicationId);
|
|
|
|
const newActions: string[] = [];
|
|
|
|
try {
|
|
const defaultValues: object | undefined = yield call(
|
|
getPluginActionDefaultValues,
|
|
datasource?.pluginId,
|
|
);
|
|
|
|
const { getQueryGenerationConfig } = WidgetFactory.getWidgetMethods(
|
|
widget.type,
|
|
);
|
|
|
|
const widgetQueryGenerationConfig = getQueryGenerationConfig?.(
|
|
widget,
|
|
action.payload,
|
|
);
|
|
|
|
const widgetQueryGenerator = WidgetQueryGeneratorRegistry.get(
|
|
plugin.packageName,
|
|
);
|
|
|
|
const actionConfigurationList = widgetQueryGenerator.build(
|
|
widgetQueryGenerationConfig,
|
|
action.payload,
|
|
defaultValues,
|
|
);
|
|
|
|
const newActionName =
|
|
plugin.type === PluginType.DB
|
|
? createNewQueryName(actions, pageId || "")
|
|
: createNewApiName(actions, pageId || "");
|
|
|
|
const commonActionPayload: Partial<Action> = yield call(
|
|
createDefaultActionPayloadWithPluginDefaults,
|
|
{
|
|
datasourceId,
|
|
from: "ONE_CLICK_BINDING",
|
|
newActionName,
|
|
},
|
|
);
|
|
|
|
const queryNameMap: Record<string, string> = {};
|
|
|
|
const actionRequestPayloadList: Partial<Action> &
|
|
{ eventData: unknown; pluginId: string; type: QUERY_TYPE }[] =
|
|
actionConfigurationList.map(
|
|
(action: {
|
|
payload: QueryActionConfig;
|
|
dynamicBindingPathList: unknown;
|
|
name: string;
|
|
type: QUERY_TYPE;
|
|
}) => {
|
|
const { dynamicBindingPathList, name, payload, type } = action;
|
|
|
|
queryNameMap[type] = createNewQueryName(actions, pageId || "", name);
|
|
|
|
return merge(
|
|
{},
|
|
{
|
|
...commonActionPayload,
|
|
pageId,
|
|
},
|
|
{
|
|
actionConfiguration: payload,
|
|
name: queryNameMap[type],
|
|
dynamicBindingPathList,
|
|
type,
|
|
},
|
|
);
|
|
},
|
|
);
|
|
|
|
/*
|
|
* Select query is created and bound first so table widget can
|
|
* create columns
|
|
*/
|
|
const groupedPayloadList = partition(actionRequestPayloadList, (d) =>
|
|
[QUERY_TYPE.SELECT, QUERY_TYPE.TOTAL_RECORD].includes(d.type),
|
|
);
|
|
|
|
for (const payloadList of groupedPayloadList) {
|
|
const createdActions: Action[] = yield all(
|
|
payloadList.map((payload) =>
|
|
call(createActionsForOneClickBindingSaga, omit(payload, "type")),
|
|
),
|
|
);
|
|
|
|
if (createdActions.some((action) => !action)) {
|
|
throw new Error("Unable to create Actions");
|
|
}
|
|
|
|
yield put(fetchActions({ applicationId }, []));
|
|
|
|
const fetchAction: ReduxAction<unknown> = yield take([
|
|
ReduxActionTypes.FETCH_ACTIONS_SUCCESS,
|
|
ReduxActionErrorTypes.FETCH_ACTIONS_ERROR,
|
|
]);
|
|
|
|
if (fetchAction.type === ReduxActionErrorTypes.FETCH_ACTIONS_ERROR) {
|
|
throw new Error("Unable to fetch newly created actions");
|
|
}
|
|
|
|
const actionsToRun = createdActions.filter(
|
|
(action) =>
|
|
action.name === queryNameMap[QUERY_TYPE.SELECT] ||
|
|
action.name === queryNameMap[QUERY_TYPE.TOTAL_RECORD],
|
|
);
|
|
|
|
//TODO(Balaji): Need to make changes to plugin saga to execute the actions in parallel
|
|
for (const actionToRun of actionsToRun) {
|
|
yield put(
|
|
runAction(
|
|
actionToRun.id,
|
|
undefined,
|
|
true,
|
|
undefined,
|
|
ActionExecutionContext.ONE_CLICK_BINDING,
|
|
),
|
|
);
|
|
|
|
const runResponse: ReduxAction<unknown> = yield take([
|
|
ReduxActionTypes.RUN_ACTION_SUCCESS,
|
|
ReduxActionErrorTypes.EXECUTE_PLUGIN_ACTION_ERROR,
|
|
ReduxActionErrorTypes.RUN_ACTION_ERROR,
|
|
]);
|
|
|
|
if (
|
|
[
|
|
ReduxActionErrorTypes.EXECUTE_PLUGIN_ACTION_ERROR,
|
|
ReduxActionErrorTypes.RUN_ACTION_ERROR,
|
|
].includes(runResponse.type)
|
|
) {
|
|
throw new Error(`Unable to run action: ${actionToRun.name}`);
|
|
}
|
|
}
|
|
|
|
const { getPropertyUpdatesForQueryBinding } =
|
|
WidgetFactory.getWidgetMethods(widget.type);
|
|
|
|
const createdQueryNames = createdActions.map((d) => d.name);
|
|
|
|
const queryBindingConfig: WidgetQueryConfig = {};
|
|
|
|
const { alertMessage } = action.payload;
|
|
|
|
if (createdQueryNames.includes(queryNameMap[QUERY_TYPE.SELECT])) {
|
|
queryBindingConfig[QUERY_TYPE.SELECT] = {
|
|
data: `{{${queryNameMap[QUERY_TYPE.SELECT]}.data}}`,
|
|
run: `{{
|
|
${queryNameMap[QUERY_TYPE.SELECT]}.run();
|
|
${
|
|
createdQueryNames.includes(queryNameMap[QUERY_TYPE.TOTAL_RECORD])
|
|
? queryNameMap[QUERY_TYPE.TOTAL_RECORD] + ".run()"
|
|
: ""
|
|
}
|
|
}}`,
|
|
};
|
|
}
|
|
|
|
if (createdQueryNames.includes(queryNameMap[QUERY_TYPE.UPDATE])) {
|
|
const selectQuery = queryNameMap[QUERY_TYPE.SELECT]
|
|
? `${queryNameMap[QUERY_TYPE.SELECT]}.run()`
|
|
: "";
|
|
|
|
const successMessage = `${
|
|
alertMessage?.success
|
|
? alertMessage.success?.update
|
|
: "Successfully saved!"
|
|
}`;
|
|
|
|
queryBindingConfig[QUERY_TYPE.UPDATE] = {
|
|
data: `{{${queryNameMap[QUERY_TYPE.UPDATE]}.data}}`,
|
|
run: `{{${queryNameMap[QUERY_TYPE.UPDATE]}.run(() => {
|
|
showAlert("${successMessage}");
|
|
${selectQuery.toString()}
|
|
}, () => {
|
|
showAlert("Unable to save!");
|
|
})}}`,
|
|
};
|
|
}
|
|
|
|
if (createdQueryNames.includes(queryNameMap[QUERY_TYPE.CREATE])) {
|
|
const selectQuery = queryNameMap[QUERY_TYPE.SELECT]
|
|
? `${queryNameMap[QUERY_TYPE.SELECT]}.run()`
|
|
: "";
|
|
|
|
const successMessage = `${
|
|
alertMessage?.success
|
|
? alertMessage.success?.create
|
|
: "Successfully created!"
|
|
}`;
|
|
|
|
queryBindingConfig[QUERY_TYPE.CREATE] = {
|
|
data: `{{${queryNameMap[QUERY_TYPE.CREATE]}.data}}`,
|
|
run: `{{${queryNameMap[QUERY_TYPE.CREATE]}.run(() => {
|
|
showAlert("${successMessage}");
|
|
${selectQuery.toString()}
|
|
}, () => {
|
|
showAlert("Unable to create!");
|
|
})}}`,
|
|
};
|
|
}
|
|
|
|
if (createdQueryNames.includes(queryNameMap[QUERY_TYPE.TOTAL_RECORD])) {
|
|
queryBindingConfig[QUERY_TYPE.TOTAL_RECORD] = {
|
|
data: `{{${widgetQueryGenerator.getTotalRecordExpression(
|
|
`${queryNameMap[QUERY_TYPE.TOTAL_RECORD]}.data`,
|
|
)}}}`,
|
|
run: `{{${queryNameMap[QUERY_TYPE.TOTAL_RECORD]}.run()}}`,
|
|
};
|
|
}
|
|
|
|
const updatedWidget: WidgetProps = yield select(getWidgetByID(widgetId));
|
|
|
|
const { dynamicUpdates, modify } =
|
|
getPropertyUpdatesForQueryBinding?.(
|
|
queryBindingConfig,
|
|
updatedWidget,
|
|
action.payload,
|
|
) || {};
|
|
|
|
yield put({
|
|
type: ReduxActionTypes.BATCH_UPDATE_WIDGET_PROPERTY,
|
|
payload: {
|
|
widgetId,
|
|
updates: {
|
|
modify,
|
|
},
|
|
dynamicUpdates,
|
|
},
|
|
});
|
|
|
|
yield take(ReduxActionTypes.SET_EVALUATED_TREE);
|
|
|
|
newActions.push(...createdQueryNames);
|
|
|
|
for (const action of createdActions) {
|
|
AnalyticsUtil.logEvent("QUERY_GENERATION_BINDING_SUCCESS", {
|
|
widgetName: widget.widgetName,
|
|
widgetType: widget.type,
|
|
QueryName: action.name,
|
|
QueryType: invert(queryNameMap)[action.name],
|
|
pluginType: plugin.type,
|
|
pluginName: plugin.name,
|
|
});
|
|
}
|
|
}
|
|
|
|
yield put({
|
|
type: ReduxActionTypes.BIND_WIDGET_TO_DATASOURCE_SUCCESS,
|
|
});
|
|
const { otherFields } = action.payload;
|
|
|
|
AnalyticsUtil.logEvent("1_CLICK_BINDING_SUCCESS", {
|
|
widgetName: widget.widgetName,
|
|
widgetType: widget.type,
|
|
pluginType: plugin.type,
|
|
pluginName: plugin.name,
|
|
isMock: datasource.isMock,
|
|
formType: otherFields?.formType,
|
|
});
|
|
} catch (e: unknown) {
|
|
yield put({
|
|
type: ReduxActionTypes.BIND_WIDGET_TO_DATASOURCE_ERROR,
|
|
payload: {
|
|
show: true,
|
|
error: {
|
|
message: e instanceof Error ? e.message : "Failed to Bind to widget",
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
toast.show(
|
|
`Successfully created action${
|
|
newActions.length > 1 ? "s" : ""
|
|
}: ${newActions.join(", ")}`,
|
|
{
|
|
hideProgressBar: true,
|
|
kind: "success",
|
|
autoClose: 3000,
|
|
},
|
|
);
|
|
}
|
|
|
|
export default function* oneClickBindingSaga() {
|
|
yield all([
|
|
takeLatest(
|
|
ReduxActionTypes.BIND_WIDGET_TO_DATASOURCE,
|
|
BindWidgetToDatasource,
|
|
),
|
|
]);
|
|
}
|