2023-04-13 11:09:24 +00:00
|
|
|
import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants";
|
2023-06-01 17:26:05 +00:00
|
|
|
import { ReduxActionErrorTypes } from "@appsmith/constants/ReduxActionConstants";
|
|
|
|
|
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
|
|
|
|
|
import type { Plugin } from "api/PluginApi";
|
|
|
|
|
import type { Action, QueryActionConfig } from "entities/Action";
|
|
|
|
|
import type { Datasource } from "entities/Datasource";
|
|
|
|
|
import { invert, merge, omit, partition } from "lodash";
|
|
|
|
|
import { all, call, put, select, takeLatest, take } from "redux-saga/effects";
|
|
|
|
|
import {
|
|
|
|
|
getCurrentApplicationId,
|
|
|
|
|
getCurrentPageId,
|
|
|
|
|
} from "selectors/editorSelectors";
|
|
|
|
|
import {
|
|
|
|
|
getActions,
|
|
|
|
|
getCurrentPageNameByActionId,
|
|
|
|
|
getDatasource,
|
|
|
|
|
getPlugin,
|
2023-09-12 11:51:39 +00:00
|
|
|
} from "@appsmith/selectors/entitiesSelector";
|
2023-06-01 17:26:05 +00:00
|
|
|
import { createNewQueryName } from "utils/AppsmithUtils";
|
|
|
|
|
import WidgetQueryGeneratorRegistry from "utils/WidgetQueryGeneratorRegistry";
|
|
|
|
|
import {
|
|
|
|
|
createDefaultActionPayload,
|
|
|
|
|
getPulginActionDefaultValues,
|
|
|
|
|
} from "./ActionSagas";
|
|
|
|
|
import "../WidgetQueryGenerators";
|
|
|
|
|
import type { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
|
|
|
|
import "WidgetQueryGenerators";
|
|
|
|
|
import { getWidgetByID } from "./selectors";
|
2023-09-06 12:15:04 +00:00
|
|
|
import type {
|
|
|
|
|
WidgetQueryConfig,
|
|
|
|
|
WidgetQueryGenerationFormConfig,
|
|
|
|
|
} from "WidgetQueryGenerators/types";
|
2023-06-01 17:26:05 +00:00
|
|
|
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 "utils/AnalyticsUtil";
|
|
|
|
|
import AppsmithConsole from "utils/AppsmithConsole";
|
|
|
|
|
import { ENTITY_TYPE } from "entities/AppsmithConsole";
|
|
|
|
|
import { fetchActions, runAction } from "actions/pluginActionActions";
|
|
|
|
|
import { Toaster, Variant } from "design-system-old";
|
2023-09-06 12:15:04 +00:00
|
|
|
import WidgetFactory from "WidgetProvider/factory";
|
2023-06-01 17:26:05 +00:00
|
|
|
|
|
|
|
|
export function* createActionsForOneClickBindingSaga(
|
|
|
|
|
payload: Partial<Action> & { eventData: unknown; pluginId: string },
|
|
|
|
|
) {
|
|
|
|
|
try {
|
|
|
|
|
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(
|
|
|
|
|
getPulginActionDefaultValues,
|
|
|
|
|
datasource?.pluginId,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const { getQueryGenerationConfig } = WidgetFactory.getWidgetMethods(
|
|
|
|
|
widget.type,
|
|
|
|
|
);
|
|
|
|
|
|
2023-09-06 12:15:04 +00:00
|
|
|
const widgetQueryGenerationConfig = getQueryGenerationConfig?.(widget);
|
2023-06-01 17:26:05 +00:00
|
|
|
|
|
|
|
|
const widgetQueryGenerator = WidgetQueryGeneratorRegistry.get(
|
|
|
|
|
plugin.packageName,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const actionConfigurationList = widgetQueryGenerator.build(
|
|
|
|
|
widgetQueryGenerationConfig,
|
|
|
|
|
action.payload,
|
|
|
|
|
defaultValues,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const commonActionPayload: Partial<Action> = yield call(
|
|
|
|
|
createDefaultActionPayload,
|
|
|
|
|
pageId,
|
|
|
|
|
datasourceId,
|
|
|
|
|
"ONE_CLICK_BINDING",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
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, {
|
|
|
|
|
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
|
2023-06-08 11:28:48 +00:00
|
|
|
for (const actionToRun of actionsToRun) {
|
|
|
|
|
yield put(runAction(actionToRun.id, undefined, true));
|
2023-06-01 17:26:05 +00:00
|
|
|
|
|
|
|
|
const runResponse: ReduxAction<unknown> = yield take([
|
2023-06-08 11:28:48 +00:00
|
|
|
ReduxActionTypes.RUN_ACTION_SUCCESS,
|
2023-06-01 17:26:05 +00:00
|
|
|
ReduxActionErrorTypes.EXECUTE_PLUGIN_ACTION_ERROR,
|
2023-08-17 07:08:06 +00:00
|
|
|
ReduxActionErrorTypes.RUN_ACTION_ERROR,
|
2023-06-01 17:26:05 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (
|
2023-08-17 07:08:06 +00:00
|
|
|
[
|
|
|
|
|
ReduxActionErrorTypes.EXECUTE_PLUGIN_ACTION_ERROR,
|
|
|
|
|
ReduxActionErrorTypes.RUN_ACTION_ERROR,
|
|
|
|
|
].includes(runResponse.type)
|
2023-06-01 17:26:05 +00:00
|
|
|
) {
|
2023-06-08 11:28:48 +00:00
|
|
|
throw new Error(`Unable to run action: ${actionToRun.name}`);
|
2023-06-01 17:26:05 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { getPropertyUpdatesForQueryBinding } =
|
|
|
|
|
WidgetFactory.getWidgetMethods(widget.type);
|
|
|
|
|
|
|
|
|
|
const createdQueryNames = createdActions.map((d) => d.name);
|
|
|
|
|
|
2023-09-06 12:15:04 +00:00
|
|
|
const queryBindingConfig: WidgetQueryConfig = {};
|
2023-06-01 17:26:05 +00:00
|
|
|
|
|
|
|
|
if (createdQueryNames.includes(queryNameMap[QUERY_TYPE.SELECT])) {
|
|
|
|
|
queryBindingConfig[QUERY_TYPE.SELECT] = {
|
|
|
|
|
data: `{{${queryNameMap[QUERY_TYPE.SELECT]}.data}}`,
|
2023-06-08 11:28:48 +00:00
|
|
|
run: `{{
|
|
|
|
|
${queryNameMap[QUERY_TYPE.SELECT]}.run();
|
2023-08-10 05:21:19 +00:00
|
|
|
${
|
|
|
|
|
createdQueryNames.includes(queryNameMap[QUERY_TYPE.TOTAL_RECORD])
|
|
|
|
|
? queryNameMap[QUERY_TYPE.TOTAL_RECORD] + ".run()"
|
|
|
|
|
: ""
|
|
|
|
|
}
|
2023-06-08 11:28:48 +00:00
|
|
|
}}`,
|
2023-06-01 17:26:05 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (createdQueryNames.includes(queryNameMap[QUERY_TYPE.UPDATE])) {
|
|
|
|
|
queryBindingConfig[QUERY_TYPE.UPDATE] = {
|
|
|
|
|
data: `{{${queryNameMap[QUERY_TYPE.UPDATE]}.data}}`,
|
|
|
|
|
run: `{{${queryNameMap[QUERY_TYPE.UPDATE]}.run(() => {
|
|
|
|
|
showAlert("Successfully saved!");
|
|
|
|
|
${queryNameMap[QUERY_TYPE.SELECT]}.run();
|
|
|
|
|
}, () => {
|
|
|
|
|
showAlert("Unable to save!");
|
|
|
|
|
})}}`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (createdQueryNames.includes(queryNameMap[QUERY_TYPE.CREATE])) {
|
|
|
|
|
queryBindingConfig[QUERY_TYPE.CREATE] = {
|
|
|
|
|
data: `{{${queryNameMap[QUERY_TYPE.CREATE]}.data}}`,
|
|
|
|
|
run: `{{${queryNameMap[QUERY_TYPE.CREATE]}.run(() => {
|
|
|
|
|
showAlert("Successfully created!");
|
|
|
|
|
${queryNameMap[QUERY_TYPE.SELECT]}.run()
|
|
|
|
|
}, () => {
|
|
|
|
|
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));
|
|
|
|
|
|
2023-09-06 12:15:04 +00:00
|
|
|
const { dynamicUpdates, modify } =
|
|
|
|
|
getPropertyUpdatesForQueryBinding?.(
|
|
|
|
|
queryBindingConfig,
|
|
|
|
|
updatedWidget,
|
|
|
|
|
action.payload,
|
|
|
|
|
) || {};
|
2023-06-01 17:26:05 +00:00
|
|
|
|
|
|
|
|
yield put({
|
|
|
|
|
type: ReduxActionTypes.BATCH_UPDATE_WIDGET_PROPERTY,
|
|
|
|
|
payload: {
|
|
|
|
|
widgetId,
|
|
|
|
|
updates: {
|
2023-07-20 06:22:20 +00:00
|
|
|
modify,
|
2023-06-01 17:26:05 +00:00
|
|
|
},
|
2023-07-20 06:22:20 +00:00
|
|
|
dynamicUpdates,
|
2023-06-01 17:26:05 +00:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
AnalyticsUtil.logEvent("1_CLICK_BINDING_SUCCESS", {
|
|
|
|
|
widgetName: widget.widgetName,
|
|
|
|
|
widgetType: widget.type,
|
|
|
|
|
pluginType: plugin.type,
|
|
|
|
|
pluginName: plugin.name,
|
|
|
|
|
isMock: datasource.isMock,
|
|
|
|
|
});
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
Toaster.show({
|
|
|
|
|
text: e.message,
|
|
|
|
|
hideProgressBar: false,
|
|
|
|
|
variant: Variant.danger,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
yield put({
|
|
|
|
|
type: ReduxActionTypes.BIND_WIDGET_TO_DATASOURCE_ERROR,
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-04-13 11:09:24 +00:00
|
|
|
|
2023-06-01 17:26:05 +00:00
|
|
|
Toaster.show({
|
|
|
|
|
text: `Successfully created action${
|
|
|
|
|
newActions.length > 1 ? "s" : ""
|
|
|
|
|
}: ${newActions.join(", ")}`,
|
|
|
|
|
hideProgressBar: true,
|
|
|
|
|
variant: Variant.success,
|
|
|
|
|
duration: 3000,
|
|
|
|
|
});
|
2023-04-13 11:09:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function* oneClickBindingSaga() {
|
|
|
|
|
yield all([
|
|
|
|
|
takeLatest(
|
|
|
|
|
ReduxActionTypes.BIND_WIDGET_TO_DATASOURCE,
|
|
|
|
|
BindWidgetToDatasource,
|
|
|
|
|
),
|
|
|
|
|
]);
|
|
|
|
|
}
|