Refactor Widget loading
This commit is contained in:
parent
97dbc156f0
commit
0dd25ba2ef
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import {
|
|||
ReduxActionErrorTypes,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { RestAction } from "api/ActionAPI";
|
||||
import { ActionWidgetIdsMap } from "sagas/ActionWidgetMapSagas";
|
||||
|
||||
export const createActionRequest = (payload: Partial<RestAction>) => {
|
||||
return {
|
||||
|
|
@ -128,13 +127,6 @@ export const copyActionError = (payload: {
|
|||
};
|
||||
};
|
||||
|
||||
export const actionToWidgetIdMapSuccess = (
|
||||
map: ActionWidgetIdsMap,
|
||||
): ReduxAction<ActionWidgetIdsMap> => ({
|
||||
type: ReduxActionTypes.CREATE_UPDATE_ACTION_WIDGETIDS_MAP_SUCCESS,
|
||||
payload: map,
|
||||
});
|
||||
|
||||
export default {
|
||||
createAction: createActionRequest,
|
||||
fetchActions,
|
||||
|
|
|
|||
|
|
@ -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<NamePathBindingMap> => ({
|
||||
type: ReduxActionTypes.CREATE_UPDATE_BINDINGS_MAP_SUCCESS,
|
||||
payload: map,
|
||||
});
|
||||
|
|
@ -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<ExecuteErrorPayload> => ({
|
||||
type: ReduxActionErrorTypes.EXECUTE_ACTION_ERROR,
|
||||
payload: executeErrorPayload,
|
||||
});
|
||||
|
||||
export const executePageLoadActions = (
|
||||
payload: PageAction[][],
|
||||
): ReduxAction<PageAction[][]> => ({
|
||||
|
|
@ -17,19 +32,6 @@ export const executePageLoadActions = (
|
|||
payload,
|
||||
});
|
||||
|
||||
export const loadingAction = (
|
||||
areLoading: boolean,
|
||||
widgetIds: string[],
|
||||
): ReduxAction<WidgetLoadingState> => {
|
||||
return {
|
||||
type: ReduxActionTypes.LOADING_ACTION,
|
||||
payload: {
|
||||
areLoading: areLoading,
|
||||
widgetIds: widgetIds,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const disableDragAction = (
|
||||
disable: boolean,
|
||||
): ReduxAction<{ disable: boolean }> => {
|
||||
|
|
|
|||
|
|
@ -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<string, ActionResponse | undefined>;
|
||||
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,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ export function FinalActionSelector(props: FinalActionSelectorProps) {
|
|||
updateLabel={props.updateLabel}
|
||||
actions={props.actions}
|
||||
labelEditable={props.labelEditable}
|
||||
></ActionSelector>
|
||||
/>
|
||||
{selectedActionLabel !== DEFAULT_ACTION_LABEL && (
|
||||
<ResolutionActionContainer>
|
||||
<ActionSelector
|
||||
|
|
@ -169,7 +169,7 @@ export function FinalActionSelector(props: FinalActionSelectorProps) {
|
|||
updateActions={props.updateActions}
|
||||
updateLabel={props.updateLabel}
|
||||
actions={props.actions}
|
||||
></ActionSelector>
|
||||
/>
|
||||
<ActionSelector
|
||||
allActions={allActions}
|
||||
actionTypeOptions={actionTypeOptions}
|
||||
|
|
@ -181,7 +181,7 @@ export function FinalActionSelector(props: FinalActionSelectorProps) {
|
|||
updateActions={props.updateActions}
|
||||
updateLabel={props.updateLabel}
|
||||
actions={props.actions}
|
||||
></ActionSelector>
|
||||
/>
|
||||
</ResolutionActionContainer>
|
||||
)}
|
||||
</ControlWrapper>
|
||||
|
|
@ -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}
|
||||
></FinalActionSelector>
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
></FinalActionSelector>
|
||||
/>
|
||||
<StyledDeleteIcon
|
||||
height={20}
|
||||
width={20}
|
||||
onClick={this.removeColumnAction.bind(this, index)}
|
||||
></StyledDeleteIcon>
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
@ -67,7 +68,7 @@ class ColumnActionSelectorControl extends BaseControl<
|
|||
color={"#FFFFFF"}
|
||||
minimal={true}
|
||||
onClick={this.addColumnAction}
|
||||
></StyledPropertyPaneButton>
|
||||
/>
|
||||
</ControlWrapper>
|
||||
);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -86,3 +86,8 @@ export interface PageAction {
|
|||
export interface TableAction extends BaseActionPayload {
|
||||
actionName: string;
|
||||
}
|
||||
|
||||
export interface ExecuteErrorPayload {
|
||||
actionId: string;
|
||||
error: any;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
2
app/client/src/constants/entityConstants.ts
Normal file
2
app/client/src/constants/entityConstants.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export const ENTITY_TYPE_ACTION = "ACTION";
|
||||
export const ENTITY_TYPE_WIDGET = "WIDGET";
|
||||
|
|
@ -88,15 +88,15 @@ class ApiSidebar extends React.Component<Props> {
|
|||
) {
|
||||
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<Props> {
|
|||
};
|
||||
|
||||
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<Props> {
|
|||
match: {
|
||||
params: { apiId },
|
||||
},
|
||||
actions: { data },
|
||||
actions,
|
||||
pluginId,
|
||||
} = this.props;
|
||||
if (!pluginId) return null;
|
||||
const data = actions.map(a => a.config);
|
||||
return (
|
||||
<EditorSidebar
|
||||
isLoading={isFetching}
|
||||
|
|
|
|||
|
|
@ -4,87 +4,139 @@ import {
|
|||
ReduxAction,
|
||||
ReduxActionErrorTypes,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { RestAction } from "api/ActionAPI";
|
||||
import { ActionWidgetIdsMap } from "sagas/ActionWidgetMapSagas";
|
||||
import { ActionResponse, RestAction } from "api/ActionAPI";
|
||||
import { ActionPayload, ExecuteErrorPayload } from "constants/ActionConstants";
|
||||
import _ from "lodash";
|
||||
|
||||
const initialState: ActionDataState = {
|
||||
data: [],
|
||||
actionToWidgetIdMap: {},
|
||||
};
|
||||
|
||||
export interface ActionDataState {
|
||||
data: RestAction[];
|
||||
actionToWidgetIdMap: ActionWidgetIdsMap;
|
||||
interface ActionData {
|
||||
isLoading: boolean;
|
||||
config: RestAction;
|
||||
data?: ActionResponse;
|
||||
}
|
||||
export type ActionDataState = ActionData[];
|
||||
|
||||
const initialState: ActionDataState = [];
|
||||
|
||||
const actionsReducer = createReducer(initialState, {
|
||||
[ReduxActionTypes.FETCH_ACTIONS_SUCCESS]: (
|
||||
state: ActionDataState,
|
||||
action: ReduxAction<RestAction[]>,
|
||||
) => ({
|
||||
...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<RestAction>,
|
||||
) => ({
|
||||
...state,
|
||||
data: state.data.concat([action.payload]),
|
||||
}),
|
||||
): ActionDataState =>
|
||||
state.concat([{ config: action.payload, isLoading: false }]),
|
||||
[ReduxActionTypes.CREATE_ACTION_SUCCESS]: (
|
||||
state: ActionDataState,
|
||||
action: ReduxAction<RestAction>,
|
||||
) => ({
|
||||
...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<RestAction>,
|
||||
) => ({
|
||||
...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<ActionWidgetIdsMap>,
|
||||
) => ({
|
||||
...state,
|
||||
actionToWidgetIdMap: action.payload,
|
||||
}),
|
||||
action: ReduxAction<ActionPayload[]>,
|
||||
): 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<ExecuteErrorPayload>,
|
||||
): 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<string>,
|
||||
): 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<RestAction>,
|
||||
) => ({
|
||||
...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<RestAction>,
|
||||
) => ({
|
||||
...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;
|
||||
|
|
|
|||
|
|
@ -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<string, ActionResponse>;
|
||||
|
||||
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;
|
||||
|
|
@ -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<NamePathBindingMap>,
|
||||
) => action.payload,
|
||||
});
|
||||
|
||||
export default bindingsReducer;
|
||||
|
|
@ -28,19 +28,6 @@ const canvasWidgetsReducer = createReducer(initialState, {
|
|||
) => {
|
||||
return { ...action.payload.widgets };
|
||||
},
|
||||
[ReduxActionTypes.WIDGETS_LOADING]: (
|
||||
state: CanvasWidgetsReduxState,
|
||||
action: ReduxAction<WidgetLoadingState>,
|
||||
) => {
|
||||
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<UpdateWidgetPropertyPayload>,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<ActionPayload[]>) {
|
|||
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<string>) {
|
|||
|
||||
function* executePageLoadActionsSaga(action: ReduxAction<PageAction[][]>) {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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<string, string[]>;
|
||||
|
||||
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<UpdateWidgetPropertyPayload>) {
|
||||
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)]);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<string, string> = {};
|
||||
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,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
|
@ -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)),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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<string, ActionResponse | undefined> => {
|
||||
const responses: Record<string, ActionResponse | undefined> = {};
|
||||
state.entities.actions.forEach(a => {
|
||||
responses[a.config.id] = a.data;
|
||||
});
|
||||
return responses;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<string, object>;
|
||||
export type NameBindingsWithData = { [key: string]: any };
|
||||
|
||||
export const getNameBindingsWithData = createSelector(
|
||||
getDataTree,
|
||||
(dataTree: DataTree): NameBindingsWithData => {
|
||||
const nameBindingsWithData: Record<string, object> = {};
|
||||
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<string, any> = {};
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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<string> => {
|
||||
const entityDeps: Record<string, string[]> = dependencyMap
|
||||
.map(d => [d[1].split(".")[0], d[0].split(".")[0]])
|
||||
.filter(d => d[0] !== d[1])
|
||||
.reduce((deps: Record<string, string[]>, 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<string>,
|
||||
deps: Record<string, string[]>,
|
||||
): Array<string> => {
|
||||
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]>,
|
||||
|
|
|
|||
|
|
@ -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<DataTree> = {
|
||||
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<AppState> = {
|
||||
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<DataTree> = {
|
||||
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<AppState> = {
|
||||
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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -256,7 +256,6 @@ export interface WidgetProps extends WidgetDataProps {
|
|||
key?: string;
|
||||
renderMode: RenderMode;
|
||||
dynamicBindings?: Record<string, boolean>;
|
||||
isLoading: boolean;
|
||||
invalidProps?: Record<string, boolean>;
|
||||
validationMessages?: Record<string, string>;
|
||||
[key: string]: any;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user