diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ExplorerTests/Entity_Explorer_Datasource_Structure_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ExplorerTests/Entity_Explorer_Datasource_Structure_spec.js index 099f0f21ba..6704dcd698 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ExplorerTests/Entity_Explorer_Datasource_Structure_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/ExplorerTests/Entity_Explorer_Datasource_Structure_spec.js @@ -139,7 +139,11 @@ describe("Entity explorer datasource structure", function() { cy.get(".CodeMirror") .first() .then((editor) => { - editor[0].CodeMirror.setValue(`DROP TABLE ${tableName}`); + editor[0].CodeMirror.setValue(""); + cy.wrap(editor) + .find("textarea") + .focus() + .type(`DROP TABLE ${tableName};`); cy.WaitAutoSave(); cy.runQuery(); cy.get(queryEditor.queryMoreAction).click(); diff --git a/app/client/src/actions/editorActions.ts b/app/client/src/actions/editorActions.ts index 64fc2472ee..0d17266121 100644 --- a/app/client/src/actions/editorActions.ts +++ b/app/client/src/actions/editorActions.ts @@ -31,3 +31,12 @@ export const updateCanvasLayoutAction = ( }, }; }; + +/** + * This action when executed updates the status of saving entity to true + * This function was created to add a sync to the entity update and shortcut command being fired to execute any command. + */ + +export const startingEntityUpdation = () => ({ + type: ReduxActionTypes.ENTITY_UPDATE_STARTED, +}); diff --git a/app/client/src/components/editorComponents/CodeEditor/index.tsx b/app/client/src/components/editorComponents/CodeEditor/index.tsx index 0c31cc6ed8..9c7eea824a 100644 --- a/app/client/src/components/editorComponents/CodeEditor/index.tsx +++ b/app/client/src/components/editorComponents/CodeEditor/index.tsx @@ -82,9 +82,11 @@ import { AutocompleteDataType } from "utils/autocomplete/TernServer"; import { Placement } from "@blueprintjs/popover2"; import { getLintAnnotations } from "./lintHelpers"; import { executeCommandAction } from "actions/apiPaneActions"; +import { startingEntityUpdation } from "actions/editorActions"; import { SlashCommandPayload } from "entities/Action"; import { Indices } from "constants/Layers"; import { replayHighlightClass } from "globalStyles/portals"; + interface ReduxStateProps { dynamicData: DataTree; datasources: any; @@ -94,6 +96,7 @@ interface ReduxStateProps { interface ReduxDispatchProps { executeCommand: (payload: any) => void; + startingEntityUpdation: () => void; } export type CodeEditorExpected = { @@ -239,7 +242,7 @@ class CodeEditor extends Component { // editor.on("beforeChange", this.handleBeforeChange); - editor.on("change", _.debounce(this.handleChange, 600)); + editor.on("change", this.startChange); editor.on("keyup", this.handleAutocompleteKeyup); editor.on("focus", this.handleEditorFocus); editor.on("cursorActivity", this.handleCursorMovement); @@ -304,7 +307,7 @@ class CodeEditor extends Component { if (!this.editor) return; this.editor.off("beforeChange", this.handleBeforeChange); - this.editor.off("change", _.debounce(this.handleChange, 600)); + this.editor.off("change", this.startChange); this.editor.off("keyup", this.handleAutocompleteKeyup); this.editor.off("focus", this.handleEditorFocus); this.editor.off("cursorActivity", this.handleCursorMovement); @@ -397,6 +400,18 @@ class CodeEditor extends Component { CodeEditor.updateMarkings(this.editor, this.props.marking); }; + handleDebouncedChange = _.debounce(this.handleChange, 600); + + startChange = (instance?: any, changeObj?: any) => { + /* This action updates the status of the savingEntity to true so that any + shortcut commands do not execute before updating the entity in the store */ + const entity = this.getEntityInformation(); + if (entity.entityId) { + this.props.startingEntityUpdation(); + } + this.handleDebouncedChange(instance, changeObj); + }; + getEntityInformation = (): FieldEntityInformation => { const { dataTreePath, dynamicData, expected } = this.props; const entityInformation: FieldEntityInformation = { @@ -716,6 +731,7 @@ const mapStateToProps = (state: AppState): ReduxStateProps => ({ const mapDispatchToProps = (dispatch: any): ReduxDispatchProps => ({ executeCommand: (payload: SlashCommandPayload) => dispatch(executeCommandAction(payload)), + startingEntityUpdation: () => dispatch(startingEntityUpdation()), }); export default Sentry.withProfiler( diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index 532b2196fe..2cc1352e6c 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -646,6 +646,9 @@ export const ReduxActionTypes = { DELETE_ORG_SUCCESS: "DELETE_ORG_SUCCESS", SET_USER_CURRENT_GEO_LOCATION: "SET_USER_CURRENT_GEO_LOCATION", SET_DISCONNECTING_GIT_APPLICATION: "SET_DISCONNECTING_GIT_APPLICATION", + /* This action constants is for identifying the status of the updates of the entities */ + ENTITY_UPDATE_STARTED: "ENTITY_UPDATE_STARTED", + ENTITY_UPDATE_SUCCESS: "ENTITY_UPDATE_SUCCESS", }; export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes]; diff --git a/app/client/src/reducers/uiReducers/editorReducer.tsx b/app/client/src/reducers/uiReducers/editorReducer.tsx index 9aac98ecc2..ce03f17e95 100644 --- a/app/client/src/reducers/uiReducers/editorReducer.tsx +++ b/app/client/src/reducers/uiReducers/editorReducer.tsx @@ -15,6 +15,7 @@ const initialState: EditorReduxState = { publishingError: false, saving: false, savingError: false, + savingEntity: false, loading: false, loadingError: false, pageSwitchingError: false, @@ -193,6 +194,21 @@ const editorReducer = createReducer(initialState, { isPreviewMode: action.payload, }; }, + /* This action updates the status of the savingEntity for any entity of the application in the store */ + [ReduxActionTypes.ENTITY_UPDATE_STARTED]: (state: EditorReduxState) => ({ + ...state, + loadingStates: { + ...state.loadingStates, + savingEntity: true, + }, + }), + [ReduxActionTypes.ENTITY_UPDATE_SUCCESS]: (state: EditorReduxState) => ({ + ...state, + loadingStates: { + ...state.loadingStates, + savingEntity: false, + }, + }), }); export interface EditorReduxState { @@ -209,6 +225,7 @@ export interface EditorReduxState { loadingStates: { saving: boolean; savingError: boolean; + savingEntity: boolean; publishing: boolean; published?: string; publishingError: boolean; diff --git a/app/client/src/sagas/ActionExecution/PluginActionSaga.ts b/app/client/src/sagas/ActionExecution/PluginActionSaga.ts index 733314ca55..4143309302 100644 --- a/app/client/src/sagas/ActionExecution/PluginActionSaga.ts +++ b/app/client/src/sagas/ActionExecution/PluginActionSaga.ts @@ -60,6 +60,7 @@ import { } from "constants/AppsmithActionConstants/ActionConstants"; import { getCurrentPageId, + getIsSavingEntity, getLayoutOnLoadActions, } from "selectors/editorSelectors"; import PerformanceTracker, { @@ -407,7 +408,8 @@ function* runActionSaga( const actionId = reduxAction.payload.id; const isSaving = yield select(isActionSaving(actionId)); const isDirty = yield select(isActionDirty(actionId)); - if (isSaving || isDirty) { + const isSavingEntity = yield select(getIsSavingEntity); + if (isSaving || isDirty || isSavingEntity) { if (isDirty && !isSaving) { yield put(updateAction({ id: actionId })); } diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index 80f3d64232..944594820b 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -930,6 +930,17 @@ function* executeCommandSaga(actionPayload: ReduxAction) { } } +function* updateEntitySavingStatus() { + yield race([ + take(ReduxActionTypes.UPDATE_ACTION_SUCCESS), + take(ReduxActionTypes.SAVE_PAGE_SUCCESS), + ]); + + yield put({ + type: ReduxActionTypes.ENTITY_UPDATE_SUCCESS, + }); +} + export function* watchActionSagas() { yield all([ takeEvery(ReduxActionTypes.SET_ACTION_PROPERTY, setActionPropertySaga), @@ -958,5 +969,9 @@ export function* watchActionSagas() { toggleActionExecuteOnLoadSaga, ), takeLatest(ReduxActionTypes.EXECUTE_COMMAND, executeCommandSaga), + takeLatest( + ReduxActionTypes.ENTITY_UPDATE_STARTED, + updateEntitySavingStatus, + ), ]); } diff --git a/app/client/src/selectors/editorSelectors.tsx b/app/client/src/selectors/editorSelectors.tsx index d10ecb3520..fcabf62567 100644 --- a/app/client/src/selectors/editorSelectors.tsx +++ b/app/client/src/selectors/editorSelectors.tsx @@ -69,7 +69,10 @@ export const getIsPageSaving = (state: AppState) => { }); return ( - state.ui.editor.loadingStates.saving || areApisSaving || areJsObjectsSaving + state.ui.editor.loadingStates.saving || + areApisSaving || + areJsObjectsSaving || + state.ui.editor.loadingStates.savingEntity ); }; @@ -392,3 +395,12 @@ export const previewModeSelector = (state: AppState) => { export const getZoomLevel = (state: AppState) => { return state.ui.editor.zoomLevel; }; + +/** + * returns the `state.ui.editor.savingEntity` + * + * @param state AppState + * @returns boolean + */ +export const getIsSavingEntity = (state: AppState) => + state.ui.editor.loadingStates.savingEntity;