PromucFlow_constructor/app/client/src/sagas/JSPaneSagas.ts
Pranav Kanade b778b83ac4
refactor: admin settings (#9906)
* refactor admin settings feature

* separated save-restart bar to separate component

* created new CE dir to facilitate code split

* created separate ee dir and exporting everything we have in ce file.

* little mod

* minor fix

* splitting settings types config

* using object literals for category types instead of enums

* CE: support use of component for each category

* minor style fix

* authentication page  UI changes implemented

* github signup doc url added back

* removed comments

* routing updates

* made subcategories listing in left pane optional

* added muted saml to auth listing

* added breadcrumbs and enabled button

* created separate component for auth page and auth config

* added callout and disconnect components

* updated breadcrumbs component

* minor updates to common components

* updated warning callout and added icon

* ce: test cases fixed

* updated test file name

* warning banner callout added on auth page

* updated callout banner for form login

* CE: Split config files

* CE: moved the window declaration in EE file as its dependency will be updated in EE

* CE: Splitting ApiConstants and SocialLogin constants

* CE: split login page

* CE: moved getSocialLoginButtonProps func to EE file as it's dependencies will be updated in EE

* added key icon

* CE: created a factory class to share social auths list

* Minor style fix for social btns

* Updated the third party auth styles

* Small fixes to styling

* ce: splitting forms constants

* breadcrumbs implemented for all pages in admin settings

* Settings breadcrumbs separated

* splitted settings breadcrumbs between ce and ee

* renamed default import

* minor style fix

* added login form config.

* updated login/signup pages to use form login disabled config

* removed common functionality outside

* implemented breadcrumb component from scratch without using blueprint

* removed unwanted code

* Small style update

* updated breadcrumb categories file name and breadcrumb icon

* added cypress tests for admin settings auth page

* added comments

* update locator for upgrade button

* added link for intercom on upgrade button

* removed unnecessary file

* minor style fix

* style fix for auth option cards

* split messages constant

* fixed imports for message constants splitting.

* added message constants

* updated unit test cases

* fixed messages import in cypress index

* fixed messages import again, cypress fails to read re-exported objs.

* added OIDC auth method on authentication page

* updated import statements from ee to @appsmith

* removed dead code

* updated read more link UI

* PR comments fixes

* some UI fixes

* used color and fonts from theme

* fixed some imports

* fixed some imports

* removed warning imports

* updated OIDC logo and auth method desc copies

* css changes

* css changes

* css changes

* updated cypress test for breadcrumb

* moved callout component to ads as calloutv2

* UI changes for form fields

* updated css for spacing between form fields

* added sub-text on auth pages

* added active class for breadcrumb item

* added config for disable signup toggle and fixed UI issues of restart banner

* fixed admin settings page bugs

* assigned true as default state for signup

* fixed messages import statements

* updated code for PR comments related suggestions

* reverted file path change in cypress support

* updated cypress test

* updated cypress test

Co-authored-by: Ankita Kinger <ankita@appsmith.com>
2022-02-11 23:38:46 +05:30

450 lines
14 KiB
TypeScript

import {
all,
select,
put,
takeEvery,
debounce,
call,
take,
} from "redux-saga/effects";
import {
ReduxAction,
ReduxActionTypes,
ReduxActionErrorTypes,
} from "constants/ReduxActionConstants";
import {
getCurrentApplicationId,
getCurrentPageId,
} from "selectors/editorSelectors";
import { getJSCollection, getJSCollections } from "selectors/entitiesSelector";
import { JSCollectionData } from "reducers/entityReducers/jsActionsReducer";
import { createNewJSFunctionName, getQueryParams } from "utils/AppsmithUtils";
import { JSCollection, JSAction } from "entities/JSCollection";
import { createJSCollectionRequest } from "actions/jsActionActions";
import { JS_COLLECTION_ID_URL } from "constants/routes";
import history from "utils/history";
import { executeFunction } from "./EvaluationsSaga";
import { getJSCollectionIdFromURL } from "pages/Editor/Explorer/helpers";
import {
getDifferenceInJSCollection,
JSUpdate,
pushLogsForObjectUpdate,
createDummyJSCollectionActions,
} from "../utils/JSPaneUtils";
import JSActionAPI, { RefactorAction } from "../api/JSActionAPI";
import {
updateJSCollectionSuccess,
refactorJSCollectionAction,
updateJSCollectionBodySuccess,
} from "actions/jsPaneActions";
import { getCurrentOrgId } from "selectors/organizationSelectors";
import { getPluginIdOfPackageName } from "sagas/selectors";
import { PluginType } from "entities/Action";
import { Toaster } from "components/ads/Toast";
import { Variant } from "components/ads/common";
import {
createMessage,
ERROR_JS_COLLECTION_RENAME_FAIL,
JS_EXECUTION_SUCCESS,
JS_EXECUTION_FAILURE,
JS_EXECUTION_FAILURE_TOASTER,
JS_FUNCTION_CREATE_SUCCESS,
JS_FUNCTION_DELETE_SUCCESS,
JS_FUNCTION_UPDATE_SUCCESS,
} from "@appsmith/constants/messages";
import { validateResponse } from "./ErrorSagas";
import AppsmithConsole from "utils/AppsmithConsole";
import { ENTITY_TYPE, PLATFORM_ERROR } from "entities/AppsmithConsole";
import LOG_TYPE from "entities/AppsmithConsole/logtype";
import PageApi from "api/PageApi";
import { updateCanvasWithDSL } from "sagas/PageSagas";
export const JS_PLUGIN_PACKAGE_NAME = "js-plugin";
import { updateReplayEntity } from "actions/pageActions";
function* handleCreateNewJsActionSaga(action: ReduxAction<{ pageId: string }>) {
const organizationId: string = yield select(getCurrentOrgId);
const applicationId = yield select(getCurrentApplicationId);
const { pageId } = action.payload;
const pluginId: string = yield select(
getPluginIdOfPackageName,
JS_PLUGIN_PACKAGE_NAME,
);
if (pageId && pluginId) {
const jsActions = yield select(getJSCollections);
const pageJSActions = jsActions.filter(
(a: JSCollectionData) => a.config.pageId === pageId,
);
const newJSCollectionName = createNewJSFunctionName(pageJSActions, pageId);
const { actions, body } = createDummyJSCollectionActions(
pageId,
organizationId,
);
yield put(
createJSCollectionRequest({
name: newJSCollectionName,
pageId,
organizationId,
pluginId,
body: body,
variables: [
{
name: "myVar1",
value: [],
},
{
name: "myVar2",
value: {},
},
],
actions: actions,
applicationId,
pluginType: PluginType.JS,
}),
);
}
}
function* handleJSCollectionCreatedSaga(
actionPayload: ReduxAction<JSCollection>,
) {
const { id } = actionPayload.payload;
const applicationId = yield select(getCurrentApplicationId);
const pageId = yield select(getCurrentPageId);
history.push(JS_COLLECTION_ID_URL(applicationId, pageId, id, {}));
}
function* handleEachUpdateJSCollection(update: JSUpdate) {
const jsActionId = update.id;
const organizationId: string = yield select(getCurrentOrgId);
if (jsActionId) {
const jsAction: JSCollection = yield select(getJSCollection, jsActionId);
const parsedBody = update.parsedBody;
const jsActionTobeUpdated = JSON.parse(JSON.stringify(jsAction));
if (parsedBody) {
// jsActionTobeUpdated.body = jsAction.body;
const data = getDifferenceInJSCollection(parsedBody, jsAction);
if (data.nameChangedActions.length) {
for (let i = 0; i < data.nameChangedActions.length; i++) {
yield put(
refactorJSCollectionAction({
refactorAction: {
actionId: data.nameChangedActions[i].id,
collectionName: jsAction.name,
pageId: data.nameChangedActions[i].pageId,
oldName: data.nameChangedActions[i].oldName,
newName: data.nameChangedActions[i].newName,
},
actionCollection: jsActionTobeUpdated,
}),
);
}
} else {
let newActions: Partial<JSAction>[] = [];
let updateActions: JSAction[] = [];
let deletedActions: JSAction[] = [];
let updateCollection = false;
const changedVariables = data.changedVariables;
if (changedVariables.length) {
jsActionTobeUpdated.variables = parsedBody.variables;
updateCollection = true;
}
if (data.newActions.length) {
newActions = data.newActions;
for (let i = 0; i < data.newActions.length; i++) {
jsActionTobeUpdated.actions.push({
...data.newActions[i],
organizationId: organizationId,
});
}
updateCollection = true;
}
if (data.updateActions.length > 0) {
updateActions = data.updateActions;
let changedActions = [];
for (let i = 0; i < data.updateActions.length; i++) {
changedActions = jsActionTobeUpdated.actions.map(
(js: JSAction) =>
data.updateActions.find(
(update: JSAction) => update.id === js.id,
) || js,
);
}
updateCollection = true;
jsActionTobeUpdated.actions = changedActions;
}
if (data.deletedActions.length > 0) {
deletedActions = data.deletedActions;
const nonDeletedActions = jsActionTobeUpdated.actions.filter(
(js: JSAction) => {
return !data.deletedActions.find((deleted) => {
return deleted.id === js.id;
});
},
);
updateCollection = true;
jsActionTobeUpdated.actions = nonDeletedActions;
}
if (updateCollection) {
yield call(updateJSCollection, {
jsCollection: jsActionTobeUpdated,
newActions: newActions,
updatedActions: updateActions,
deletedActions: deletedActions,
});
}
}
}
}
}
export function* makeUpdateJSCollection(jsUpdates: Record<string, JSUpdate>) {
yield all(
Object.keys(jsUpdates).map((key) =>
call(handleEachUpdateJSCollection, jsUpdates[key]),
),
);
}
function* updateJSCollection(data: {
jsCollection: JSCollection;
newActions?: Partial<JSAction>[];
updatedActions?: JSAction[];
deletedActions?: JSAction[];
}) {
let jsAction = {};
const jsActionId = getJSCollectionIdFromURL();
if (jsActionId) {
jsAction = yield select(getJSCollection, jsActionId);
}
try {
const { deletedActions, jsCollection, newActions, updatedActions } = data;
if (jsCollection) {
const response = yield JSActionAPI.updateJSCollection(jsCollection);
const isValidResponse = yield validateResponse(response);
if (isValidResponse) {
if (newActions && newActions.length) {
pushLogsForObjectUpdate(
newActions,
jsCollection,
createMessage(JS_FUNCTION_CREATE_SUCCESS),
);
}
if (updatedActions && updatedActions.length) {
pushLogsForObjectUpdate(
updatedActions,
jsCollection,
createMessage(JS_FUNCTION_UPDATE_SUCCESS),
);
}
if (deletedActions && deletedActions.length) {
pushLogsForObjectUpdate(
deletedActions,
jsCollection,
createMessage(JS_FUNCTION_DELETE_SUCCESS),
);
}
yield put(updateJSCollectionSuccess({ data: response?.data }));
}
}
} catch (error) {
yield put({
type: ReduxActionErrorTypes.UPDATE_JS_ACTION_ERROR,
payload: { error, data: jsAction },
});
}
}
function* handleJSObjectNameChangeSuccessSaga(
action: ReduxAction<{ actionId: string }>,
) {
const { actionId } = action.payload;
const actionObj = yield select(getJSCollection, actionId);
yield take(ReduxActionTypes.FETCH_JS_ACTIONS_FOR_PAGE_SUCCESS);
if (!actionObj) {
// Error case, log to sentry
Toaster.show({
text: createMessage(ERROR_JS_COLLECTION_RENAME_FAIL, ""),
variant: Variant.danger,
});
return;
}
if (actionObj.pluginType === PluginType.API) {
const params = getQueryParams();
if (params.editName) {
params.editName = "false";
}
const applicationId = yield select(getCurrentApplicationId);
const pageId = yield select(getCurrentPageId);
history.push(JS_COLLECTION_ID_URL(applicationId, pageId, actionId, params));
}
}
function* handleExecuteJSFunctionSaga(
data: ReduxAction<{
collectionName: string;
action: JSAction;
collectionId: string;
}>,
): any {
const { action, collectionId, collectionName } = data.payload;
const actionId = action.id;
try {
const result = yield call(executeFunction, collectionName, action);
yield put({
type: ReduxActionTypes.EXECUTE_JS_FUNCTION_SUCCESS,
payload: {
results: result,
collectionId,
actionId,
},
});
AppsmithConsole.info({
text: createMessage(JS_EXECUTION_SUCCESS),
source: {
type: ENTITY_TYPE.JSACTION,
name: collectionName + "." + action.name,
id: collectionId,
},
state: { response: result },
});
} catch (e) {
AppsmithConsole.addError({
id: actionId,
logType: LOG_TYPE.ACTION_EXECUTION_ERROR,
text: createMessage(JS_EXECUTION_FAILURE),
source: {
type: ENTITY_TYPE.JSACTION,
name: collectionName + "." + action.name,
id: collectionId,
},
messages: [
{
message: e.message,
type: PLATFORM_ERROR.PLUGIN_EXECUTION,
},
],
});
Toaster.show({
text: e.message || createMessage(JS_EXECUTION_FAILURE_TOASTER),
variant: Variant.danger,
showDebugButton: true,
});
}
}
function* handleUpdateJSCollectionBody(
actionPayload: ReduxAction<{ body: string; id: string; isReplay: boolean }>,
) {
const jsCollection = yield select(getJSCollection, actionPayload.payload.id);
jsCollection.body = actionPayload.payload.body;
try {
if (jsCollection) {
const response = yield JSActionAPI.updateJSCollection(jsCollection);
const isValidResponse = yield validateResponse(response);
if (isValidResponse) {
yield put(updateJSCollectionBodySuccess({ data: response?.data }));
}
}
} catch (error) {
yield put({
type: ReduxActionErrorTypes.UPDATE_JS_ACTION_BODY_ERROR,
payload: { error, data: jsCollection },
});
}
if (!actionPayload.payload.isReplay)
yield put(
updateReplayEntity(
actionPayload.payload.id,
{
id: actionPayload.payload.id,
body: actionPayload.payload.body,
},
ENTITY_TYPE.JSACTION,
),
);
}
function* handleRefactorJSActionNameSaga(
data: ReduxAction<{
refactorAction: RefactorAction;
actionCollection: JSCollection;
}>,
) {
const pageResponse = yield call(PageApi.fetchPage, {
id: data.payload.refactorAction.pageId,
});
const isPageRequestSuccessful = yield validateResponse(pageResponse);
if (isPageRequestSuccessful) {
// get the layoutId from the page response
const layoutId = pageResponse.data.layouts[0].id;
const requestData = {
refactorAction: {
...data.payload.refactorAction,
layoutId: layoutId,
},
actionCollection: data.payload.actionCollection,
};
// call to refactor action
try {
const refactorResponse = yield JSActionAPI.updateJSCollectionActionRefactor(
requestData,
);
const isRefactorSuccessful = yield validateResponse(refactorResponse);
const currentPageId = yield select(getCurrentPageId);
if (isRefactorSuccessful) {
yield put({
type: ReduxActionTypes.REFACTOR_JS_ACTION_NAME_SUCCESS,
payload: { collectionId: data.payload.actionCollection.id },
});
if (currentPageId === data.payload.refactorAction.pageId) {
yield updateCanvasWithDSL(
refactorResponse.data,
data.payload.refactorAction.pageId,
layoutId,
);
}
}
} catch (error) {
yield put({
type: ReduxActionErrorTypes.REFACTOR_JS_ACTION_NAME_ERROR,
payload: { collectionId: data.payload.actionCollection.id },
});
}
}
}
export default function* root() {
yield all([
takeEvery(
ReduxActionTypes.CREATE_NEW_JS_ACTION,
handleCreateNewJsActionSaga,
),
takeEvery(
ReduxActionTypes.CREATE_JS_ACTION_SUCCESS,
handleJSCollectionCreatedSaga,
),
takeEvery(
ReduxActionTypes.SAVE_JS_COLLECTION_NAME_SUCCESS,
handleJSObjectNameChangeSuccessSaga,
),
takeEvery(
ReduxActionTypes.EXECUTE_JS_FUNCTION_INIT,
handleExecuteJSFunctionSaga,
),
takeEvery(
ReduxActionTypes.REFACTOR_JS_ACTION_NAME,
handleRefactorJSActionNameSaga,
),
debounce(
100,
ReduxActionTypes.UPDATE_JS_ACTION_BODY_INIT,
handleUpdateJSCollectionBody,
),
]);
}