diff --git a/app/client/package.json b/app/client/package.json index 29563d3b19..3f09505ebf 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -46,7 +46,6 @@ "husky": "^3.0.5", "interweave": "^12.1.1", "interweave-autolink": "^4.0.1", - "jsonpath-plus": "^1.0.0", "lint-staged": "^9.2.5", "localforage": "^1.7.3", "lodash": "^4.17.11", diff --git a/app/client/src/actions/actionActions.ts b/app/client/src/actions/actionActions.ts index eeaa18324a..c7d2d87038 100644 --- a/app/client/src/actions/actionActions.ts +++ b/app/client/src/actions/actionActions.ts @@ -4,7 +4,6 @@ import { ReduxActionErrorTypes, } from "constants/ReduxActionConstants"; import { RestAction } from "api/ActionAPI"; -import { ActionWidgetIdsMap } from "sagas/ActionWidgetMapSagas"; export const createActionRequest = (payload: Partial) => { return { @@ -128,13 +127,6 @@ export const copyActionError = (payload: { }; }; -export const actionToWidgetIdMapSuccess = ( - map: ActionWidgetIdsMap, -): ReduxAction => ({ - type: ReduxActionTypes.CREATE_UPDATE_ACTION_WIDGETIDS_MAP_SUCCESS, - payload: map, -}); - export default { createAction: createActionRequest, fetchActions, diff --git a/app/client/src/actions/bindingActions.ts b/app/client/src/actions/bindingActions.ts deleted file mode 100644 index 9dc18bba3e..0000000000 --- a/app/client/src/actions/bindingActions.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - ReduxAction, - ReduxActionTypes, - ReduxActionWithoutPayload, -} from "constants/ReduxActionConstants"; -import { NamePathBindingMap } from "constants/BindingsConstants"; - -export const initBindingMapListener = (): ReduxActionWithoutPayload => ({ - type: ReduxActionTypes.CREATE_UPDATE_BINDINGS_MAP_LISTENER_INIT, -}); - -export const bindingsMapSuccess = ( - map: NamePathBindingMap, -): ReduxAction => ({ - type: ReduxActionTypes.CREATE_UPDATE_BINDINGS_MAP_SUCCESS, - payload: map, -}); diff --git a/app/client/src/actions/widgetActions.tsx b/app/client/src/actions/widgetActions.tsx index f57974a09a..0cfed7b34b 100644 --- a/app/client/src/actions/widgetActions.tsx +++ b/app/client/src/actions/widgetActions.tsx @@ -1,5 +1,13 @@ -import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants"; -import { ActionPayload, PageAction } from "constants/ActionConstants"; +import { + ReduxActionTypes, + ReduxAction, + ReduxActionErrorTypes, +} from "constants/ReduxActionConstants"; +import { + ActionPayload, + ExecuteErrorPayload, + PageAction, +} from "constants/ActionConstants"; export const executeAction = ( actionPayloads: ActionPayload[], @@ -10,6 +18,13 @@ export const executeAction = ( }; }; +export const executeActionError = ( + executeErrorPayload: ExecuteErrorPayload, +): ReduxAction => ({ + type: ReduxActionErrorTypes.EXECUTE_ACTION_ERROR, + payload: executeErrorPayload, +}); + export const executePageLoadActions = ( payload: PageAction[][], ): ReduxAction => ({ @@ -17,19 +32,6 @@ export const executePageLoadActions = ( payload, }); -export const loadingAction = ( - areLoading: boolean, - widgetIds: string[], -): ReduxAction => { - return { - type: ReduxActionTypes.LOADING_ACTION, - payload: { - areLoading: areLoading, - widgetIds: widgetIds, - }, - }; -}; - export const disableDragAction = ( disable: boolean, ): ReduxAction<{ disable: boolean }> => { diff --git a/app/client/src/components/editorComponents/ApiResponseView.tsx b/app/client/src/components/editorComponents/ApiResponseView.tsx index b4a6a20b31..ea48d73b73 100644 --- a/app/client/src/components/editorComponents/ApiResponseView.tsx +++ b/app/client/src/components/editorComponents/ApiResponseView.tsx @@ -12,6 +12,7 @@ import { APIEditorRouteParams } from "constants/routes"; import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer"; import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScreen"; import CodeEditor from "components/editorComponents/CodeEditor"; +import { getActionResponses } from "selectors/entitiesSelector"; const ResponseWrapper = styled.div` position: relative; @@ -49,9 +50,7 @@ const TableWrapper = styled.div` `; interface ReduxStateProps { - responses: { - [id: string]: ActionResponse; - }; + responses: Record; apiPane: ApiPaneReduxState; } @@ -100,7 +99,7 @@ const ApiResponseView = (props: Props) => { let response: ActionResponse = EMPTY_RESPONSE; let isRunning = false; if (apiId && apiId in responses) { - response = responses[apiId]; + response = responses[apiId] || EMPTY_RESPONSE; isRunning = apiPane.isRunning[apiId]; } return ( @@ -160,7 +159,7 @@ const ApiResponseView = (props: Props) => { }; const mapStateToProps = (state: AppState): ReduxStateProps => ({ - responses: state.entities.apiData, + responses: getActionResponses(state), apiPane: state.ui.apiPane, }); diff --git a/app/client/src/components/propertyControls/ActionSelectorControl.tsx b/app/client/src/components/propertyControls/ActionSelectorControl.tsx index c339cbb514..d82dfb6e17 100644 --- a/app/client/src/components/propertyControls/ActionSelectorControl.tsx +++ b/app/client/src/components/propertyControls/ActionSelectorControl.tsx @@ -155,7 +155,7 @@ export function FinalActionSelector(props: FinalActionSelectorProps) { updateLabel={props.updateLabel} actions={props.actions} labelEditable={props.labelEditable} - > + /> {selectedActionLabel !== DEFAULT_ACTION_LABEL && ( + /> + /> )} @@ -189,7 +189,7 @@ export function FinalActionSelector(props: FinalActionSelectorProps) { } class ActionSelectorControl extends BaseControl< - ControlProps & ActionDataState + ControlProps & { data: RestAction[] } > { render() { return ( @@ -199,7 +199,7 @@ class ActionSelectorControl extends BaseControl< actions={this.props.propertyValue} identifier={this.props.propertyName} updateActions={this.updateActions} - > + /> ); } @@ -216,8 +216,8 @@ export interface ActionSelectorControlProps extends ControlProps { propertyValue: ActionPayload[]; } -const mapStateToProps = (state: AppState): ActionDataState => ({ - ...state.entities.actions, +const mapStateToProps = (state: AppState): { data: RestAction[] } => ({ + data: state.entities.actions.map(a => a.config), }); export default connect(mapStateToProps)(ActionSelectorControl); diff --git a/app/client/src/components/propertyControls/ColumnActionSelectorControl.tsx b/app/client/src/components/propertyControls/ColumnActionSelectorControl.tsx index 373ecac7c2..1438091bca 100644 --- a/app/client/src/components/propertyControls/ColumnActionSelectorControl.tsx +++ b/app/client/src/components/propertyControls/ColumnActionSelectorControl.tsx @@ -12,6 +12,7 @@ import { generateReactKey } from "utils/generators"; import styled from "constants/DefaultTheme"; import { AnyStyledComponent } from "styled-components"; import { FormIcons } from "icons/FormIcons"; +import { RestAction } from "api/ActionAPI"; export interface ColumnAction { label: string; id: string; @@ -27,7 +28,7 @@ const StyledDeleteIcon = styled(FormIcons.DELETE_ICON as AnyStyledComponent)` `; class ColumnActionSelectorControl extends BaseControl< - ColumnActionSelectorControlProps & ActionDataState + ColumnActionSelectorControlProps & { data: RestAction[] } > { render() { return ( @@ -51,12 +52,12 @@ class ColumnActionSelectorControl extends BaseControl< label={columnAction.label} labelEditable={true} updateLabel={this.updateLabel} - > + /> + /> ); }, @@ -67,7 +68,7 @@ class ColumnActionSelectorControl extends BaseControl< color={"#FFFFFF"} minimal={true} onClick={this.addColumnAction} - > + /> ); } @@ -141,8 +142,8 @@ class ColumnActionSelectorControl extends BaseControl< export type ColumnActionSelectorControlProps = ControlProps; -const mapStateToProps = (state: AppState): ActionDataState => ({ - ...state.entities.actions, +const mapStateToProps = (state: AppState): { data: RestAction[] } => ({ + data: state.entities.actions.map(a => a.config), }); export default connect(mapStateToProps)(ColumnActionSelectorControl); diff --git a/app/client/src/constants/ActionConstants.tsx b/app/client/src/constants/ActionConstants.tsx index ec50400f75..8641519418 100644 --- a/app/client/src/constants/ActionConstants.tsx +++ b/app/client/src/constants/ActionConstants.tsx @@ -86,3 +86,8 @@ export interface PageAction { export interface TableAction extends BaseActionPayload { actionName: string; } + +export interface ExecuteErrorPayload { + actionId: string; + error: any; +} diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index 4b2713cbe7..b5986fab0d 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -31,7 +31,6 @@ export const ReduxActionTypes: { [key: string]: string } = { RUN_API_SUCCESS: "RUN_API_SUCCESS", EXECUTE_ACTION: "EXECUTE_ACTION", EXECUTE_ACTION_SUCCESS: "EXECUTE_ACTION_SUCCESS", - EXECUTE_ACTION_ERROR: "EXECUTE_ACTION_ERROR", LOAD_CANVAS_ACTIONS: "LOAD_CANVAS_ACTIONS", SAVE_PAGE_INIT: "SAVE_PAGE_INIT", SAVE_PAGE_SUCCESS: "SAVE_PAGE_SUCCESS", @@ -76,11 +75,6 @@ export const ReduxActionTypes: { [key: string]: string } = { FETCH_APPLICATION_LIST_SUCCESS: "FETCH_APPLICATION_LIST_SUCCESS", CREATE_APPLICATION_INIT: "CREATE_APPLICATION_INIT", CREATE_APPLICATION_SUCCESS: "CREATE_APPLICATION_SUCCESS", - CREATE_UPDATE_BINDINGS_MAP_LISTENER_INIT: - "CREATE_UPDATE_BINDINGS_MAP_LISTENER_INIT", - CREATE_UPDATE_BINDINGS_MAP_SUCCESS: "CREATE_UPDATE_BINDINGS_MAP_SUCCESS", - CREATE_UPDATE_ACTION_WIDGETIDS_MAP_SUCCESS: - "CREATE_UPDATE_ACTION_WIDGETIDS_MAP_SUCCESS", UPDATE_WIDGET_PROPERTY_VALIDATION: "UPDATE_WIDGET_PROPERTY_VALIDATION", HIDE_PROPERTY_PANE: "HIDE_PROPERTY_PANE", INIT_API_PANE: "INIT_API_PANE", diff --git a/app/client/src/constants/entityConstants.ts b/app/client/src/constants/entityConstants.ts new file mode 100644 index 0000000000..774dcb79bb --- /dev/null +++ b/app/client/src/constants/entityConstants.ts @@ -0,0 +1,2 @@ +export const ENTITY_TYPE_ACTION = "ACTION"; +export const ENTITY_TYPE_WIDGET = "WIDGET"; diff --git a/app/client/src/pages/Editor/ApiSidebar.tsx b/app/client/src/pages/Editor/ApiSidebar.tsx index 335d23b015..71907b6768 100644 --- a/app/client/src/pages/Editor/ApiSidebar.tsx +++ b/app/client/src/pages/Editor/ApiSidebar.tsx @@ -88,15 +88,15 @@ class ApiSidebar extends React.Component { ) { return true; } - return nextProps.actions.data !== this.props.actions.data; + return nextProps.actions !== this.props.actions; } handleCreateNew = () => { const { actions } = this.props; const { pageId } = this.props.match.params; - const pageApiNames = actions.data - .filter(a => a.pageId === pageId) - .map(a => a.name); + const pageApiNames = actions + .filter(a => a.config.pageId === pageId) + .map(a => a.config.name); const newName = getNextEntityName("Api", pageApiNames); this.props.createAction({ ...DEFAULT_API_ACTION, name: newName, pageId }); }; @@ -106,23 +106,28 @@ class ApiSidebar extends React.Component { }; handleMove = (itemId: string, destinationPageId: string) => { - const action = this.props.actions.data.filter(a => a.id === itemId)[0]; - const pageApiNames = this.props.actions.data - .filter(a => a.pageId === destinationPageId) - .map(a => a.name); - let name = action.name; - if (pageApiNames.indexOf(action.name) > -1) { + const action = this.props.actions.filter(a => a.config.id === itemId)[0]; + const pageApiNames = this.props.actions + .filter(a => a.config.pageId === destinationPageId) + .map(a => a.config.name); + let name = action.config.name; + if (pageApiNames.indexOf(action.config.name) > -1) { name = getNextEntityName(name, pageApiNames); } - this.props.moveAction(itemId, destinationPageId, name, action.pageId); + this.props.moveAction( + itemId, + destinationPageId, + name, + action.config.pageId, + ); }; handleCopy = (itemId: string, destinationPageId: string) => { - const action = this.props.actions.data.filter(a => a.id === itemId)[0]; - const pageApiNames = this.props.actions.data - .filter(a => a.pageId === destinationPageId) - .map(a => a.name); - let name = `${action.name}Copy`; + const action = this.props.actions.filter(a => a.config.id === itemId)[0]; + const pageApiNames = this.props.actions + .filter(a => a.config.pageId === destinationPageId) + .map(a => a.config.name); + let name = `${action.config.name}Copy`; if (pageApiNames.indexOf(name) > -1) { name = getNextEntityName(name, pageApiNames); } @@ -154,10 +159,11 @@ class ApiSidebar extends React.Component { match: { params: { apiId }, }, - actions: { data }, + actions, pluginId, } = this.props; if (!pluginId) return null; + const data = actions.map(a => a.config); return ( , - ) => ({ - ...state, - data: action.payload, - isFetching: false, - }), - [ReduxActionErrorTypes.FETCH_ACTIONS_ERROR]: (state: ActionDataState) => ({ - ...state, - data: [], - }), + ): ActionDataState => + action.payload.map(a => ({ + isLoading: false, + config: a, + })), + [ReduxActionErrorTypes.FETCH_ACTIONS_ERROR]: () => initialState, [ReduxActionTypes.CREATE_ACTION_INIT]: ( state: ActionDataState, action: ReduxAction, - ) => ({ - ...state, - data: state.data.concat([action.payload]), - }), + ): ActionDataState => + state.concat([{ config: action.payload, isLoading: false }]), [ReduxActionTypes.CREATE_ACTION_SUCCESS]: ( state: ActionDataState, action: ReduxAction, - ) => ({ - ...state, - data: state.data.map(a => { + ): ActionDataState => + state.map(a => { if ( - a.pageId === action.payload.pageId && - a.name === action.payload.name + a.config.pageId === action.payload.pageId && + a.config.name === action.payload.name ) { - return action.payload; + return { ...a, config: action.payload }; } return a; }), - }), [ReduxActionTypes.CREATE_ACTION_ERROR]: ( state: ActionDataState, action: ReduxAction, - ) => ({ - ...state, - data: state.data.filter( - a => a.name !== action.payload.name && a.pageId !== action.payload.pageId, + ): ActionDataState => + state.filter( + a => + a.config.name !== action.payload.name && + a.config.pageId !== action.payload.pageId, ), - }), [ReduxActionTypes.UPDATE_ACTION_SUCCESS]: ( state: ActionDataState, action: ReduxAction<{ data: RestAction }>, - ) => ({ - ...state, - data: state.data.map(d => { - if (d.id === action.payload.data.id) return action.payload.data; - return d; + ): ActionDataState => + state.map(a => { + if (a.config.id === action.payload.data.id) + return { ...a, config: action.payload.data }; + return a; }), - }), [ReduxActionTypes.DELETE_ACTION_SUCCESS]: ( state: ActionDataState, action: ReduxAction<{ id: string }>, - ) => ({ - ...state, - data: state.data.filter(d => d.id !== action.payload.id), - }), - [ReduxActionTypes.CREATE_UPDATE_ACTION_WIDGETIDS_MAP_SUCCESS]: ( + ): ActionDataState => state.filter(a => a.config.id !== action.payload.id), + [ReduxActionTypes.EXECUTE_ACTION]: ( state: ActionDataState, - action: ReduxAction, - ) => ({ - ...state, - actionToWidgetIdMap: action.payload, - }), + action: ReduxAction, + ): ActionDataState => + state.map(a => { + if (_.find(action.payload, { actionId: a.config.id })) { + return { + ...a, + isLoading: true, + }; + } + return a; + }), + [ReduxActionTypes.EXECUTE_ACTION_SUCCESS]: ( + state: ActionDataState, + action: ReduxAction<{ [id: string]: ActionResponse }>, + ): ActionDataState => { + const actionId = Object.keys(action.payload)[0]; + return state.map(a => { + if (a.config.id === actionId) { + return { ...a, isLoading: false, data: action.payload[actionId] }; + } + return a; + }); + }, + [ReduxActionErrorTypes.EXECUTE_ACTION_ERROR]: ( + state: ActionDataState, + action: ReduxAction, + ): ActionDataState => + state.map(a => { + if (a.config.id === action.payload.actionId) { + return { ...a, isLoading: false }; + } + return a; + }), + [ReduxActionTypes.RUN_API_REQUEST]: ( + state: ActionDataState, + action: ReduxAction, + ): ActionDataState => + state.map(a => { + if (action.payload === a.config.id) { + return { + ...a, + isLoading: true, + }; + } + return a; + }), + [ReduxActionTypes.RUN_API_SUCCESS]: ( + state: ActionDataState, + action: ReduxAction<{ [id: string]: ActionResponse }>, + ): ActionDataState => { + const actionId = Object.keys(action.payload)[0]; + return state.map(a => { + if (a.config.id === actionId) { + return { ...a, isLoading: false, data: action.payload[actionId] }; + } + return a; + }); + }, + [ReduxActionErrorTypes.RUN_API_ERROR]: ( + state: ActionDataState, + action: ReduxAction<{ id: string }>, + ): ActionDataState => + state.map(a => { + if (a.config.id === action.payload.id) { + return { ...a, isLoading: false }; + } + return a; + }), [ReduxActionTypes.MOVE_ACTION_INIT]: ( state: ActionDataState, action: ReduxAction<{ @@ -92,46 +144,46 @@ const actionsReducer = createReducer(initialState, { destinationPageId: string; name: string; }>, - ) => ({ - ...state, - data: state.data.map(restAction => { - if (restAction.id === action.payload.id) { + ): ActionDataState => + state.map(a => { + if (a.config.id === action.payload.id) { return { - ...restAction, - name: action.payload.name, - pageId: action.payload.destinationPageId, + ...a, + config: { + ...a.config, + name: action.payload.name, + pageId: action.payload.destinationPageId, + }, }; } - return restAction; + return a; }), - }), [ReduxActionTypes.MOVE_ACTION_SUCCESS]: ( state: ActionDataState, action: ReduxAction, - ) => ({ - ...state, - data: state.data.map(restAction => { - if (restAction.id === action.payload.id) { - return action.payload; + ): ActionDataState => + state.map(a => { + if (a.config.id === action.payload.id) { + return { ...a, config: action.payload }; } - return restAction; + return a; }), - }), [ReduxActionErrorTypes.MOVE_ACTION_ERROR]: ( state: ActionDataState, action: ReduxAction<{ id: string; originalPageId: string }>, - ) => ({ - ...state, - data: state.data.map(restAction => { - if (restAction.id === action.payload.id) { + ): ActionDataState => + state.map(a => { + if (a.config.id === action.payload.id) { return { - ...restAction, - pageId: action.payload.originalPageId, + ...a, + config: { + ...a.config, + pageId: action.payload.originalPageId, + }, }; } - return restAction; + return a; }), - }), [ReduxActionTypes.COPY_ACTION_INIT]: ( state: ActionDataState, action: ReduxAction<{ @@ -139,34 +191,35 @@ const actionsReducer = createReducer(initialState, { destinationPageId: string; name: string; }>, - ) => ({ - ...state, - data: state.data.concat( - state.data - .filter(a => a.id === action.payload.id) + ): ActionDataState => + state.concat( + state + .filter(a => a.config.id === action.payload.id) .map(a => ({ ...a, - name: action.payload.name, - pageId: action.payload.destinationPageId, + config: { + ...a.config, + name: action.payload.name, + pageId: action.payload.destinationPageId, + }, })), ), - }), [ReduxActionTypes.COPY_ACTION_SUCCESS]: ( state: ActionDataState, action: ReduxAction, - ) => ({ - ...state, - data: state.data.map(a => { + ): ActionDataState => + state.map(a => { if ( - a.pageId === action.payload.pageId && - a.name === action.payload.name + a.config.pageId === action.payload.pageId && + a.config.name === action.payload.name ) { - return action.payload; + return { + ...a, + config: action.payload, + }; } return a; }), - }), - [ReduxActionErrorTypes.COPY_ACTION_ERROR]: ( state: ActionDataState, action: ReduxAction<{ @@ -174,18 +227,16 @@ const actionsReducer = createReducer(initialState, { destinationPageId: string; name: string; }>, - ) => ({ - ...state, - data: state.data.filter(a => { - if (a.pageId === action.payload.destinationPageId) { - if (a.id === action.payload.id) { - return a.name !== action.payload.name; + ): ActionDataState => + state.filter(a => { + if (a.config.pageId === action.payload.destinationPageId) { + if (a.config.id === action.payload.id) { + return a.config.name !== action.payload.name; } return true; } return true; }), - }), }); export default actionsReducer; diff --git a/app/client/src/reducers/entityReducers/apiDataReducer.tsx b/app/client/src/reducers/entityReducers/apiDataReducer.tsx deleted file mode 100644 index ed0971815f..0000000000 --- a/app/client/src/reducers/entityReducers/apiDataReducer.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { createReducer } from "utils/AppsmithUtils"; -import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants"; -import { ActionResponse } from "api/ActionAPI"; -import { ActionDataState } from "./actionsReducer"; -import _ from "lodash"; - -const initialState: APIDataState = {}; - -export type APIDataState = Record; - -const apiDataReducer = createReducer(initialState, { - [ReduxActionTypes.EXECUTE_ACTION_SUCCESS]: ( - state: ActionDataState, - action: ReduxAction<{ [id: string]: ActionResponse }>, - ) => ({ ...state, ...action.payload }), - [ReduxActionTypes.RUN_API_SUCCESS]: ( - state: ActionDataState, - action: ReduxAction<{ [id: string]: ActionResponse }>, - ) => ({ ...state, ...action.payload }), - [ReduxActionTypes.DELETE_ACTION_SUCCESS]: ( - state: ActionDataState, - action: ReduxAction<{ id: string }>, - ) => _.omit(state, action.payload.id), -}); - -export default apiDataReducer; diff --git a/app/client/src/reducers/entityReducers/bindingsReducer.ts b/app/client/src/reducers/entityReducers/bindingsReducer.ts deleted file mode 100644 index f2c5cf2844..0000000000 --- a/app/client/src/reducers/entityReducers/bindingsReducer.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createReducer } from "utils/AppsmithUtils"; -import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants"; -import { NamePathBindingMap } from "constants/BindingsConstants"; - -export type BindingsDataState = NamePathBindingMap; - -const initialState: BindingsDataState = {}; - -const bindingsReducer = createReducer(initialState, { - [ReduxActionTypes.CREATE_UPDATE_BINDINGS_MAP_SUCCESS]: ( - state: BindingsDataState, - action: ReduxAction, - ) => action.payload, -}); - -export default bindingsReducer; diff --git a/app/client/src/reducers/entityReducers/canvasWidgetsReducer.tsx b/app/client/src/reducers/entityReducers/canvasWidgetsReducer.tsx index 41e94d7226..7b516056a9 100644 --- a/app/client/src/reducers/entityReducers/canvasWidgetsReducer.tsx +++ b/app/client/src/reducers/entityReducers/canvasWidgetsReducer.tsx @@ -28,19 +28,6 @@ const canvasWidgetsReducer = createReducer(initialState, { ) => { return { ...action.payload.widgets }; }, - [ReduxActionTypes.WIDGETS_LOADING]: ( - state: CanvasWidgetsReduxState, - action: ReduxAction, - ) => { - const finalState = { ...state }; - action.payload.widgetIds.forEach(widgetId => { - const widget = state[widgetId]; - widget.isLoading = action.payload.areLoading; - finalState[widgetId] = widget; - }); - - return finalState; - }, [ReduxActionTypes.UPDATE_WIDGET_PROPERTY]: ( state: CanvasWidgetsReduxState, action: ReduxAction, diff --git a/app/client/src/reducers/entityReducers/index.tsx b/app/client/src/reducers/entityReducers/index.tsx index 27279756de..4882ed9674 100644 --- a/app/client/src/reducers/entityReducers/index.tsx +++ b/app/client/src/reducers/entityReducers/index.tsx @@ -1,25 +1,21 @@ import { combineReducers } from "redux"; import canvasWidgetsReducer from "./canvasWidgetsReducer"; -import apiDataReducer from "./apiDataReducer"; import queryDataReducer from "./queryDataReducer"; import widgetConfigReducer from "./widgetConfigReducer"; import actionsReducer from "./actionsReducer"; import propertyPaneConfigReducer from "./propertyPaneConfigReducer"; import datasourceReducer from "./datasourceReducer"; -import bindingsReducer from "./bindingsReducer"; import pageListReducer from "./pageListReducer"; import jsExecutionsReducer from "./jsExecutionsReducer"; import pluginsReducer from "reducers/entityReducers/pluginsReducer"; const entityReducer = combineReducers({ canvasWidgets: canvasWidgetsReducer, - apiData: apiDataReducer, queryData: queryDataReducer, widgetConfig: widgetConfigReducer, actions: actionsReducer, propertyConfig: propertyPaneConfigReducer, datasources: datasourceReducer, - nameBindings: bindingsReducer, pageList: pageListReducer, jsExecutions: jsExecutionsReducer, plugins: pluginsReducer, diff --git a/app/client/src/reducers/index.tsx b/app/client/src/reducers/index.tsx index 80fe84894e..5aff04ce14 100644 --- a/app/client/src/reducers/index.tsx +++ b/app/client/src/reducers/index.tsx @@ -5,7 +5,6 @@ import { reducer as formReducer } from "redux-form"; import { CanvasWidgetsReduxState } from "./entityReducers/canvasWidgetsReducer"; import { EditorReduxState } from "./uiReducers/editorReducer"; import { ErrorReduxState } from "./uiReducers/errorReducer"; -import { APIDataState } from "./entityReducers/apiDataReducer"; import { QueryDataState } from "./entityReducers/queryDataReducer"; import { ActionDataState } from "./entityReducers/actionsReducer"; import { PropertyPaneConfigState } from "./entityReducers/propertyPaneConfigReducer"; @@ -15,7 +14,6 @@ import { WidgetSidebarReduxState } from "./uiReducers/widgetSidebarReducer"; import { DatasourceDataState } from "./entityReducers/datasourceReducer"; import { AppViewReduxState } from "./uiReducers/appViewReducer"; import { ApplicationsReduxState } from "./uiReducers/applicationsReducer"; -import { BindingsDataState } from "./entityReducers/bindingsReducer"; import { PageListReduxState } from "./entityReducers/pageListReducer"; import { ApiPaneReduxState } from "./uiReducers/apiPaneReducer"; import { RoutesParamsReducerState } from "reducers/uiReducers/routesParamsReducer"; @@ -50,13 +48,11 @@ export interface AppState { }; entities: { canvasWidgets: CanvasWidgetsReduxState; - apiData: APIDataState; queryData: QueryDataState; actions: ActionDataState; propertyConfig: PropertyPaneConfigState; widgetConfig: WidgetConfigReducerState; datasources: DatasourceDataState; - nameBindings: BindingsDataState; pageList: PageListReduxState; plugins: PluginDataState; }; diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index 1d70feec9d..79b50f0273 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -25,7 +25,7 @@ import ActionAPI, { Property, RestAction, } from "api/ActionAPI"; -import { AppState, DataTree } from "reducers"; +import { AppState } from "reducers"; import _ from "lodash"; import { mapToPropList } from "utils/AppsmithUtils"; import AppToaster from "components/editorComponents/ToastComponent"; @@ -48,23 +48,24 @@ import { removeBindingsFromObject, } from "utils/DynamicBindingUtils"; import { validateResponse } from "./ErrorSagas"; -import { getDataTree } from "selectors/entitiesSelector"; import { ERROR_MESSAGE_SELECT_ACTION, ERROR_MESSAGE_SELECT_ACTION_TYPE, } from "constants/messages"; import { getFormData } from "selectors/formSelectors"; import { API_EDITOR_FORM_NAME } from "constants/forms"; -import { executeAction } from "actions/widgetActions"; +import { executeAction, executeActionError } from "actions/widgetActions"; import JSExecutionManagerSingleton from "jsExecution/JSExecutionManagerSingleton"; import { getParsedDataTree } from "selectors/nameBindingsWithDataSelector"; import { transformRestAction } from "transformers/RestActionTransformer"; +import { getActionResponses } from "selectors/entitiesSelector"; export const getAction = ( state: AppState, actionId: string, ): RestAction | undefined => { - return _.find(state.entities.actions.data, { id: actionId }); + const action = _.find(state.entities.actions, a => a.config.id === actionId); + return action ? action.config : undefined; }; const createActionResponse = (response: ActionApiResponse): ActionResponse => ({ @@ -126,10 +127,12 @@ export function* executeAPIQueryActionSaga(apiAction: ActionPayload) { try { const api: PageAction = yield select(getAction, apiAction.actionId); if (!api) { - yield put({ - type: ReduxActionTypes.EXECUTE_ACTION_ERROR, - payload: "No action selected", - }); + yield put( + executeActionError({ + actionId: apiAction.actionId, + error: "No action selected", + }), + ); return; } const params: Property[] = yield call(getActionParams, api.jsonPathKeys); @@ -137,26 +140,9 @@ export function* executeAPIQueryActionSaga(apiAction: ActionPayload) { action: { id: apiAction.actionId }, params, }; - const dataTree: DataTree = yield select(getDataTree); - yield put({ - type: ReduxActionTypes.WIDGETS_LOADING, - payload: { - widgetIds: - dataTree.actions.actionToWidgetIdMap[apiAction.actionId] || [], - areLoading: true, - }, - }); const response: ActionApiResponse = yield ActionAPI.executeAction( executeActionRequest, ); - yield put({ - type: ReduxActionTypes.WIDGETS_LOADING, - payload: { - widgetIds: - dataTree.actions.actionToWidgetIdMap[apiAction.actionId] || [], - areLoading: false, - }, - }); let payload = createActionResponse(response); if (response.responseMeta && response.responseMeta.error) { payload = createActionErrorResponse(response); @@ -166,10 +152,12 @@ export function* executeAPIQueryActionSaga(apiAction: ActionPayload) { payload: apiAction.onError, }); } - yield put({ - type: ReduxActionTypes.EXECUTE_ACTION_ERROR, - payload: { [apiAction.actionId]: payload }, - }); + yield put( + executeActionError({ + actionId: apiAction.actionId, + error: response.responseMeta.error, + }), + ); } else { if (apiAction.onSuccess) { yield put({ @@ -184,10 +172,12 @@ export function* executeAPIQueryActionSaga(apiAction: ActionPayload) { } return response; } catch (error) { - yield put({ - type: ReduxActionTypes.EXECUTE_ACTION_ERROR, - payload: { error }, - }); + yield put( + executeActionError({ + actionId: apiAction.actionId, + error, + }), + ); } } @@ -241,10 +231,12 @@ export function* executeReduxActionSaga(action: ReduxAction) { if (!_.isNil(action.payload)) { yield* executeActionSaga(action.payload); } else { - yield put({ - type: ReduxActionTypes.EXECUTE_ACTION_ERROR, - payload: "No action payload", - }); + yield put( + executeActionError({ + actionId: "", + error: "No action payload", + }), + ); } } @@ -390,9 +382,7 @@ export function* runApiActionSaga(action: ReduxAction) { function* executePageLoadActionsSaga(action: ReduxAction) { const pageActions = action.payload; - const apiResponses = yield select( - (state: AppState) => state.entities.apiData, - ); + const apiResponses = yield select(getActionResponses); const actionPayloads: ActionPayload[][] = pageActions.map(actionSet => actionSet.map(action => ({ actionId: action.id, diff --git a/app/client/src/sagas/ActionWidgetMapSagas.tsx b/app/client/src/sagas/ActionWidgetMapSagas.tsx deleted file mode 100644 index 138fb441cc..0000000000 --- a/app/client/src/sagas/ActionWidgetMapSagas.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { all, select, takeLatest, put } from "redux-saga/effects"; -import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants"; -import { actionToWidgetIdMapSuccess } from "actions/actionActions"; -import { AppState } from "reducers"; -import { getDynamicBindings } from "utils/DynamicBindingUtils"; -import { UpdateWidgetPropertyPayload } from "actions/controlActions"; -import _ from "lodash"; -import { RestAction } from "api/ActionAPI"; -export type ActionWidgetIdsMap = Record; - -function* init() { - const data: AppState = yield select(); - const actionToWidgetIdMap: any = {}; - - Object.keys(data.entities.canvasWidgets).forEach(widgetId => { - const widget = data.entities.canvasWidgets[widgetId]; - const dynamicBindings = widget.dynamicBindings || {}; - - Object.keys(dynamicBindings).forEach(key => { - const dynamicBindingString = widget[key]; - const binding = getDynamicBindings(dynamicBindingString); - const firstElementInPathList = binding.paths.map( - path => path.split(".")[0], - ); - firstElementInPathList.forEach(name => { - const action = data.entities.actions.data.find( - action => action.name === name, - ); - if (action) { - if (actionToWidgetIdMap[action.id] === undefined) { - actionToWidgetIdMap[action.id] = []; - } - - if (!_.includes(actionToWidgetIdMap[action.id], widgetId)) { - actionToWidgetIdMap[action.id].push(widgetId); - } - } - }); - }); - }); - yield put(actionToWidgetIdMapSuccess(actionToWidgetIdMap)); -} - -function getActionsForPaths( - paths: string[], - actions: RestAction[], -): RestAction[] { - // Let's say dynamic binding = {{UsersTable.selectedRow}} - return ( - paths // Get UsersTable - .map(path => path.split(".")[0]) - // Check if the UsersTable is an action or not - .map(apiName => { - const action = actions.find(action => action.name === apiName); - return action ? action : null; - }) - // Filter the null generated by non actions - .filter(action => action !== null) as RestAction[] - ); -} - -function removeWidgetForActionId( - actionToWidgetIdMap: ActionWidgetIdsMap, - actionId: string, - widgetId: string, -) { - actionToWidgetIdMap[actionId] = actionToWidgetIdMap[actionId].filter( - actionWidgetId => actionWidgetId !== widgetId, - ); -} - -function removeWidgetFromUnboundAction( - boundActions: RestAction[], - actionToWidgetIdMap: ActionWidgetIdsMap, - widgetId: string, -) { - const mapActionIds = Object.keys(actionToWidgetIdMap); - mapActionIds.forEach(mapActionId => { - const actionFound = boundActions.find(boundAction => { - return boundAction.id === mapActionId; - }); - - if (!actionFound) { - removeWidgetForActionId(actionToWidgetIdMap, mapActionId, widgetId); - } - }); -} - -function addWidgetToBoundAction( - boundActions: RestAction[], - actionToWidgetIdMap: ActionWidgetIdsMap, - widgetId: string, -) { - boundActions.forEach(boundAction => { - // Initiate if undefined - if (actionToWidgetIdMap[boundAction.id] === undefined) { - actionToWidgetIdMap[boundAction.id] = []; - } - // Make sure you don't push any duplicate widgetIds inside the map - if (!_.includes(actionToWidgetIdMap[boundAction.id], widgetId)) { - actionToWidgetIdMap[boundAction.id].push(widgetId); - } - }); -} - -function* updateAction(reduxAction: ReduxAction) { - const data: AppState = yield select(); - const actionsData = data.entities.actions; - const allActions = actionsData.data; - const actionToWidgetIdMap = { - ...actionsData.actionToWidgetIdMap, - }; - const { widgetId } = reduxAction.payload; - - const widget = data.entities.canvasWidgets[widgetId]; - const widgetDynamicBindings = widget.dynamicBindings as Record< - string, - boolean - >; - - if (widgetDynamicBindings !== undefined) { - const paths = Object.keys(widgetDynamicBindings) - .map(propName => { - const dynamicBindingString = widget[propName]; - return getDynamicBindings(dynamicBindingString); - }) - .map(obj => obj.paths) - .flat(); - - const boundActions = getActionsForPaths(paths, allActions); - addWidgetToBoundAction(boundActions, actionToWidgetIdMap, widgetId); - removeWidgetFromUnboundAction(boundActions, actionToWidgetIdMap, widgetId); - } - - yield put( - actionToWidgetIdMapSuccess({ - ...data.entities.actions.actionToWidgetIdMap, - ...actionToWidgetIdMap, - }), - ); -} - -export function* watchPropertyAndBindingUpdate() { - yield takeLatest( - [ - ReduxActionTypes.UPDATE_WIDGET_PROPERTY, - ReduxActionTypes.UPDATE_WIDGET_DYNAMIC_PROPERTY, - ], - updateAction, - ); -} - -export default function* watchActionWidgetMapSagas() { - yield all([takeLatest(ReduxActionTypes.INITIALIZE_EDITOR_SUCCESS, init)]); -} diff --git a/app/client/src/sagas/ApiPaneSagas.ts b/app/client/src/sagas/ApiPaneSagas.ts index 12796f8380..9cec291269 100644 --- a/app/client/src/sagas/ApiPaneSagas.ts +++ b/app/client/src/sagas/ApiPaneSagas.ts @@ -35,7 +35,8 @@ const getApiDraft = (state: AppState, id: string) => { return {}; }; -const getActions = (state: AppState) => state.entities.actions.data; +const getActions = (state: AppState) => + state.entities.actions.map(a => a.config); const getLastUsedAction = (state: AppState) => state.ui.apiPane.lastUsed; diff --git a/app/client/src/sagas/BindingsSagas.ts b/app/client/src/sagas/BindingsSagas.ts deleted file mode 100644 index 1f5eab857a..0000000000 --- a/app/client/src/sagas/BindingsSagas.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { all, select, takeLatest, put, call, take } from "redux-saga/effects"; -import { ReduxActionTypes } from "constants/ReduxActionConstants"; -import { AppState } from "reducers"; -import { bindingsMapSuccess } from "actions/bindingActions"; - -function* createUpdateBindingsMapData() { - const data: AppState = yield select(); - const map: Record = {}; - data.entities.actions.data.forEach(action => { - map[action.name] = `$.apiData.${action.id}.body`; - }); - Object.keys(data.entities.canvasWidgets).forEach(widgetId => { - const name = data.entities.canvasWidgets[widgetId].widgetName; - map[name] = `$.canvasWidgets.${widgetId}`; - }); - yield put(bindingsMapSuccess(map)); -} - -// The listener will keep track of any action -// that requires an update of the action and -// then call the update function again -function* initListener() { - while (true) { - // list all actions types here - yield take([ - ReduxActionTypes.INITIALIZE_EDITOR_SUCCESS, - ReduxActionTypes.CREATE_ACTION_SUCCESS, - ReduxActionTypes.UPDATE_ACTION_SUCCESS, - ReduxActionTypes.DELETE_ACTION_SUCCESS, - ReduxActionTypes.UPDATE_CANVAS, - ReduxActionTypes.SAVE_PAGE_INIT, - ]); - yield call(createUpdateBindingsMapData); - } -} - -export default function* watchBindingsSagas() { - yield all([ - takeLatest( - ReduxActionTypes.CREATE_UPDATE_BINDINGS_MAP_LISTENER_INIT, - initListener, - ), - ]); -} diff --git a/app/client/src/sagas/InitSagas.ts b/app/client/src/sagas/InitSagas.ts index 7cc54c0cb7..ff7f6e9309 100644 --- a/app/client/src/sagas/InitSagas.ts +++ b/app/client/src/sagas/InitSagas.ts @@ -8,7 +8,6 @@ import { import { fetchEditorConfigs } from "actions/configsActions"; import { fetchPageList } from "actions/pageActions"; import { fetchDatasources } from "actions/datasourcesActions"; -import { initBindingMapListener } from "actions/bindingActions"; import { fetchPlugins } from "actions/pluginActions"; import { fetchActions } from "actions/actionActions"; @@ -21,7 +20,6 @@ function* initializeEditorSaga( put(fetchPlugins()), put(fetchPageList(applicationId)), put(fetchEditorConfigs()), - put(initBindingMapListener()), put(fetchActions(applicationId)), put(fetchDatasources()), ]); @@ -44,7 +42,6 @@ export function* initializeAppViewerSaga( ) { const { applicationId } = action.payload; yield all([ - put(initBindingMapListener()), put(fetchActions(applicationId)), put(fetchPageList(applicationId)), ]); diff --git a/app/client/src/sagas/index.tsx b/app/client/src/sagas/index.tsx index 2dd4f9a840..b0325abeeb 100644 --- a/app/client/src/sagas/index.tsx +++ b/app/client/src/sagas/index.tsx @@ -8,10 +8,6 @@ import configsSagas from "./ConfigsSagas"; import applicationSagas from "./ApplicationSagas"; import { watchDatasourcesSagas } from "./DatasourcesSagas"; import initSagas from "./InitSagas"; -import bindingsSagas from "./BindingsSagas"; -import watchActionWidgetMapSagas, { - watchPropertyAndBindingUpdate, -} from "./ActionWidgetMapSagas"; import apiPaneSagas from "./ApiPaneSagas"; import userSagas from "./userSagas"; import pluginSagas from "./PluginSagas"; @@ -28,9 +24,6 @@ export function* rootSaga() { spawn(configsSagas), spawn(watchDatasourcesSagas), spawn(applicationSagas), - spawn(bindingsSagas), - spawn(watchActionWidgetMapSagas), - spawn(watchPropertyAndBindingUpdate), spawn(apiPaneSagas), spawn(userSagas), spawn(pluginSagas), diff --git a/app/client/src/selectors/entitiesSelector.ts b/app/client/src/selectors/entitiesSelector.ts index dbabecd2cf..4ed4b259b8 100644 --- a/app/client/src/selectors/entitiesSelector.ts +++ b/app/client/src/selectors/entitiesSelector.ts @@ -1,11 +1,9 @@ import { AppState, DataTree } from "reducers"; import { ActionDataState } from "reducers/entityReducers/actionsReducer"; +import { ActionResponse } from "api/ActionAPI"; export const getDataTree = (state: AppState): DataTree => state.entities; -export const getDynamicNames = (state: AppState): DataTree["nameBindings"] => - state.entities.nameBindings; - export const getPluginIdOfName = ( state: AppState, name: string, @@ -17,5 +15,15 @@ export const getPluginIdOfName = ( return plugin.id; }; -export const getActions = (state: AppState): ActionDataState["data"] => - state.entities.actions.data; +export const getActions = (state: AppState): ActionDataState => + state.entities.actions; + +export const getActionResponses = ( + state: AppState, +): Record => { + const responses: Record = {}; + state.entities.actions.forEach(a => { + responses[a.config.id] = a.data; + }); + return responses; +}; diff --git a/app/client/src/selectors/nameBindingsWithDataSelector.ts b/app/client/src/selectors/nameBindingsWithDataSelector.ts index 051e0f3e19..7c4d3ce703 100644 --- a/app/client/src/selectors/nameBindingsWithDataSelector.ts +++ b/app/client/src/selectors/nameBindingsWithDataSelector.ts @@ -1,23 +1,33 @@ import { AppState, DataTree } from "reducers"; -import { JSONPath } from "jsonpath-plus"; import { createSelector } from "reselect"; import { getActions, getDataTree } from "./entitiesSelector"; import { ActionDataState } from "reducers/entityReducers/actionsReducer"; import createCachedSelector from "re-reselect"; import { getEvaluatedDataTree } from "utils/DynamicBindingUtils"; +import { + ENTITY_TYPE_ACTION, + ENTITY_TYPE_WIDGET, +} from "constants/entityConstants"; -export type NameBindingsWithData = Record; +export type NameBindingsWithData = { [key: string]: any }; export const getNameBindingsWithData = createSelector( getDataTree, (dataTree: DataTree): NameBindingsWithData => { const nameBindingsWithData: Record = {}; - Object.keys(dataTree.nameBindings).forEach(key => { - const nameBindings = dataTree.nameBindings[key]; - nameBindingsWithData[key] = JSONPath({ - path: nameBindings, - json: dataTree, - })[0]; + dataTree.actions.forEach(a => { + nameBindingsWithData[a.config.name] = { + ...a, + data: a.data ? a.data.body : {}, + __type: ENTITY_TYPE_ACTION, + }; + }); + Object.keys(dataTree.canvasWidgets).forEach(w => { + const widget = dataTree.canvasWidgets[w]; + nameBindingsWithData[widget.widgetName] = { + ...widget, + __type: ENTITY_TYPE_WIDGET, + }; }); return nameBindingsWithData; }, @@ -35,19 +45,21 @@ export const getParsedDataTree = createSelector( export const getNameBindingsForAutocomplete = createCachedSelector( getParsedDataTree, getActions, - (dataTree: NameBindingsWithData, actions: ActionDataState["data"]) => { + (dataTree: NameBindingsWithData, actions: ActionDataState) => { const cachedResponses: Record = {}; if (actions && actions.length) { actions.forEach(action => { - if (!(action.name in dataTree) && action.cacheResponse) { + if (!(action.config.name in dataTree) && action.config.cacheResponse) { try { - cachedResponses[action.name] = JSON.parse(action.cacheResponse); + cachedResponses[action.config.name] = JSON.parse( + action.config.cacheResponse, + ); } catch (e) { - cachedResponses[action.name] = action.cacheResponse; + cachedResponses[action.config.name] = action.config.cacheResponse; } } }); } return { ...dataTree, ...cachedResponses }; }, -)((state: AppState) => state.entities.actions.data.length); +)((state: AppState) => state.entities.actions.length); diff --git a/app/client/src/utils/DynamicBindingUtils.ts b/app/client/src/utils/DynamicBindingUtils.ts index 13e41dfc64..2937170c1a 100644 --- a/app/client/src/utils/DynamicBindingUtils.ts +++ b/app/client/src/utils/DynamicBindingUtils.ts @@ -6,6 +6,7 @@ import JSExecutionManagerSingleton from "jsExecution/JSExecutionManagerSingleton import unescapeJS from "unescape-js"; import { NameBindingsWithData } from "selectors/nameBindingsWithDataSelector"; import toposort from "toposort"; +import { ENTITY_TYPE_ACTION } from "constants/entityConstants"; export const removeBindingsFromObject = (obj: object) => { const string = JSON.stringify(obj); @@ -207,10 +208,11 @@ export const getEvaluatedDataTree = ( dynamicDependencyMap, parseValues, ); + const treeWithLoading = setTreeLoading(evaluatedTree, dynamicDependencyMap); if (parseValues) { - return getParsedTree(evaluatedTree); + return getParsedTree(treeWithLoading); } else { - return evaluatedTree; + return treeWithLoading; } }; @@ -268,6 +270,59 @@ const calculateSubDependencies = ( return subDeps; }; +export const setTreeLoading = ( + dataTree: NameBindingsWithData, + dependencyMap: Array<[string, string]>, +) => { + const result = _.cloneDeep(dataTree); + Object.keys(dataTree) + .filter( + e => dataTree[e].__type === ENTITY_TYPE_ACTION && dataTree[e].isLoading, + ) + .reduce( + (allEntities: string[], curr) => + allEntities.concat(getEntityDependencies(dependencyMap, curr)), + [], + ) + .forEach(w => (result[w].isLoading = true)); + return result; +}; + +export const getEntityDependencies = ( + dependencyMap: Array<[string, string]>, + entity: string, +): Array => { + const entityDeps: Record = dependencyMap + .map(d => [d[1].split(".")[0], d[0].split(".")[0]]) + .filter(d => d[0] !== d[1]) + .reduce((deps: Record, dep) => { + const key: string = dep[0]; + const value: string = dep[1]; + return { + ...deps, + [key]: deps[key] ? deps[key].concat(value) : [value], + }; + }, {}); + + if (entity in entityDeps) { + const recFind = ( + keys: Array, + deps: Record, + ): Array => { + let allDeps: string[] = []; + keys.forEach(e => { + allDeps = allDeps.concat([e]); + if (e in deps) { + allDeps = allDeps.concat([...recFind(deps[e], deps)]); + } + }); + return allDeps; + }; + return recFind(entityDeps[entity], entityDeps); + } + return []; +}; + export function dependencySortedEvaluateDataTree( dataTree: NameBindingsWithData, dependencyTree: Array<[string, string]>, diff --git a/app/client/src/utils/DynamicBindingsUtil.test.ts b/app/client/src/utils/DynamicBindingsUtil.test.ts index d646b2bf8e..472ce375cc 100644 --- a/app/client/src/utils/DynamicBindingsUtil.test.ts +++ b/app/client/src/utils/DynamicBindingsUtil.test.ts @@ -11,6 +11,7 @@ jest.mock("jsExecution/RealmExecutor", () => { import { dependencySortedEvaluateDataTree, getDynamicValue, + getEntityDependencies, parseDynamicString, } from "./DynamicBindingUtils"; import { getNameBindingsWithData } from "selectors/nameBindingsWithDataSelector"; @@ -23,35 +24,11 @@ beforeAll(() => { it("Gets the value from the data tree", () => { const dynamicBinding = "{{GetUsers.data}}"; - const dataTree: Partial = { - apiData: { - id: { - body: { - data: "correct data", - }, - headers: {}, - statusCode: "0", - duration: "0", - size: "0", - }, - someOtherId: { - body: { - data: "wrong data", - }, - headers: {}, - statusCode: "0", - duration: "0", - size: "0", - }, - }, - nameBindings: { - GetUsers: "$.apiData.id.body", + const nameBindingsWithData = { + GetUsers: { + data: "correct data", }, }; - const appState: Partial = { - entities: dataTree as DataTree, - }; - const nameBindingsWithData = getNameBindingsWithData(appState as AppState); const actualValue = "correct data"; const value = getDynamicValue(dynamicBinding, nameBindingsWithData); @@ -88,44 +65,6 @@ describe.each([ }); }); -it("Parse the dynamic string", () => { - const dynamicBinding = "{{GetUsers.data}}"; - const dataTree: Partial = { - apiData: { - id: { - body: { - data: "correct data", - }, - headers: {}, - statusCode: "0", - duration: "0", - size: "0", - }, - someOtherId: { - body: { - data: "wrong data", - }, - headers: {}, - statusCode: "0", - duration: "0", - size: "0", - }, - }, - nameBindings: { - GetUsers: "$.apiData.id.body", - }, - }; - const appState: Partial = { - entities: dataTree as DataTree, - }; - const nameBindingsWithData = getNameBindingsWithData(appState as AppState); - const actualValue = "correct data"; - - const value = getDynamicValue(dynamicBinding, nameBindingsWithData); - - expect(value).toEqual(actualValue); -}); - it("evaluates the data tree", () => { const input = { widget1: { @@ -165,3 +104,19 @@ it("evaluates the data tree", () => { const result = dependencySortedEvaluateDataTree(input, dynamicBindings); expect(result).toEqual(output); }); + +it("finds dependencies of a entity", () => { + const depMap: Array<[string, string]> = [ + ["Widget5.text", "Widget2.data.visible"], + ["Widget1.options", "Action1.data"], + ["Widget2.text", "Widget1.selectedOption"], + ["Widget3.text", "Widget4.selectedRow.name"], + ["Widget6.label", "Action1.data.label"], + ]; + const entity = "Action1"; + const result = ["Widget1", "Widget2", "Widget5", "Widget6"]; + + const actual = getEntityDependencies(depMap, entity); + + expect(actual).toEqual(result); +}); diff --git a/app/client/src/widgets/BaseWidget.tsx b/app/client/src/widgets/BaseWidget.tsx index d03f8e14c0..1c7ab155af 100644 --- a/app/client/src/widgets/BaseWidget.tsx +++ b/app/client/src/widgets/BaseWidget.tsx @@ -256,7 +256,6 @@ export interface WidgetProps extends WidgetDataProps { key?: string; renderMode: RenderMode; dynamicBindings?: Record; - isLoading: boolean; invalidProps?: Record; validationMessages?: Record; [key: string]: any; diff --git a/app/client/yarn.lock b/app/client/yarn.lock index cb1cddb704..953a2ae3c7 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -9197,11 +9197,6 @@ jsonify@~0.0.0: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= -jsonpath-plus@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-1.1.0.tgz#7caaea4db88b761a0a3b55d715cb01eaa469dfa5" - integrity sha512-ydqTBOuLcFCUr9e7AxJlKCFgxzEQ03HjnIim0hJSdk2NxD8MOsaMOrRgP6XWEm5q3VuDY5+cRT1DM9vLlGo/qA== - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"