Introduce different evaluation types for different binding fields (#3834)
This commit is contained in:
parent
05e935b0a0
commit
c92eb0e5a4
|
|
@ -3,6 +3,8 @@ import {
|
||||||
ReduxActionTypes,
|
ReduxActionTypes,
|
||||||
ReduxAction,
|
ReduxAction,
|
||||||
ReduxActionErrorTypes,
|
ReduxActionErrorTypes,
|
||||||
|
EvaluationReduxAction,
|
||||||
|
ReduxActionWithoutPayload,
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
import { Action } from "entities/Action";
|
import { Action } from "entities/Action";
|
||||||
import { batchAction } from "actions/batchActions";
|
import { batchAction } from "actions/batchActions";
|
||||||
|
|
@ -27,10 +29,12 @@ export type FetchActionsPayload = {
|
||||||
|
|
||||||
export const fetchActions = (
|
export const fetchActions = (
|
||||||
applicationId: string,
|
applicationId: string,
|
||||||
): ReduxAction<FetchActionsPayload> => {
|
postEvalActions: Array<ReduxAction<unknown> | ReduxActionWithoutPayload>,
|
||||||
|
): EvaluationReduxAction<unknown> => {
|
||||||
return {
|
return {
|
||||||
type: ReduxActionTypes.FETCH_ACTIONS_INIT,
|
type: ReduxActionTypes.FETCH_ACTIONS_INIT,
|
||||||
payload: { applicationId },
|
payload: { applicationId },
|
||||||
|
postEvalActions,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import {
|
||||||
EvaluationReduxAction,
|
EvaluationReduxAction,
|
||||||
ReduxAction,
|
ReduxAction,
|
||||||
ReduxActionTypes,
|
ReduxActionTypes,
|
||||||
|
ReduxActionWithoutPayload,
|
||||||
UpdateCanvasPayload,
|
UpdateCanvasPayload,
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
|
|
@ -46,18 +47,14 @@ export const fetchPublishedPage = (pageId: string, bustCache = false) => ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const fetchPageSuccess = (
|
export const fetchPageSuccess = (): ReduxActionWithoutPayload => {
|
||||||
postEvalActions: ReduxAction<unknown>[],
|
|
||||||
): EvaluationReduxAction<unknown> => {
|
|
||||||
return {
|
return {
|
||||||
type: ReduxActionTypes.FETCH_PAGE_SUCCESS,
|
type: ReduxActionTypes.FETCH_PAGE_SUCCESS,
|
||||||
payload: {},
|
|
||||||
postEvalActions,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchPublishedPageSuccess = (
|
export const fetchPublishedPageSuccess = (
|
||||||
postEvalActions: ReduxAction<unknown>[],
|
postEvalActions: Array<ReduxAction<unknown> | ReduxActionWithoutPayload>,
|
||||||
): EvaluationReduxAction<undefined> => ({
|
): EvaluationReduxAction<undefined> => ({
|
||||||
type: ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS,
|
type: ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS,
|
||||||
postEvalActions,
|
postEvalActions,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import {
|
||||||
ReduxActionWithoutPayload,
|
ReduxActionWithoutPayload,
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
import { PluginFormPayload } from "api/PluginApi";
|
import { PluginFormPayload } from "api/PluginApi";
|
||||||
|
import { DependencyMap } from "utils/DynamicBindingUtils";
|
||||||
|
|
||||||
export const fetchPlugins = (): ReduxActionWithoutPayload => ({
|
export const fetchPlugins = (): ReduxActionWithoutPayload => ({
|
||||||
type: ReduxActionTypes.FETCH_PLUGINS_REQUEST,
|
type: ReduxActionTypes.FETCH_PLUGINS_REQUEST,
|
||||||
|
|
@ -17,6 +18,7 @@ export type PluginFormsPayload = {
|
||||||
formConfigs: Record<string, any[]>;
|
formConfigs: Record<string, any[]>;
|
||||||
editorConfigs: Record<string, any[]>;
|
editorConfigs: Record<string, any[]>;
|
||||||
settingConfigs: Record<string, any[]>;
|
settingConfigs: Record<string, any[]>;
|
||||||
|
dependencies: Record<string, DependencyMap>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchPluginFormConfigsSuccess = (
|
export const fetchPluginFormConfigsSuccess = (
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ import {
|
||||||
ReduxActionTypes,
|
ReduxActionTypes,
|
||||||
ReduxAction,
|
ReduxAction,
|
||||||
ReduxActionErrorTypes,
|
ReduxActionErrorTypes,
|
||||||
|
ReduxActionWithoutPayload,
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
import {
|
import {
|
||||||
ExecuteActionPayload,
|
ExecuteActionPayload,
|
||||||
ExecuteErrorPayload,
|
ExecuteErrorPayload,
|
||||||
PageAction,
|
|
||||||
} from "constants/AppsmithActionConstants/ActionConstants";
|
} from "constants/AppsmithActionConstants/ActionConstants";
|
||||||
import { BatchAction, batchAction } from "actions/batchActions";
|
import { BatchAction, batchAction } from "actions/batchActions";
|
||||||
import PerformanceTracker, {
|
import PerformanceTracker, {
|
||||||
|
|
@ -30,11 +30,8 @@ export const executeActionError = (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const executePageLoadActions = (
|
export const executePageLoadActions = (): ReduxActionWithoutPayload => ({
|
||||||
payload: PageAction[][],
|
|
||||||
): ReduxAction<PageAction[][]> => ({
|
|
||||||
type: ReduxActionTypes.EXECUTE_PAGE_LOAD_ACTIONS,
|
type: ReduxActionTypes.EXECUTE_PAGE_LOAD_ACTIONS,
|
||||||
payload,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const disableDragAction = (
|
export const disableDragAction = (
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import Api from "api/Api";
|
||||||
import { AxiosPromise } from "axios";
|
import { AxiosPromise } from "axios";
|
||||||
import { GenericApiResponse } from "api/ApiResponses";
|
import { GenericApiResponse } from "api/ApiResponses";
|
||||||
import { PluginType } from "entities/Action";
|
import { PluginType } from "entities/Action";
|
||||||
|
import { DependencyMap } from "utils/DynamicBindingUtils";
|
||||||
|
|
||||||
export interface Plugin {
|
export interface Plugin {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -21,6 +22,7 @@ export interface PluginFormPayload {
|
||||||
form: any[];
|
form: any[];
|
||||||
editor: any[];
|
editor: any[];
|
||||||
setting: any[];
|
setting: any[];
|
||||||
|
dependencies: DependencyMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PluginsApi extends Api {
|
class PluginsApi extends Api {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ import { theme } from "constants/DefaultTheme";
|
||||||
import { Placement } from "popper.js";
|
import { Placement } from "popper.js";
|
||||||
import ScrollIndicator from "components/ads/ScrollIndicator";
|
import ScrollIndicator from "components/ads/ScrollIndicator";
|
||||||
import DebugButton from "components/editorComponents/Debugger/DebugCTA";
|
import DebugButton from "components/editorComponents/Debugger/DebugCTA";
|
||||||
|
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||||
|
import Tooltip from "components/ads/Tooltip";
|
||||||
|
import { Classes } from "@blueprintjs/core";
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -112,6 +115,7 @@ interface Props {
|
||||||
error?: string;
|
error?: string;
|
||||||
useValidationMessage?: boolean;
|
useValidationMessage?: boolean;
|
||||||
hideEvaluatedValue?: boolean;
|
hideEvaluatedValue?: boolean;
|
||||||
|
evaluationSubstitutionType?: EvaluationSubstitutionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PopoverContentProps {
|
interface PopoverContentProps {
|
||||||
|
|
@ -124,12 +128,56 @@ interface PopoverContentProps {
|
||||||
onMouseEnter: () => void;
|
onMouseEnter: () => void;
|
||||||
onMouseLeave: () => void;
|
onMouseLeave: () => void;
|
||||||
hideEvaluatedValue?: boolean;
|
hideEvaluatedValue?: boolean;
|
||||||
|
preparedStatementViewer: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PreparedStatementViewerContainer = styled.span`
|
||||||
|
.${Classes.POPOVER_TARGET} {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const PreparedStatementParameter = styled.span`
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
color: #333;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type PreparedStatementValue = {
|
||||||
|
value: string;
|
||||||
|
parameters: Record<string, number | string>;
|
||||||
|
};
|
||||||
|
export const PreparedStatementViewer = (props: {
|
||||||
|
evaluatedValue: PreparedStatementValue;
|
||||||
|
}) => {
|
||||||
|
const { value, parameters } = props.evaluatedValue;
|
||||||
|
const stringSegments = value.split(/\$\d/);
|
||||||
|
const $params = [...value.matchAll(/\$\d/g)].map((matches) => matches[0]);
|
||||||
|
const paramsWithTooltips = $params.map((param) => (
|
||||||
|
<Tooltip content={<span>{parameters[param]}</span>} key={param}>
|
||||||
|
<PreparedStatementParameter key={param}>
|
||||||
|
{param}
|
||||||
|
</PreparedStatementParameter>
|
||||||
|
</Tooltip>
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PreparedStatementViewerContainer>
|
||||||
|
{stringSegments.map((segment, index) => (
|
||||||
|
<span key={segment}>
|
||||||
|
{segment}
|
||||||
|
{paramsWithTooltips[index]}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</PreparedStatementViewerContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const CurrentValueViewer = (props: {
|
export const CurrentValueViewer = (props: {
|
||||||
theme: EditorTheme;
|
theme: EditorTheme;
|
||||||
evaluatedValue: any;
|
evaluatedValue: any;
|
||||||
hideLabel?: boolean;
|
hideLabel?: boolean;
|
||||||
|
preparedStatementViewer?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const currentValueWrapperRef = React.createRef<HTMLDivElement>();
|
const currentValueWrapperRef = React.createRef<HTMLDivElement>();
|
||||||
const codeWrapperRef = React.createRef<HTMLPreElement>();
|
const codeWrapperRef = React.createRef<HTMLPreElement>();
|
||||||
|
|
@ -145,19 +193,31 @@ export const CurrentValueViewer = (props: {
|
||||||
_.isObject(props.evaluatedValue) ||
|
_.isObject(props.evaluatedValue) ||
|
||||||
Array.isArray(props.evaluatedValue)
|
Array.isArray(props.evaluatedValue)
|
||||||
) {
|
) {
|
||||||
const reactJsonProps = {
|
if (props.preparedStatementViewer) {
|
||||||
theme: props.theme === EditorTheme.DARK ? "summerfruit" : "rjv-default",
|
content = (
|
||||||
name: null,
|
<CodeWrapper colorTheme={props.theme} ref={codeWrapperRef}>
|
||||||
enableClipboard: false,
|
<PreparedStatementViewer
|
||||||
displayObjectSize: false,
|
evaluatedValue={props.evaluatedValue as PreparedStatementValue}
|
||||||
displayDataTypes: false,
|
/>
|
||||||
style: {
|
<ScrollIndicator containerRef={codeWrapperRef} />
|
||||||
fontSize: "12px",
|
</CodeWrapper>
|
||||||
},
|
);
|
||||||
collapsed: 2,
|
} else {
|
||||||
collapseStringsAfterLength: 20,
|
const reactJsonProps = {
|
||||||
};
|
theme:
|
||||||
content = <ReactJson src={props.evaluatedValue} {...reactJsonProps} />;
|
props.theme === EditorTheme.DARK ? "summerfruit" : "rjv-default",
|
||||||
|
name: null,
|
||||||
|
enableClipboard: false,
|
||||||
|
displayObjectSize: false,
|
||||||
|
displayDataTypes: false,
|
||||||
|
style: {
|
||||||
|
fontSize: "12px",
|
||||||
|
},
|
||||||
|
collapsed: 2,
|
||||||
|
collapseStringsAfterLength: 20,
|
||||||
|
};
|
||||||
|
content = <ReactJson src={props.evaluatedValue} {...reactJsonProps} />;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
content = (
|
content = (
|
||||||
<CodeWrapper colorTheme={props.theme} ref={codeWrapperRef}>
|
<CodeWrapper colorTheme={props.theme} ref={codeWrapperRef}>
|
||||||
|
|
@ -222,6 +282,7 @@ const PopoverContent = (props: PopoverContentProps) => {
|
||||||
<CurrentValueViewer
|
<CurrentValueViewer
|
||||||
theme={props.theme}
|
theme={props.theme}
|
||||||
evaluatedValue={props.evaluatedValue}
|
evaluatedValue={props.evaluatedValue}
|
||||||
|
preparedStatementViewer={props.preparedStatementViewer}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
|
|
@ -246,7 +307,7 @@ const EvaluatedValuePopup = (props: Props) => {
|
||||||
<Popper
|
<Popper
|
||||||
targetNode={wrapperRef.current || undefined}
|
targetNode={wrapperRef.current || undefined}
|
||||||
isOpen
|
isOpen
|
||||||
zIndex={15}
|
zIndex={5}
|
||||||
placement={placement}
|
placement={placement}
|
||||||
modifiers={{
|
modifiers={{
|
||||||
offset: {
|
offset: {
|
||||||
|
|
@ -263,6 +324,12 @@ const EvaluatedValuePopup = (props: Props) => {
|
||||||
hasError={props.hasError}
|
hasError={props.hasError}
|
||||||
theme={props.theme}
|
theme={props.theme}
|
||||||
hideEvaluatedValue={props.hideEvaluatedValue}
|
hideEvaluatedValue={props.hideEvaluatedValue}
|
||||||
|
preparedStatementViewer={
|
||||||
|
props.evaluationSubstitutionType
|
||||||
|
? props.evaluationSubstitutionType ===
|
||||||
|
EvaluationSubstitutionType.PARAMETER
|
||||||
|
: false
|
||||||
|
}
|
||||||
onMouseLeave={() => {
|
onMouseLeave={() => {
|
||||||
setContentHovered(false);
|
setContentHovered(false);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,10 @@ import { getDataTreeForAutocomplete } from "selectors/dataTreeSelectors";
|
||||||
import EvaluatedValuePopup from "components/editorComponents/CodeEditor/EvaluatedValuePopup";
|
import EvaluatedValuePopup from "components/editorComponents/CodeEditor/EvaluatedValuePopup";
|
||||||
import { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form";
|
import { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
import {
|
||||||
|
DataTree,
|
||||||
|
EvaluationSubstitutionType,
|
||||||
|
} from "entities/DataTree/dataTreeFactory";
|
||||||
import { Skin } from "constants/DefaultTheme";
|
import { Skin } from "constants/DefaultTheme";
|
||||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
import "components/editorComponents/CodeEditor/modes";
|
import "components/editorComponents/CodeEditor/modes";
|
||||||
|
|
@ -84,6 +87,7 @@ export type EditorStyleProps = {
|
||||||
hoverInteraction?: boolean;
|
hoverInteraction?: boolean;
|
||||||
fill?: boolean;
|
fill?: boolean;
|
||||||
useValidationMessage?: boolean;
|
useValidationMessage?: boolean;
|
||||||
|
evaluationSubstitutionType?: EvaluationSubstitutionType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EditorProps = EditorStyleProps &
|
export type EditorProps = EditorStyleProps &
|
||||||
|
|
@ -352,6 +356,7 @@ class CodeEditor extends Component<Props, State> {
|
||||||
fill,
|
fill,
|
||||||
useValidationMessage,
|
useValidationMessage,
|
||||||
hideEvaluatedValue,
|
hideEvaluatedValue,
|
||||||
|
evaluationSubstitutionType,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const hasError = !!(meta && meta.error);
|
const hasError = !!(meta && meta.error);
|
||||||
let evaluated = evaluatedValue;
|
let evaluated = evaluatedValue;
|
||||||
|
|
@ -398,6 +403,7 @@ class CodeEditor extends Component<Props, State> {
|
||||||
error={meta?.error}
|
error={meta?.error}
|
||||||
useValidationMessage={useValidationMessage}
|
useValidationMessage={useValidationMessage}
|
||||||
hideEvaluatedValue={hideEvaluatedValue}
|
hideEvaluatedValue={hideEvaluatedValue}
|
||||||
|
evaluationSubstitutionType={evaluationSubstitutionType}
|
||||||
>
|
>
|
||||||
<EditorWrapper
|
<EditorWrapper
|
||||||
editorTheme={this.props.theme}
|
editorTheme={this.props.theme}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { mockCodemirrorRender } from "test/__mocks__/CodeMirrorEditorMock";
|
||||||
import { PluginType } from "entities/Action";
|
import { PluginType } from "entities/Action";
|
||||||
import { waitFor } from "@testing-library/dom";
|
import { waitFor } from "@testing-library/dom";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||||
|
|
||||||
const TestForm = (props: any) => <div>{props.children}</div>;
|
const TestForm = (props: any) => <div>{props.children}</div>;
|
||||||
|
|
||||||
|
|
@ -33,6 +34,7 @@ describe("DynamicTextFieldControl", () => {
|
||||||
id={"test"}
|
id={"test"}
|
||||||
isValid={true}
|
isValid={true}
|
||||||
pluginId="123"
|
pluginId="123"
|
||||||
|
evaluationSubstitutionType={EvaluationSubstitutionType.TEMPLATE}
|
||||||
/>
|
/>
|
||||||
</ReduxFormDecorator>,
|
</ReduxFormDecorator>,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import {
|
||||||
getQueryParams,
|
getQueryParams,
|
||||||
} from "utils/AppsmithUtils";
|
} from "utils/AppsmithUtils";
|
||||||
import { actionPathFromName } from "components/formControls/utils";
|
import { actionPathFromName } from "components/formControls/utils";
|
||||||
|
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
.dynamic-text-field {
|
.dynamic-text-field {
|
||||||
|
|
@ -64,6 +65,7 @@ class DynamicTextControl extends BaseControl<
|
||||||
placeholderText,
|
placeholderText,
|
||||||
actionName,
|
actionName,
|
||||||
configProperty,
|
configProperty,
|
||||||
|
evaluationSubstitutionType,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const dataTreePath = actionPathFromName(actionName, configProperty);
|
const dataTreePath = actionPathFromName(actionName, configProperty);
|
||||||
const isNewQuery =
|
const isNewQuery =
|
||||||
|
|
@ -104,6 +106,7 @@ class DynamicTextControl extends BaseControl<
|
||||||
mode={mode}
|
mode={mode}
|
||||||
tabBehaviour={TabBehaviour.INDENT}
|
tabBehaviour={TabBehaviour.INDENT}
|
||||||
placeholder={placeholderText}
|
placeholder={placeholderText}
|
||||||
|
evaluationSubstitutionType={evaluationSubstitutionType}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|
@ -117,6 +120,7 @@ export interface DynamicTextFieldProps extends ControlProps {
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
responseType: string;
|
responseType: string;
|
||||||
placeholderText?: string;
|
placeholderText?: string;
|
||||||
|
evaluationSubstitutionType: EvaluationSubstitutionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: AppState, props: DynamicTextFieldProps) => {
|
const mapStateToProps = (state: AppState, props: DynamicTextFieldProps) => {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,13 @@ export const DEFAULT_API_ACTION_CONFIG: ApiActionConfig = {
|
||||||
httpMethod: HTTP_METHODS[0],
|
httpMethod: HTTP_METHODS[0],
|
||||||
headers: EMPTY_KEY_VALUE_PAIRS.slice(),
|
headers: EMPTY_KEY_VALUE_PAIRS.slice(),
|
||||||
queryParameters: EMPTY_KEY_VALUE_PAIRS.slice(),
|
queryParameters: EMPTY_KEY_VALUE_PAIRS.slice(),
|
||||||
|
body: "",
|
||||||
|
pluginSpecifiedTemplates: [
|
||||||
|
{
|
||||||
|
// JSON smart substitution
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_PROVIDER_OPTION = "Business Software";
|
export const DEFAULT_PROVIDER_OPTION = "Business Software";
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import queryActionSettingsConfig from "constants/AppsmithActionConstants/formCon
|
||||||
import apiActionSettingsConfig from "constants/AppsmithActionConstants/formConfig/ApiSettingsConfig";
|
import apiActionSettingsConfig from "constants/AppsmithActionConstants/formConfig/ApiSettingsConfig";
|
||||||
import apiActionEditorConfig from "constants/AppsmithActionConstants/formConfig/ApiEditorConfigs";
|
import apiActionEditorConfig from "constants/AppsmithActionConstants/formConfig/ApiEditorConfigs";
|
||||||
import saasActionSettingsConfig from "constants/AppsmithActionConstants/formConfig/GoogleSheetsSettingsConfig";
|
import saasActionSettingsConfig from "constants/AppsmithActionConstants/formConfig/GoogleSheetsSettingsConfig";
|
||||||
|
import apiActionDependencyConfig from "constants/AppsmithActionConstants/formConfig/ApiDependencyConfigs";
|
||||||
|
|
||||||
export type ExecuteActionPayloadEvent = {
|
export type ExecuteActionPayloadEvent = {
|
||||||
type: EventType;
|
type: EventType;
|
||||||
|
|
@ -123,3 +124,12 @@ export const defaultActionEditorConfigs: Record<PluginType, any> = {
|
||||||
[PluginType.DB]: [],
|
[PluginType.DB]: [],
|
||||||
[PluginType.SAAS]: [],
|
[PluginType.SAAS]: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const defaultActionDependenciesConfig: Record<
|
||||||
|
PluginType,
|
||||||
|
Record<string, string[]>
|
||||||
|
> = {
|
||||||
|
[PluginType.API]: apiActionDependencyConfig,
|
||||||
|
[PluginType.DB]: {},
|
||||||
|
[PluginType.SAAS]: {},
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
export default [
|
export default {
|
||||||
{
|
"actionConfiguration.body": [
|
||||||
dependencies: {
|
"actionConfiguration.pluginSpecifiedTemplates[0].value",
|
||||||
"actionConfiguration.body": [
|
],
|
||||||
"actionConfiguration.pluginSpecifiedTemplates[0].value",
|
};
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,23 @@ export default [
|
||||||
label: "Body",
|
label: "Body",
|
||||||
configProperty: "actionConfiguration.body",
|
configProperty: "actionConfiguration.body",
|
||||||
controlType: "QUERY_DYNAMIC_INPUT_TEXT",
|
controlType: "QUERY_DYNAMIC_INPUT_TEXT",
|
||||||
|
evaluationSubstitutionType: "SMART_SUBSTITUTE",
|
||||||
|
hidden: {
|
||||||
|
path: "actionConfiguration.pluginSpecifiedTemplates[0].value",
|
||||||
|
comparison: "EQUALS",
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Body",
|
||||||
|
configProperty: "actionConfiguration.body",
|
||||||
|
controlType: "QUERY_DYNAMIC_INPUT_TEXT",
|
||||||
|
evaluationSubstitutionType: "TEMPLATE",
|
||||||
|
hidden: {
|
||||||
|
path: "actionConfiguration.pluginSpecifiedTemplates[0].value",
|
||||||
|
comparison: "EQUALS",
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Query Parameters",
|
label: "Query Parameters",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
export const DATA_BIND_REGEX = /{{([\s\S]*?)}}/;
|
export const DATA_BIND_REGEX = /{{([\s\S]*?)}}/;
|
||||||
export const DATA_BIND_REGEX_GLOBAL = /{{([\s\S]*?)}}/g;
|
export const DATA_BIND_REGEX_GLOBAL = /{{([\s\S]*?)}}/g;
|
||||||
export const AUTOCOMPLETE_MATCH_REGEX = /{{\s*.*?\s*}}/g;
|
export const AUTOCOMPLETE_MATCH_REGEX = /{{\s*.*?\s*}}/g;
|
||||||
|
export const QUOTED_BINDING_REGEX = /["']({{[\s\S]*?}})["']/g;
|
||||||
|
|
|
||||||
|
|
@ -497,7 +497,7 @@ export interface ReduxActionWithCallbacks<T, S, E> extends ReduxAction<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EvaluationReduxAction<T> extends ReduxAction<T> {
|
export interface EvaluationReduxAction<T> extends ReduxAction<T> {
|
||||||
postEvalActions?: ReduxAction<any>[];
|
postEvalActions?: Array<ReduxAction<any> | ReduxActionWithoutPayload>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PromisePayload {
|
export interface PromisePayload {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Action, PluginType } from "entities/Action/index";
|
import { Action, PluginType } from "entities/Action/index";
|
||||||
import { getBindingPathsOfAction } from "entities/Action/actionProperties";
|
import { getBindingPathsOfAction } from "entities/Action/actionProperties";
|
||||||
|
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||||
|
|
||||||
const DEFAULT_ACTION: Action = {
|
const DEFAULT_ACTION: Action = {
|
||||||
actionConfiguration: {},
|
actionConfiguration: {},
|
||||||
|
|
@ -24,9 +25,9 @@ describe("getBindingPathsOfAction", () => {
|
||||||
it("returns default list of no config is sent", () => {
|
it("returns default list of no config is sent", () => {
|
||||||
const response = getBindingPathsOfAction(DEFAULT_ACTION, undefined);
|
const response = getBindingPathsOfAction(DEFAULT_ACTION, undefined);
|
||||||
expect(response).toStrictEqual({
|
expect(response).toStrictEqual({
|
||||||
data: true,
|
data: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isLoading: true,
|
isLoading: EvaluationSubstitutionType.TEMPLATE,
|
||||||
config: true,
|
config: EvaluationSubstitutionType.TEMPLATE,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -46,6 +47,18 @@ describe("getBindingPathsOfAction", () => {
|
||||||
configProperty: "actionConfiguration.body2",
|
configProperty: "actionConfiguration.body2",
|
||||||
controlType: "QUERY_DYNAMIC_INPUT_TEXT",
|
controlType: "QUERY_DYNAMIC_INPUT_TEXT",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "",
|
||||||
|
configProperty: "actionConfiguration.field1",
|
||||||
|
controlType: "QUERY_DYNAMIC_INPUT_TEXT",
|
||||||
|
evaluationSubstitutionType: "SMART_SUBSTITUTE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "",
|
||||||
|
configProperty: "actionConfiguration.field2",
|
||||||
|
controlType: "QUERY_DYNAMIC_INPUT_TEXT",
|
||||||
|
evaluationSubstitutionType: "PARAMETER",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -54,15 +67,19 @@ describe("getBindingPathsOfAction", () => {
|
||||||
actionConfiguration: {
|
actionConfiguration: {
|
||||||
body: "basic action",
|
body: "basic action",
|
||||||
body2: "another body",
|
body2: "another body",
|
||||||
|
field1: "test",
|
||||||
|
field2: "anotherTest",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = getBindingPathsOfAction(basicAction, config);
|
const response = getBindingPathsOfAction(basicAction, config);
|
||||||
expect(response).toStrictEqual({
|
expect(response).toStrictEqual({
|
||||||
data: true,
|
data: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isLoading: true,
|
isLoading: EvaluationSubstitutionType.TEMPLATE,
|
||||||
"config.body": true,
|
"config.body": EvaluationSubstitutionType.TEMPLATE,
|
||||||
"config.body2": true,
|
"config.body2": EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
"config.field1": EvaluationSubstitutionType.SMART_SUBSTITUTE,
|
||||||
|
"config.field2": EvaluationSubstitutionType.PARAMETER,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -122,12 +139,12 @@ describe("getBindingPathsOfAction", () => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const response = getBindingPathsOfAction(basicAction, config);
|
const response = getBindingPathsOfAction(basicAction, config);
|
||||||
expect(response).toStrictEqual({
|
expect(response).toStrictEqual({
|
||||||
data: true,
|
data: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isLoading: true,
|
isLoading: EvaluationSubstitutionType.TEMPLATE,
|
||||||
"config.params[0].key": true,
|
"config.params[0].key": EvaluationSubstitutionType.TEMPLATE,
|
||||||
"config.params[0].value": true,
|
"config.params[0].value": EvaluationSubstitutionType.TEMPLATE,
|
||||||
"config.params[1].key": true,
|
"config.params[1].key": EvaluationSubstitutionType.TEMPLATE,
|
||||||
"config.params[1].value": true,
|
"config.params[1].value": EvaluationSubstitutionType.TEMPLATE,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -177,10 +194,76 @@ describe("getBindingPathsOfAction", () => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const response = getBindingPathsOfAction(basicAction, config);
|
const response = getBindingPathsOfAction(basicAction, config);
|
||||||
expect(response).toStrictEqual({
|
expect(response).toStrictEqual({
|
||||||
data: true,
|
data: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isLoading: true,
|
isLoading: EvaluationSubstitutionType.TEMPLATE,
|
||||||
"config.key": true,
|
"config.key": EvaluationSubstitutionType.TEMPLATE,
|
||||||
"config.value": true,
|
"config.value": EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("checks for hidden field and returns bindingPaths accordingly", () => {
|
||||||
|
const config = [
|
||||||
|
{
|
||||||
|
sectionName: "",
|
||||||
|
id: 1,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: "",
|
||||||
|
configProperty: "actionConfiguration.body",
|
||||||
|
controlType: "QUERY_DYNAMIC_TEXT",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "",
|
||||||
|
configProperty: "actionConfiguration.body2",
|
||||||
|
controlType: "QUERY_DYNAMIC_INPUT_TEXT",
|
||||||
|
hidden: {
|
||||||
|
path: "actionConfiguration.template.setting",
|
||||||
|
comparison: "EQUALS",
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "",
|
||||||
|
configProperty: "actionConfiguration.field1",
|
||||||
|
controlType: "QUERY_DYNAMIC_INPUT_TEXT",
|
||||||
|
evaluationSubstitutionType: "SMART_SUBSTITUTE",
|
||||||
|
hidden: {
|
||||||
|
path: "actionConfiguration.template.setting",
|
||||||
|
comparison: "EQUALS",
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const basicAction = {
|
||||||
|
...DEFAULT_ACTION,
|
||||||
|
actionConfiguration: {
|
||||||
|
body: "basic action",
|
||||||
|
body2: "another body",
|
||||||
|
field1: "alternate body",
|
||||||
|
template: {
|
||||||
|
setting: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = getBindingPathsOfAction(basicAction, config);
|
||||||
|
expect(response).toStrictEqual({
|
||||||
|
data: EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
isLoading: EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
"config.body": EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
"config.field1": EvaluationSubstitutionType.SMART_SUBSTITUTE,
|
||||||
|
});
|
||||||
|
|
||||||
|
basicAction.actionConfiguration.template.setting = true;
|
||||||
|
|
||||||
|
const response2 = getBindingPathsOfAction(basicAction, config);
|
||||||
|
expect(response2).toStrictEqual({
|
||||||
|
data: EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
isLoading: EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
"config.body": EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
"config.body2": EvaluationSubstitutionType.TEMPLATE,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,46 @@
|
||||||
import { Action } from "entities/Action/index";
|
import { Action } from "entities/Action/index";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||||
|
import { isHidden } from "components/formControls/utils";
|
||||||
|
|
||||||
const dynamicFields = ["QUERY_DYNAMIC_TEXT", "QUERY_DYNAMIC_INPUT_TEXT"];
|
const dynamicFields = ["QUERY_DYNAMIC_TEXT", "QUERY_DYNAMIC_INPUT_TEXT"];
|
||||||
|
|
||||||
|
const getCorrectEvaluationSubstitutionType = (substitutionType?: string) => {
|
||||||
|
if (substitutionType) {
|
||||||
|
if (substitutionType === EvaluationSubstitutionType.SMART_SUBSTITUTE) {
|
||||||
|
return EvaluationSubstitutionType.SMART_SUBSTITUTE;
|
||||||
|
} else if (substitutionType === EvaluationSubstitutionType.PARAMETER) {
|
||||||
|
return EvaluationSubstitutionType.PARAMETER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return EvaluationSubstitutionType.TEMPLATE;
|
||||||
|
};
|
||||||
|
|
||||||
export const getBindingPathsOfAction = (
|
export const getBindingPathsOfAction = (
|
||||||
action: Action,
|
action: Action,
|
||||||
formConfig?: any[],
|
formConfig?: any[],
|
||||||
): Record<string, true> => {
|
): Record<string, EvaluationSubstitutionType> => {
|
||||||
const bindingPaths: Record<string, true> = {
|
const bindingPaths: Record<string, EvaluationSubstitutionType> = {
|
||||||
data: true,
|
data: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isLoading: true,
|
isLoading: EvaluationSubstitutionType.TEMPLATE,
|
||||||
};
|
};
|
||||||
if (!formConfig) {
|
if (!formConfig) {
|
||||||
return {
|
return {
|
||||||
...bindingPaths,
|
...bindingPaths,
|
||||||
config: true,
|
config: EvaluationSubstitutionType.TEMPLATE,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const recursiveFindBindingPaths = (formConfig: any) => {
|
const recursiveFindBindingPaths = (formConfig: any) => {
|
||||||
if (formConfig.children) {
|
if (formConfig.children) {
|
||||||
formConfig.children.forEach(recursiveFindBindingPaths);
|
formConfig.children.forEach(recursiveFindBindingPaths);
|
||||||
} else {
|
} else {
|
||||||
const configPath = formConfig.configProperty.replace(
|
const configPath = getDataTreeActionConfigPath(formConfig.configProperty);
|
||||||
"actionConfiguration.",
|
|
||||||
"config.",
|
|
||||||
);
|
|
||||||
if (dynamicFields.includes(formConfig.controlType)) {
|
if (dynamicFields.includes(formConfig.controlType)) {
|
||||||
bindingPaths[configPath] = true;
|
if (!isHidden(action, formConfig.hidden)) {
|
||||||
|
bindingPaths[configPath] = getCorrectEvaluationSubstitutionType(
|
||||||
|
formConfig.evaluationSubstitutionType,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (formConfig.controlType === "ARRAY_FIELD") {
|
if (formConfig.controlType === "ARRAY_FIELD") {
|
||||||
const actionValue = _.get(action, formConfig.configProperty);
|
const actionValue = _.get(action, formConfig.configProperty);
|
||||||
|
|
@ -38,7 +52,11 @@ export const getBindingPathsOfAction = (
|
||||||
dynamicFields.includes(schemaField.controlType)
|
dynamicFields.includes(schemaField.controlType)
|
||||||
) {
|
) {
|
||||||
const arrayConfigPath = `${configPath}[${i}].${schemaField.key}`;
|
const arrayConfigPath = `${configPath}[${i}].${schemaField.key}`;
|
||||||
bindingPaths[arrayConfigPath] = true;
|
bindingPaths[
|
||||||
|
arrayConfigPath
|
||||||
|
] = getCorrectEvaluationSubstitutionType(
|
||||||
|
formConfig.evaluationSubstitutionType,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -51,3 +69,6 @@ export const getBindingPathsOfAction = (
|
||||||
|
|
||||||
return bindingPaths;
|
return bindingPaths;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getDataTreeActionConfigPath = (propertyPath: string) =>
|
||||||
|
propertyPath.replace("actionConfiguration.", "config.");
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ export enum PaginationType {
|
||||||
export interface ActionConfig {
|
export interface ActionConfig {
|
||||||
timeoutInMillisecond?: number;
|
timeoutInMillisecond?: number;
|
||||||
paginationType?: PaginationType;
|
paginationType?: PaginationType;
|
||||||
|
pluginSpecifiedTemplates?: Array<{ key?: string; value?: unknown }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ActionProvider {
|
export interface ActionProvider {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import { DynamicPath } from "utils/DynamicBindingUtils";
|
import { DependencyMap, DynamicPath } from "utils/DynamicBindingUtils";
|
||||||
import { DataTreeAction, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
import { DataTreeAction, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
||||||
import { ActionData } from "reducers/entityReducers/actionsReducer";
|
import { ActionData } from "reducers/entityReducers/actionsReducer";
|
||||||
import { getBindingPathsOfAction } from "entities/Action/actionProperties";
|
import {
|
||||||
|
getBindingPathsOfAction,
|
||||||
|
getDataTreeActionConfigPath,
|
||||||
|
} from "entities/Action/actionProperties";
|
||||||
|
|
||||||
export const generateDataTreeAction = (
|
export const generateDataTreeAction = (
|
||||||
action: ActionData,
|
action: ActionData,
|
||||||
editorConfig: any[],
|
editorConfig: any[],
|
||||||
|
dependencyConfig: DependencyMap = {},
|
||||||
): DataTreeAction => {
|
): DataTreeAction => {
|
||||||
let dynamicBindingPathList: DynamicPath[] = [];
|
let dynamicBindingPathList: DynamicPath[] = [];
|
||||||
// update paths
|
// update paths
|
||||||
|
|
@ -18,6 +22,12 @@ export const generateDataTreeAction = (
|
||||||
key: `config.${d.key}`,
|
key: `config.${d.key}`,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
const dependencyMap: DependencyMap = {};
|
||||||
|
Object.entries(dependencyConfig).forEach(([dependent, dependencies]) => {
|
||||||
|
dependencyMap[getDataTreeActionConfigPath(dependent)] = dependencies.map(
|
||||||
|
getDataTreeActionConfigPath,
|
||||||
|
);
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
run: {},
|
run: {},
|
||||||
actionId: action.config.id,
|
actionId: action.config.id,
|
||||||
|
|
@ -29,5 +39,6 @@ export const generateDataTreeAction = (
|
||||||
ENTITY_TYPE: ENTITY_TYPE.ACTION,
|
ENTITY_TYPE: ENTITY_TYPE.ACTION,
|
||||||
isLoading: action.isLoading,
|
isLoading: action.isLoading,
|
||||||
bindingPaths: getBindingPathsOfAction(action.config, editorConfig),
|
bindingPaths: getBindingPathsOfAction(action.config, editorConfig),
|
||||||
|
dependencyMap,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { MetaState } from "reducers/entityReducers/metaReducer";
|
||||||
import { PageListPayload } from "constants/ReduxActionConstants";
|
import { PageListPayload } from "constants/ReduxActionConstants";
|
||||||
import { ActionConfig, PluginType } from "entities/Action";
|
import { ActionConfig, PluginType } from "entities/Action";
|
||||||
import { AppDataState } from "reducers/entityReducers/appReducer";
|
import { AppDataState } from "reducers/entityReducers/appReducer";
|
||||||
import { DynamicPath } from "utils/DynamicBindingUtils";
|
import { DependencyMap, DynamicPath } from "utils/DynamicBindingUtils";
|
||||||
import { generateDataTreeAction } from "entities/DataTree/dataTreeAction";
|
import { generateDataTreeAction } from "entities/DataTree/dataTreeAction";
|
||||||
import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget";
|
import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget";
|
||||||
import { VALIDATION_TYPES } from "constants/WidgetValidation";
|
import { VALIDATION_TYPES } from "constants/WidgetValidation";
|
||||||
|
|
@ -36,6 +36,12 @@ export type RunActionPayload = {
|
||||||
params: Record<string, any> | string;
|
params: Record<string, any> | string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum EvaluationSubstitutionType {
|
||||||
|
TEMPLATE = "TEMPLATE",
|
||||||
|
PARAMETER = "PARAMETER",
|
||||||
|
SMART_SUBSTITUTE = "SMART_SUBSTITUTE",
|
||||||
|
}
|
||||||
|
|
||||||
export interface DataTreeAction extends Omit<ActionData, "data" | "config"> {
|
export interface DataTreeAction extends Omit<ActionData, "data" | "config"> {
|
||||||
data: ActionResponse["body"];
|
data: ActionResponse["body"];
|
||||||
actionId: string;
|
actionId: string;
|
||||||
|
|
@ -46,12 +52,13 @@ export interface DataTreeAction extends Omit<ActionData, "data" | "config"> {
|
||||||
| ActionDispatcher<RunActionPayload, [string, string, string]>
|
| ActionDispatcher<RunActionPayload, [string, string, string]>
|
||||||
| Record<string, any>;
|
| Record<string, any>;
|
||||||
dynamicBindingPathList: DynamicPath[];
|
dynamicBindingPathList: DynamicPath[];
|
||||||
bindingPaths: Record<string, boolean>;
|
bindingPaths: Record<string, EvaluationSubstitutionType>;
|
||||||
ENTITY_TYPE: ENTITY_TYPE.ACTION;
|
ENTITY_TYPE: ENTITY_TYPE.ACTION;
|
||||||
|
dependencyMap: DependencyMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataTreeWidget extends WidgetProps {
|
export interface DataTreeWidget extends WidgetProps {
|
||||||
bindingPaths: Record<string, boolean>;
|
bindingPaths: Record<string, EvaluationSubstitutionType>;
|
||||||
triggerPaths: Record<string, boolean>;
|
triggerPaths: Record<string, boolean>;
|
||||||
validationPaths: Record<string, VALIDATION_TYPES>;
|
validationPaths: Record<string, VALIDATION_TYPES>;
|
||||||
ENTITY_TYPE: ENTITY_TYPE.WIDGET;
|
ENTITY_TYPE: ENTITY_TYPE.WIDGET;
|
||||||
|
|
@ -79,6 +86,7 @@ export type DataTree = {
|
||||||
type DataTreeSeed = {
|
type DataTreeSeed = {
|
||||||
actions: ActionDataState;
|
actions: ActionDataState;
|
||||||
editorConfigs: Record<string, any[]>;
|
editorConfigs: Record<string, any[]>;
|
||||||
|
pluginDependencyConfig: Record<string, DependencyMap>;
|
||||||
widgets: CanvasWidgetsReduxState;
|
widgets: CanvasWidgetsReduxState;
|
||||||
widgetsMeta: MetaState;
|
widgetsMeta: MetaState;
|
||||||
pageList: PageListPayload;
|
pageList: PageListPayload;
|
||||||
|
|
@ -93,13 +101,16 @@ export class DataTreeFactory {
|
||||||
pageList,
|
pageList,
|
||||||
appData,
|
appData,
|
||||||
editorConfigs,
|
editorConfigs,
|
||||||
|
pluginDependencyConfig,
|
||||||
}: DataTreeSeed): DataTree {
|
}: DataTreeSeed): DataTree {
|
||||||
const dataTree: DataTree = {};
|
const dataTree: DataTree = {};
|
||||||
actions.forEach((action) => {
|
actions.forEach((action) => {
|
||||||
const editorConfig = editorConfigs[action.config.pluginId];
|
const editorConfig = editorConfigs[action.config.pluginId];
|
||||||
|
const dependencyConfig = pluginDependencyConfig[action.config.pluginId];
|
||||||
dataTree[action.config.name] = generateDataTreeAction(
|
dataTree[action.config.name] = generateDataTreeAction(
|
||||||
action,
|
action,
|
||||||
editorConfig,
|
editorConfig,
|
||||||
|
dependencyConfig,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
Object.values(widgets).forEach((widget) => {
|
Object.values(widgets).forEach((widget) => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
|
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||||
import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget";
|
import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget";
|
||||||
import { DataTreeWidget, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
import {
|
||||||
|
DataTreeWidget,
|
||||||
|
ENTITY_TYPE,
|
||||||
|
EvaluationSubstitutionType,
|
||||||
|
} from "entities/DataTree/dataTreeFactory";
|
||||||
import { RenderModes, WidgetTypes } from "constants/WidgetConstants";
|
import { RenderModes, WidgetTypes } from "constants/WidgetConstants";
|
||||||
import WidgetFactory from "utils/WidgetFactory";
|
import WidgetFactory from "utils/WidgetFactory";
|
||||||
import { VALIDATION_TYPES } from "constants/WidgetValidation";
|
import { VALIDATION_TYPES } from "constants/WidgetValidation";
|
||||||
|
|
@ -181,19 +185,19 @@ describe("generateDataTreeWidget", () => {
|
||||||
|
|
||||||
const expected: DataTreeWidget = {
|
const expected: DataTreeWidget = {
|
||||||
bindingPaths: {
|
bindingPaths: {
|
||||||
defaultText: true,
|
defaultText: EvaluationSubstitutionType.TEMPLATE,
|
||||||
errorMessage: true,
|
errorMessage: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isDirty: true,
|
isDirty: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isDisabled: true,
|
isDisabled: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isFocused: true,
|
isFocused: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isRequired: true,
|
isRequired: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isValid: true,
|
isValid: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isVisible: true,
|
isVisible: EvaluationSubstitutionType.TEMPLATE,
|
||||||
placeholderText: true,
|
placeholderText: EvaluationSubstitutionType.TEMPLATE,
|
||||||
regex: true,
|
regex: EvaluationSubstitutionType.TEMPLATE,
|
||||||
resetOnSubmit: true,
|
resetOnSubmit: EvaluationSubstitutionType.TEMPLATE,
|
||||||
text: true,
|
text: EvaluationSubstitutionType.TEMPLATE,
|
||||||
value: true,
|
value: EvaluationSubstitutionType.TEMPLATE,
|
||||||
},
|
},
|
||||||
triggerPaths: {
|
triggerPaths: {
|
||||||
onSubmit: true,
|
onSubmit: true,
|
||||||
|
|
|
||||||
|
|
@ -19,26 +19,10 @@ export const generateDataTreeWidget = (
|
||||||
const propertyPaneConfigs = WidgetFactory.getWidgetPropertyPaneConfig(
|
const propertyPaneConfigs = WidgetFactory.getWidgetPropertyPaneConfig(
|
||||||
widget.type,
|
widget.type,
|
||||||
);
|
);
|
||||||
const {
|
|
||||||
bindingPaths,
|
|
||||||
triggerPaths,
|
|
||||||
validationPaths,
|
|
||||||
} = getAllPathsFromPropertyConfig(
|
|
||||||
widget,
|
|
||||||
propertyPaneConfigs,
|
|
||||||
Object.fromEntries(
|
|
||||||
Object.keys(derivedPropertyMap).map((key) => [key, true]),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Object.keys(defaultMetaProps).forEach((defaultPath) => {
|
|
||||||
bindingPaths[defaultPath] = true;
|
|
||||||
});
|
|
||||||
const derivedProps: any = {};
|
const derivedProps: any = {};
|
||||||
const dynamicBindingPathList = getEntityDynamicBindingPathList(widget);
|
const dynamicBindingPathList = getEntityDynamicBindingPathList(widget);
|
||||||
dynamicBindingPathList.forEach((dynamicPath) => {
|
dynamicBindingPathList.forEach((dynamicPath) => {
|
||||||
const propertyPath = dynamicPath.key;
|
const propertyPath = dynamicPath.key;
|
||||||
// Add any dynamically generated dynamic bindings in the binding paths
|
|
||||||
bindingPaths[propertyPath] = true;
|
|
||||||
const propertyValue = _.get(widget, propertyPath);
|
const propertyValue = _.get(widget, propertyPath);
|
||||||
if (_.isObject(propertyValue)) {
|
if (_.isObject(propertyValue)) {
|
||||||
// Stringify this because composite controls may have bindings in the sub controls
|
// Stringify this because composite controls may have bindings in the sub controls
|
||||||
|
|
@ -54,7 +38,6 @@ export const generateDataTreeWidget = (
|
||||||
dynamicBindingPathList.push({
|
dynamicBindingPathList.push({
|
||||||
key: propertyName,
|
key: propertyName,
|
||||||
});
|
});
|
||||||
bindingPaths[propertyName] = true;
|
|
||||||
});
|
});
|
||||||
const unInitializedDefaultProps: Record<string, undefined> = {};
|
const unInitializedDefaultProps: Record<string, undefined> = {};
|
||||||
Object.values(defaultProps).forEach((propertyName) => {
|
Object.values(defaultProps).forEach((propertyName) => {
|
||||||
|
|
@ -62,6 +45,16 @@ export const generateDataTreeWidget = (
|
||||||
unInitializedDefaultProps[propertyName] = undefined;
|
unInitializedDefaultProps[propertyName] = undefined;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const {
|
||||||
|
bindingPaths,
|
||||||
|
triggerPaths,
|
||||||
|
validationPaths,
|
||||||
|
} = getAllPathsFromPropertyConfig(widget, propertyPaneConfigs, {
|
||||||
|
...derivedPropertyMap,
|
||||||
|
...defaultMetaProps,
|
||||||
|
...unInitializedDefaultProps,
|
||||||
|
..._.keyBy(dynamicBindingPathList, "key"),
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
...widget,
|
...widget,
|
||||||
...defaultMetaProps,
|
...defaultMetaProps,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { getAllPathsFromPropertyConfig } from "./utils";
|
||||||
import { RenderModes, WidgetTypes } from "../../constants/WidgetConstants";
|
import { RenderModes, WidgetTypes } from "../../constants/WidgetConstants";
|
||||||
import tablePropertyPaneConfig from "widgets/TableWidget/TablePropertyPaneConfig";
|
import tablePropertyPaneConfig from "widgets/TableWidget/TablePropertyPaneConfig";
|
||||||
import chartPorpertyConfig from "widgets/ChartWidget/propertyConfig";
|
import chartPorpertyConfig from "widgets/ChartWidget/propertyConfig";
|
||||||
|
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||||
|
|
||||||
describe("getAllPathsFromPropertyConfig", () => {
|
describe("getAllPathsFromPropertyConfig", () => {
|
||||||
it("works as expected for table widget", () => {
|
it("works as expected for table widget", () => {
|
||||||
|
|
@ -112,31 +113,47 @@ describe("getAllPathsFromPropertyConfig", () => {
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
bindingPaths: {
|
bindingPaths: {
|
||||||
selectedRow: true,
|
selectedRow: EvaluationSubstitutionType.TEMPLATE,
|
||||||
selectedRows: true,
|
selectedRows: EvaluationSubstitutionType.TEMPLATE,
|
||||||
tableData: true,
|
tableData: EvaluationSubstitutionType.TEMPLATE,
|
||||||
defaultSearchText: true,
|
defaultSearchText: EvaluationSubstitutionType.TEMPLATE,
|
||||||
defaultSelectedRow: true,
|
defaultSelectedRow: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isVisible: true,
|
isVisible: EvaluationSubstitutionType.TEMPLATE,
|
||||||
"primaryColumns.name.computedValue": true,
|
"primaryColumns.name.computedValue":
|
||||||
"primaryColumns.name.horizontalAlignment": true,
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
"primaryColumns.name.verticalAlignment": true,
|
"primaryColumns.name.horizontalAlignment":
|
||||||
"primaryColumns.name.textSize": true,
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
"primaryColumns.name.fontStyle": true,
|
"primaryColumns.name.verticalAlignment":
|
||||||
"primaryColumns.name.textColor": true,
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
"primaryColumns.name.cellBackground": true,
|
"primaryColumns.name.textSize": EvaluationSubstitutionType.TEMPLATE,
|
||||||
"primaryColumns.createdAt.inputFormat": true,
|
"primaryColumns.name.fontStyle": EvaluationSubstitutionType.TEMPLATE,
|
||||||
"primaryColumns.createdAt.outputFormat": true,
|
"primaryColumns.name.textColor": EvaluationSubstitutionType.TEMPLATE,
|
||||||
"primaryColumns.createdAt.computedValue": true,
|
"primaryColumns.name.cellBackground":
|
||||||
"primaryColumns.createdAt.horizontalAlignment": true,
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
"primaryColumns.createdAt.verticalAlignment": true,
|
"primaryColumns.createdAt.inputFormat":
|
||||||
"primaryColumns.createdAt.textSize": true,
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
"primaryColumns.createdAt.fontStyle": true,
|
"primaryColumns.createdAt.outputFormat":
|
||||||
"primaryColumns.createdAt.textColor": true,
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
"primaryColumns.createdAt.cellBackground": true,
|
"primaryColumns.createdAt.computedValue":
|
||||||
"primaryColumns.status.buttonLabel": true,
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
"primaryColumns.status.buttonStyle": true,
|
"primaryColumns.createdAt.horizontalAlignment":
|
||||||
"primaryColumns.status.buttonLabelColor": true,
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
"primaryColumns.createdAt.verticalAlignment":
|
||||||
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
"primaryColumns.createdAt.textSize":
|
||||||
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
"primaryColumns.createdAt.fontStyle":
|
||||||
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
"primaryColumns.createdAt.textColor":
|
||||||
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
"primaryColumns.createdAt.cellBackground":
|
||||||
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
"primaryColumns.status.buttonLabel":
|
||||||
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
"primaryColumns.status.buttonStyle":
|
||||||
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
"primaryColumns.status.buttonLabelColor":
|
||||||
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
},
|
},
|
||||||
triggerPaths: {
|
triggerPaths: {
|
||||||
onRowSelected: true,
|
onRowSelected: true,
|
||||||
|
|
@ -197,13 +214,13 @@ describe("getAllPathsFromPropertyConfig", () => {
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
bindingPaths: {
|
bindingPaths: {
|
||||||
chartType: true,
|
chartType: EvaluationSubstitutionType.TEMPLATE,
|
||||||
chartName: true,
|
chartName: EvaluationSubstitutionType.TEMPLATE,
|
||||||
"chartData[0].seriesName": true,
|
"chartData[0].seriesName": EvaluationSubstitutionType.TEMPLATE,
|
||||||
"chartData[0].data": true,
|
"chartData[0].data": EvaluationSubstitutionType.TEMPLATE,
|
||||||
xAxisName: true,
|
xAxisName: EvaluationSubstitutionType.TEMPLATE,
|
||||||
yAxisName: true,
|
yAxisName: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isVisible: true,
|
isVisible: EvaluationSubstitutionType.TEMPLATE,
|
||||||
},
|
},
|
||||||
triggerPaths: {
|
triggerPaths: {
|
||||||
onDataPointClick: true,
|
onDataPointClick: true,
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,22 @@ import { PropertyPaneConfig } from "constants/PropertyControlConstants";
|
||||||
import { get } from "lodash";
|
import { get } from "lodash";
|
||||||
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
|
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||||
import { VALIDATION_TYPES } from "constants/WidgetValidation";
|
import { VALIDATION_TYPES } from "constants/WidgetValidation";
|
||||||
|
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||||
|
|
||||||
export const getAllPathsFromPropertyConfig = (
|
export const getAllPathsFromPropertyConfig = (
|
||||||
widget: WidgetProps,
|
widget: WidgetProps,
|
||||||
widgetConfig: readonly PropertyPaneConfig[],
|
widgetConfig: readonly PropertyPaneConfig[],
|
||||||
derivedProperties: Record<string, true>,
|
defaultProperties: Record<string, any>,
|
||||||
): {
|
): {
|
||||||
bindingPaths: Record<string, true>;
|
bindingPaths: Record<string, EvaluationSubstitutionType>;
|
||||||
triggerPaths: Record<string, true>;
|
triggerPaths: Record<string, true>;
|
||||||
validationPaths: Record<string, VALIDATION_TYPES>;
|
validationPaths: Record<string, VALIDATION_TYPES>;
|
||||||
} => {
|
} => {
|
||||||
const bindingPaths: Record<string, true> = derivedProperties;
|
const bindingPaths: Record<string, EvaluationSubstitutionType> = {};
|
||||||
|
Object.keys(defaultProperties).forEach(
|
||||||
|
(property) =>
|
||||||
|
(bindingPaths[property] = EvaluationSubstitutionType.TEMPLATE),
|
||||||
|
);
|
||||||
const triggerPaths: Record<string, true> = {};
|
const triggerPaths: Record<string, true> = {};
|
||||||
const validationPaths: Record<any, VALIDATION_TYPES> = {};
|
const validationPaths: Record<any, VALIDATION_TYPES> = {};
|
||||||
widgetConfig.forEach((config) => {
|
widgetConfig.forEach((config) => {
|
||||||
|
|
@ -29,7 +34,8 @@ export const getAllPathsFromPropertyConfig = (
|
||||||
controlConfig.isBindProperty &&
|
controlConfig.isBindProperty &&
|
||||||
!controlConfig.isTriggerProperty
|
!controlConfig.isTriggerProperty
|
||||||
) {
|
) {
|
||||||
bindingPaths[controlConfig.propertyName] = true;
|
bindingPaths[controlConfig.propertyName] =
|
||||||
|
EvaluationSubstitutionType.TEMPLATE;
|
||||||
if (controlConfig.validation) {
|
if (controlConfig.validation) {
|
||||||
validationPaths[controlConfig.propertyName] =
|
validationPaths[controlConfig.propertyName] =
|
||||||
controlConfig.validation;
|
controlConfig.validation;
|
||||||
|
|
@ -72,7 +78,8 @@ export const getAllPathsFromPropertyConfig = (
|
||||||
panelColumnControlConfig.isBindProperty &&
|
panelColumnControlConfig.isBindProperty &&
|
||||||
!panelColumnControlConfig.isTriggerProperty
|
!panelColumnControlConfig.isTriggerProperty
|
||||||
) {
|
) {
|
||||||
bindingPaths[panelPropertyPath] = true;
|
bindingPaths[panelPropertyPath] =
|
||||||
|
EvaluationSubstitutionType.TEMPLATE;
|
||||||
if (panelColumnControlConfig.validation) {
|
if (panelColumnControlConfig.validation) {
|
||||||
validationPaths[panelPropertyPath] =
|
validationPaths[panelPropertyPath] =
|
||||||
panelColumnControlConfig.validation;
|
panelColumnControlConfig.validation;
|
||||||
|
|
@ -107,7 +114,8 @@ export const getAllPathsFromPropertyConfig = (
|
||||||
childPropertyConfig.isBindProperty &&
|
childPropertyConfig.isBindProperty &&
|
||||||
!childPropertyConfig.isTriggerProperty
|
!childPropertyConfig.isTriggerProperty
|
||||||
) {
|
) {
|
||||||
bindingPaths[childArrayPropertyPath] = true;
|
bindingPaths[childArrayPropertyPath] =
|
||||||
|
EvaluationSubstitutionType.TEMPLATE;
|
||||||
if (childPropertyConfig.validation) {
|
if (childPropertyConfig.validation) {
|
||||||
validationPaths[childArrayPropertyPath] =
|
validationPaths[childArrayPropertyPath] =
|
||||||
childPropertyConfig.validation;
|
childPropertyConfig.validation;
|
||||||
|
|
@ -131,13 +139,13 @@ export const getAllPathsFromPropertyConfig = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const nextAvailableRowInContainer = (
|
export const nextAvailableRowInContainer = (
|
||||||
parenContainertId: string,
|
parentContainerId: string,
|
||||||
canvasWidgets: { [widgetId: string]: FlattenedWidgetProps },
|
canvasWidgets: { [widgetId: string]: FlattenedWidgetProps },
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
Object.values(canvasWidgets).reduce(
|
Object.values(canvasWidgets).reduce(
|
||||||
(prev: number, next: any) =>
|
(prev: number, next: any) =>
|
||||||
next?.parentId === parenContainertId && next.bottomRow > prev
|
next?.parentId === parentContainerId && next.bottomRow > prev
|
||||||
? next.bottomRow
|
? next.bottomRow
|
||||||
: prev,
|
: prev,
|
||||||
0,
|
0,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
PluginFormPayloadWithId,
|
PluginFormPayloadWithId,
|
||||||
PluginFormsPayload,
|
PluginFormsPayload,
|
||||||
} from "actions/pluginActions";
|
} from "actions/pluginActions";
|
||||||
|
import { DependencyMap } from "utils/DynamicBindingUtils";
|
||||||
|
|
||||||
export interface PluginDataState {
|
export interface PluginDataState {
|
||||||
list: Plugin[];
|
list: Plugin[];
|
||||||
|
|
@ -16,6 +17,7 @@ export interface PluginDataState {
|
||||||
formConfigs: Record<string, any[]>;
|
formConfigs: Record<string, any[]>;
|
||||||
editorConfigs: Record<string, any[]>;
|
editorConfigs: Record<string, any[]>;
|
||||||
settingConfigs: Record<string, any[]>;
|
settingConfigs: Record<string, any[]>;
|
||||||
|
dependencies: Record<string, DependencyMap>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: PluginDataState = {
|
const initialState: PluginDataState = {
|
||||||
|
|
@ -24,6 +26,7 @@ const initialState: PluginDataState = {
|
||||||
formConfigs: {},
|
formConfigs: {},
|
||||||
editorConfigs: {},
|
editorConfigs: {},
|
||||||
settingConfigs: {},
|
settingConfigs: {},
|
||||||
|
dependencies: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const pluginsReducer = createReducer(initialState, {
|
const pluginsReducer = createReducer(initialState, {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import {
|
||||||
ReduxActionErrorTypes,
|
ReduxActionErrorTypes,
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
import { PageAction } from "constants/AppsmithActionConstants/ActionConstants";
|
||||||
|
|
||||||
const initialState: EditorReduxState = {
|
const initialState: EditorReduxState = {
|
||||||
initialized: false,
|
initialized: false,
|
||||||
|
|
@ -106,6 +107,7 @@ const editorReducer = createReducer(initialState, {
|
||||||
pageWidgetId,
|
pageWidgetId,
|
||||||
currentApplicationId,
|
currentApplicationId,
|
||||||
currentPageId,
|
currentPageId,
|
||||||
|
pageActions,
|
||||||
} = action.payload;
|
} = action.payload;
|
||||||
state.loadingStates.publishing = false;
|
state.loadingStates.publishing = false;
|
||||||
state.loadingStates.publishingError = false;
|
state.loadingStates.publishingError = false;
|
||||||
|
|
@ -116,6 +118,7 @@ const editorReducer = createReducer(initialState, {
|
||||||
pageWidgetId,
|
pageWidgetId,
|
||||||
currentApplicationId,
|
currentApplicationId,
|
||||||
currentPageId,
|
currentPageId,
|
||||||
|
pageActions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[ReduxActionTypes.CLONE_PAGE_INIT]: (state: EditorReduxState) => {
|
[ReduxActionTypes.CLONE_PAGE_INIT]: (state: EditorReduxState) => {
|
||||||
|
|
@ -171,6 +174,7 @@ export interface EditorReduxState {
|
||||||
currentLayoutId?: string;
|
currentLayoutId?: string;
|
||||||
currentPageName?: string;
|
currentPageName?: string;
|
||||||
currentPageId?: string;
|
currentPageId?: string;
|
||||||
|
pageActions?: PageAction[][];
|
||||||
loadingStates: {
|
loadingStates: {
|
||||||
saving: boolean;
|
saving: boolean;
|
||||||
savingError: boolean;
|
savingError: boolean;
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import { executeAction, executeActionError } from "actions/widgetActions";
|
||||||
import {
|
import {
|
||||||
getCurrentApplicationId,
|
getCurrentApplicationId,
|
||||||
getCurrentPageId,
|
getCurrentPageId,
|
||||||
|
getLayoutOnLoadActions,
|
||||||
getPageList,
|
getPageList,
|
||||||
} from "selectors/editorSelectors";
|
} from "selectors/editorSelectors";
|
||||||
import _, { get, isString } from "lodash";
|
import _, { get, isString } from "lodash";
|
||||||
|
|
@ -979,9 +980,9 @@ function* executePageLoadAction(pageAction: PageAction) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* executePageLoadActionsSaga(action: ReduxAction<PageAction[][]>) {
|
function* executePageLoadActionsSaga() {
|
||||||
try {
|
try {
|
||||||
const pageActions = action.payload;
|
const pageActions: PageAction[][] = yield select(getLayoutOnLoadActions);
|
||||||
const actionCount = _.flatten(pageActions).length;
|
const actionCount = _.flatten(pageActions).length;
|
||||||
PerformanceTracker.startAsyncTracking(
|
PerformanceTracker.startAsyncTracking(
|
||||||
PerformanceTransactionName.EXECUTE_PAGE_LOAD_ACTIONS,
|
PerformanceTransactionName.EXECUTE_PAGE_LOAD_ACTIONS,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
EvaluationReduxAction,
|
||||||
ReduxAction,
|
ReduxAction,
|
||||||
ReduxActionErrorTypes,
|
ReduxActionErrorTypes,
|
||||||
ReduxActionTypes,
|
ReduxActionTypes,
|
||||||
|
|
@ -158,7 +159,9 @@ export function* createActionSaga(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* fetchActionsSaga(action: ReduxAction<FetchActionsPayload>) {
|
export function* fetchActionsSaga(
|
||||||
|
action: EvaluationReduxAction<FetchActionsPayload>,
|
||||||
|
) {
|
||||||
const { applicationId } = action.payload;
|
const { applicationId } = action.payload;
|
||||||
PerformanceTracker.startAsyncTracking(
|
PerformanceTracker.startAsyncTracking(
|
||||||
PerformanceTransactionName.FETCH_ACTIONS_API,
|
PerformanceTransactionName.FETCH_ACTIONS_API,
|
||||||
|
|
@ -173,6 +176,7 @@ export function* fetchActionsSaga(action: ReduxAction<FetchActionsPayload>) {
|
||||||
yield put({
|
yield put({
|
||||||
type: ReduxActionTypes.FETCH_ACTIONS_SUCCESS,
|
type: ReduxActionTypes.FETCH_ACTIONS_SUCCESS,
|
||||||
payload: response.data,
|
payload: response.data,
|
||||||
|
postEvalActions: action.postEvalActions,
|
||||||
});
|
});
|
||||||
PerformanceTracker.stopAsyncTracking(
|
PerformanceTracker.stopAsyncTracking(
|
||||||
PerformanceTransactionName.FETCH_ACTIONS_API,
|
PerformanceTransactionName.FETCH_ACTIONS_API,
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@ import { createMessage, ERROR_ACTION_RENAME_FAIL } from "constants/messages";
|
||||||
import { checkCurrentStep } from "./OnboardingSagas";
|
import { checkCurrentStep } from "./OnboardingSagas";
|
||||||
import { OnboardingStep } from "constants/OnboardingConstants";
|
import { OnboardingStep } from "constants/OnboardingConstants";
|
||||||
import { getIndextoUpdate } from "utils/ApiPaneUtils";
|
import { getIndextoUpdate } from "utils/ApiPaneUtils";
|
||||||
import { changeQuery } from "actions/queryPaneActions";
|
|
||||||
|
|
||||||
function* syncApiParamsSaga(
|
function* syncApiParamsSaga(
|
||||||
actionPayload: ReduxActionWithMeta<string, { field: string }>,
|
actionPayload: ReduxActionWithMeta<string, { field: string }>,
|
||||||
|
|
@ -208,29 +207,10 @@ function* initializeExtraFormDataSaga() {
|
||||||
const { extraformData } = state.ui.apiPane;
|
const { extraformData } = state.ui.apiPane;
|
||||||
const formData = yield select(getFormData, API_EDITOR_FORM_NAME);
|
const formData = yield select(getFormData, API_EDITOR_FORM_NAME);
|
||||||
const { values } = formData;
|
const { values } = formData;
|
||||||
const headers = get(
|
const headers = get(values, "actionConfiguration.headers");
|
||||||
values,
|
|
||||||
"actionConfiguration.headers",
|
|
||||||
DEFAULT_API_ACTION_CONFIG.headers,
|
|
||||||
);
|
|
||||||
|
|
||||||
const queryParameters = get(
|
|
||||||
values,
|
|
||||||
"actionConfiguration.queryParameters",
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
if (!extraformData[values.id]) {
|
if (!extraformData[values.id]) {
|
||||||
yield put(
|
yield call(setHeaderFormat, values.id, headers);
|
||||||
change(API_EDITOR_FORM_NAME, "actionConfiguration.headers", headers),
|
|
||||||
);
|
|
||||||
if (queryParameters.length === 0)
|
|
||||||
yield put(
|
|
||||||
change(
|
|
||||||
API_EDITOR_FORM_NAME,
|
|
||||||
"actionConfiguration.queryParameters",
|
|
||||||
DEFAULT_API_ACTION_CONFIG.queryParameters,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,6 +250,42 @@ function* changeApiSaga(actionPayload: ReduxAction<{ id: string }>) {
|
||||||
PerformanceTracker.stopTracking();
|
PerformanceTracker.stopTracking();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function* setHeaderFormat(apiId: string, headers?: Property[]) {
|
||||||
|
let displayFormat;
|
||||||
|
|
||||||
|
if (headers) {
|
||||||
|
const contentType = headers.find(
|
||||||
|
(header: any) =>
|
||||||
|
header &&
|
||||||
|
header.key &&
|
||||||
|
header.key.toLowerCase() === CONTENT_TYPE_HEADER_KEY,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
contentType &&
|
||||||
|
contentType.value &&
|
||||||
|
POST_BODY_FORMATS.includes(contentType.value)
|
||||||
|
) {
|
||||||
|
displayFormat = {
|
||||||
|
label: contentType.value,
|
||||||
|
value: contentType.value,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
displayFormat = POST_BODY_FORMAT_OPTIONS[3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yield put({
|
||||||
|
type: ReduxActionTypes.SET_EXTRA_FORMDATA,
|
||||||
|
payload: {
|
||||||
|
id: apiId,
|
||||||
|
values: {
|
||||||
|
displayFormat,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function* updateFormFields(
|
function* updateFormFields(
|
||||||
actionPayload: ReduxActionWithMeta<string, { field: string }>,
|
actionPayload: ReduxActionWithMeta<string, { field: string }>,
|
||||||
) {
|
) {
|
||||||
|
|
@ -318,35 +334,7 @@ function* updateFormFields(
|
||||||
"actionConfiguration.headers",
|
"actionConfiguration.headers",
|
||||||
);
|
);
|
||||||
const apiId = get(values, "id");
|
const apiId = get(values, "id");
|
||||||
let displayFormat;
|
yield call(setHeaderFormat, apiId, actionConfigurationHeaders);
|
||||||
|
|
||||||
if (actionConfigurationHeaders) {
|
|
||||||
const contentType = actionConfigurationHeaders.find(
|
|
||||||
(header: any) =>
|
|
||||||
header &&
|
|
||||||
header.key &&
|
|
||||||
header.key.toLowerCase() === CONTENT_TYPE_HEADER_KEY,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (contentType && POST_BODY_FORMATS.includes(contentType.value)) {
|
|
||||||
displayFormat = {
|
|
||||||
label: contentType.value,
|
|
||||||
value: contentType.value,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
displayFormat = POST_BODY_FORMAT_OPTIONS[3];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionTypes.SET_EXTRA_FORMDATA,
|
|
||||||
payload: {
|
|
||||||
id: apiId,
|
|
||||||
values: {
|
|
||||||
displayFormat,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -562,12 +550,6 @@ function* handleApiNameChangeFailureSaga(
|
||||||
yield put(change(API_EDITOR_FORM_NAME, "name", action.payload.oldName));
|
yield put(change(API_EDITOR_FORM_NAME, "name", action.payload.oldName));
|
||||||
}
|
}
|
||||||
|
|
||||||
function* updateFormValues(action: ReduxAction<{ data: Action }>) {
|
|
||||||
if (action.payload.data.pluginType === PluginType.API) {
|
|
||||||
yield call(changeApiSaga, changeQuery(action.payload.data.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function* root() {
|
export default function* root() {
|
||||||
yield all([
|
yield all([
|
||||||
takeEvery(ReduxActionTypes.API_PANE_CHANGE_API, changeApiSaga),
|
takeEvery(ReduxActionTypes.API_PANE_CHANGE_API, changeApiSaga),
|
||||||
|
|
@ -597,7 +579,6 @@ export default function* root() {
|
||||||
ReduxActionTypes.UPDATE_API_ACTION_BODY_CONTENT_TYPE,
|
ReduxActionTypes.UPDATE_API_ACTION_BODY_CONTENT_TYPE,
|
||||||
handleUpdateBodyContentType,
|
handleUpdateBodyContentType,
|
||||||
),
|
),
|
||||||
takeEvery(ReduxActionTypes.UPDATE_ACTION_SUCCESS, updateFormValues),
|
|
||||||
// Intercepting the redux-form change actionType
|
// Intercepting the redux-form change actionType
|
||||||
takeEvery(ReduxFormActionTypes.VALUE_CHANGE, formValueChangeSaga),
|
takeEvery(ReduxFormActionTypes.VALUE_CHANGE, formValueChangeSaga),
|
||||||
takeEvery(ReduxFormActionTypes.ARRAY_REMOVE, formValueChangeSaga),
|
takeEvery(ReduxFormActionTypes.ARRAY_REMOVE, formValueChangeSaga),
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
ReduxAction,
|
ReduxAction,
|
||||||
ReduxActionErrorTypes,
|
ReduxActionErrorTypes,
|
||||||
ReduxActionTypes,
|
ReduxActionTypes,
|
||||||
|
ReduxActionWithoutPayload,
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
import { getUnevaluatedDataTree } from "selectors/dataTreeSelectors";
|
import { getUnevaluatedDataTree } from "selectors/dataTreeSelectors";
|
||||||
import WidgetFactory, { WidgetTypeConfigMap } from "../utils/WidgetFactory";
|
import WidgetFactory, { WidgetTypeConfigMap } from "../utils/WidgetFactory";
|
||||||
|
|
@ -109,13 +110,17 @@ const evalErrorHandler = (errors: EvalError[]) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function* postEvalActionDispatcher(actions: ReduxAction<unknown>[]) {
|
function* postEvalActionDispatcher(
|
||||||
|
actions: Array<ReduxAction<unknown> | ReduxActionWithoutPayload>,
|
||||||
|
) {
|
||||||
for (const action of actions) {
|
for (const action of actions) {
|
||||||
yield put(action);
|
yield put(action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* evaluateTreeSaga(postEvalActions?: ReduxAction<unknown>[]) {
|
function* evaluateTreeSaga(
|
||||||
|
postEvalActions?: Array<ReduxAction<unknown> | ReduxActionWithoutPayload>,
|
||||||
|
) {
|
||||||
const unevalTree = yield select(getUnevaluatedDataTree);
|
const unevalTree = yield select(getUnevaluatedDataTree);
|
||||||
log.debug({ unevalTree });
|
log.debug({ unevalTree });
|
||||||
PerformanceTracker.startAsyncTracking(
|
PerformanceTracker.startAsyncTracking(
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import {
|
||||||
ReduxAction,
|
ReduxAction,
|
||||||
ReduxActionErrorTypes,
|
ReduxActionErrorTypes,
|
||||||
ReduxActionTypes,
|
ReduxActionTypes,
|
||||||
|
ReduxActionWithoutPayload,
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
import { ERROR_CODES } from "constants/ApiConstants";
|
import { ERROR_CODES } from "constants/ApiConstants";
|
||||||
|
|
||||||
|
|
@ -43,6 +44,41 @@ import { resetEditorSuccess } from "actions/initActions";
|
||||||
import PerformanceTracker, {
|
import PerformanceTracker, {
|
||||||
PerformanceTransactionName,
|
PerformanceTransactionName,
|
||||||
} from "utils/PerformanceTracker";
|
} from "utils/PerformanceTracker";
|
||||||
|
import { executePageLoadActions } from "actions/widgetActions";
|
||||||
|
|
||||||
|
function* failFastApiCalls(
|
||||||
|
triggerActions: Array<ReduxAction<unknown> | ReduxActionWithoutPayload>,
|
||||||
|
successActions: string[],
|
||||||
|
failureActions: string[],
|
||||||
|
) {
|
||||||
|
const triggerEffects = [];
|
||||||
|
for (const triggerAction of triggerActions) {
|
||||||
|
triggerEffects.push(put(triggerAction));
|
||||||
|
}
|
||||||
|
const successEffects = [];
|
||||||
|
for (const successAction of successActions) {
|
||||||
|
successEffects.push(take(successAction));
|
||||||
|
}
|
||||||
|
yield all(triggerEffects);
|
||||||
|
const effectRaceResult = yield race({
|
||||||
|
success: all(successEffects),
|
||||||
|
failure: take(failureActions),
|
||||||
|
});
|
||||||
|
if (effectRaceResult.failure) {
|
||||||
|
yield put({
|
||||||
|
type: ReduxActionTypes.SAFE_CRASH_APPSMITH_REQUEST,
|
||||||
|
payload: {
|
||||||
|
code: get(
|
||||||
|
effectRaceResult,
|
||||||
|
"failure.payload.error.code",
|
||||||
|
ERROR_CODES.SERVER_ERROR,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function* initializeEditorSaga(
|
function* initializeEditorSaga(
|
||||||
initializeEditorAction: ReduxAction<InitializeEditorPayload>,
|
initializeEditorAction: ReduxAction<InitializeEditorPayload>,
|
||||||
|
|
@ -55,97 +91,61 @@ function* initializeEditorSaga(
|
||||||
yield put(setAppMode(APP_MODE.EDIT));
|
yield put(setAppMode(APP_MODE.EDIT));
|
||||||
yield put(updateAppPersistentStore(getPersistentAppStore(applicationId)));
|
yield put(updateAppPersistentStore(getPersistentAppStore(applicationId)));
|
||||||
yield put({ type: ReduxActionTypes.START_EVALUATION });
|
yield put({ type: ReduxActionTypes.START_EVALUATION });
|
||||||
yield all([
|
|
||||||
put(fetchPageList(applicationId, APP_MODE.EDIT)),
|
|
||||||
put(fetchActions(applicationId)),
|
|
||||||
put(fetchPage(pageId)),
|
|
||||||
put(fetchApplication(applicationId, APP_MODE.EDIT)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
yield put(restoreRecentEntitiesRequest(applicationId));
|
const applicationAndLayoutCalls = yield failFastApiCalls(
|
||||||
|
[
|
||||||
const resultOfPrimaryCalls = yield race({
|
fetchPageList(applicationId, APP_MODE.EDIT),
|
||||||
success: all([
|
fetchPage(pageId),
|
||||||
take(ReduxActionTypes.FETCH_PAGE_LIST_SUCCESS),
|
fetchApplication(applicationId, APP_MODE.EDIT),
|
||||||
take(ReduxActionTypes.FETCH_PAGE_SUCCESS),
|
],
|
||||||
take(ReduxActionTypes.FETCH_APPLICATION_SUCCESS),
|
[
|
||||||
take(ReduxActionTypes.FETCH_ACTIONS_SUCCESS),
|
ReduxActionTypes.FETCH_PAGE_LIST_SUCCESS,
|
||||||
]),
|
ReduxActionTypes.FETCH_PAGE_SUCCESS,
|
||||||
failure: take([
|
ReduxActionTypes.FETCH_APPLICATION_SUCCESS,
|
||||||
|
],
|
||||||
|
[
|
||||||
ReduxActionErrorTypes.FETCH_PAGE_LIST_ERROR,
|
ReduxActionErrorTypes.FETCH_PAGE_LIST_ERROR,
|
||||||
ReduxActionErrorTypes.FETCH_PAGE_ERROR,
|
ReduxActionErrorTypes.FETCH_PAGE_ERROR,
|
||||||
ReduxActionErrorTypes.FETCH_APPLICATION_ERROR,
|
ReduxActionErrorTypes.FETCH_APPLICATION_ERROR,
|
||||||
ReduxActionErrorTypes.FETCH_ACTIONS_ERROR,
|
],
|
||||||
]),
|
);
|
||||||
});
|
if (!applicationAndLayoutCalls) return;
|
||||||
|
|
||||||
if (resultOfPrimaryCalls.failure) {
|
const pluginsAndDatasourcesCalls = yield failFastApiCalls(
|
||||||
yield put({
|
[fetchPlugins(), fetchDatasources()],
|
||||||
type: ReduxActionTypes.SAFE_CRASH_APPSMITH_REQUEST,
|
[
|
||||||
payload: {
|
ReduxActionTypes.FETCH_PLUGINS_SUCCESS,
|
||||||
code: get(
|
ReduxActionTypes.FETCH_DATASOURCES_SUCCESS,
|
||||||
resultOfPrimaryCalls,
|
],
|
||||||
"failure.payload.error.code",
|
[
|
||||||
ERROR_CODES.SERVER_ERROR,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
yield all([put(fetchPlugins()), put(fetchDatasources())]);
|
|
||||||
|
|
||||||
const resultOfSecondaryCalls = yield race({
|
|
||||||
success: all([
|
|
||||||
take(ReduxActionTypes.FETCH_PLUGINS_SUCCESS),
|
|
||||||
take(ReduxActionTypes.FETCH_DATASOURCES_SUCCESS),
|
|
||||||
]),
|
|
||||||
failure: take([
|
|
||||||
ReduxActionErrorTypes.FETCH_PLUGINS_ERROR,
|
ReduxActionErrorTypes.FETCH_PLUGINS_ERROR,
|
||||||
ReduxActionErrorTypes.FETCH_DATASOURCES_ERROR,
|
ReduxActionErrorTypes.FETCH_DATASOURCES_ERROR,
|
||||||
]),
|
],
|
||||||
});
|
);
|
||||||
|
if (!pluginsAndDatasourcesCalls) return;
|
||||||
|
|
||||||
if (resultOfSecondaryCalls.failure) {
|
const pluginFormCall = yield failFastApiCalls(
|
||||||
yield put({
|
[fetchPluginFormConfigs()],
|
||||||
type: ReduxActionTypes.SAFE_CRASH_APPSMITH_REQUEST,
|
[ReduxActionTypes.FETCH_PLUGIN_FORM_CONFIGS_SUCCESS],
|
||||||
payload: {
|
[ReduxActionErrorTypes.FETCH_PLUGIN_FORM_CONFIGS_ERROR],
|
||||||
code: get(
|
);
|
||||||
resultOfSecondaryCalls,
|
if (!pluginFormCall) return;
|
||||||
"failure.payload.error.code",
|
|
||||||
ERROR_CODES.SERVER_ERROR,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
yield put(fetchPluginFormConfigs());
|
const actionsCall = yield failFastApiCalls(
|
||||||
|
[fetchActions(applicationId, [executePageLoadActions()])],
|
||||||
|
[ReduxActionTypes.FETCH_ACTIONS_SUCCESS],
|
||||||
|
[ReduxActionErrorTypes.FETCH_ACTIONS_ERROR],
|
||||||
|
);
|
||||||
|
|
||||||
const resultOfPluginFormsCall = yield race({
|
if (!actionsCall) return;
|
||||||
success: take(ReduxActionTypes.FETCH_PLUGIN_FORM_CONFIGS_SUCCESS),
|
|
||||||
failure: take(ReduxActionErrorTypes.FETCH_PLUGIN_FORM_CONFIGS_ERROR),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (resultOfPluginFormsCall.failure) {
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionTypes.SAFE_CRASH_APPSMITH_REQUEST,
|
|
||||||
payload: {
|
|
||||||
code: get(
|
|
||||||
resultOfPluginFormsCall,
|
|
||||||
"failure.payload.error.code",
|
|
||||||
ERROR_CODES.SERVER_ERROR,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentApplication = yield select(getCurrentApplication);
|
const currentApplication = yield select(getCurrentApplication);
|
||||||
|
|
||||||
const appName = currentApplication ? currentApplication.name : "";
|
const appName = currentApplication ? currentApplication.name : "";
|
||||||
const appId = currentApplication ? currentApplication.id : "";
|
const appId = currentApplication ? currentApplication.id : "";
|
||||||
|
|
||||||
|
yield put(restoreRecentEntitiesRequest(applicationId));
|
||||||
|
|
||||||
AnalyticsUtil.logEvent("EDITOR_OPEN", {
|
AnalyticsUtil.logEvent("EDITOR_OPEN", {
|
||||||
appId: appId,
|
appId: appId,
|
||||||
appName: appName,
|
appName: appName,
|
||||||
|
|
|
||||||
|
|
@ -195,12 +195,7 @@ export function* fetchPageSaga(
|
||||||
// set current page
|
// set current page
|
||||||
yield put(updateCurrentPage(id));
|
yield put(updateCurrentPage(id));
|
||||||
// dispatch fetch page success
|
// dispatch fetch page success
|
||||||
yield put(
|
yield put(fetchPageSuccess());
|
||||||
fetchPageSuccess([
|
|
||||||
// Execute page load actions after evaluation of fetch page
|
|
||||||
executePageLoadActions(canvasWidgetsPayload.pageActions),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
yield put({
|
yield put({
|
||||||
type: ReduxActionTypes.UPDATE_CANVAS_STRUCTURE,
|
type: ReduxActionTypes.UPDATE_CANVAS_STRUCTURE,
|
||||||
|
|
@ -264,7 +259,7 @@ export function* fetchPublishedPageSaga(
|
||||||
yield put(
|
yield put(
|
||||||
fetchPublishedPageSuccess(
|
fetchPublishedPageSuccess(
|
||||||
// Execute page load actions post published page eval
|
// Execute page load actions post published page eval
|
||||||
[executePageLoadActions(canvasWidgetsPayload.pageActions)],
|
[executePageLoadActions()],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
PerformanceTracker.stopAsyncTracking(
|
PerformanceTracker.stopAsyncTracking(
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import {
|
||||||
fetchPluginFormConfigSuccess,
|
fetchPluginFormConfigSuccess,
|
||||||
} from "actions/pluginActions";
|
} from "actions/pluginActions";
|
||||||
import {
|
import {
|
||||||
|
defaultActionDependenciesConfig,
|
||||||
defaultActionEditorConfigs,
|
defaultActionEditorConfigs,
|
||||||
defaultActionSettings,
|
defaultActionSettings,
|
||||||
} from "constants/AppsmithActionConstants/ActionConstants";
|
} from "constants/AppsmithActionConstants/ActionConstants";
|
||||||
|
|
@ -26,6 +27,7 @@ import { GenericApiResponse } from "api/ApiResponses";
|
||||||
import PluginApi from "api/PluginApi";
|
import PluginApi from "api/PluginApi";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { PluginType } from "entities/Action";
|
import { PluginType } from "entities/Action";
|
||||||
|
import { DependencyMap } from "utils/DynamicBindingUtils";
|
||||||
|
|
||||||
function* fetchPluginsSaga() {
|
function* fetchPluginsSaga() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -77,20 +79,30 @@ function* fetchPluginFormConfigsSaga() {
|
||||||
const formConfigs: Record<string, any[]> = {};
|
const formConfigs: Record<string, any[]> = {};
|
||||||
const editorConfigs: Record<string, any[]> = {};
|
const editorConfigs: Record<string, any[]> = {};
|
||||||
const settingConfigs: Record<string, any[]> = {};
|
const settingConfigs: Record<string, any[]> = {};
|
||||||
|
const dependencies: Record<string, DependencyMap> = {};
|
||||||
|
|
||||||
Array.from(pluginIdFormsToFetch).forEach((pluginId, index) => {
|
Array.from(pluginIdFormsToFetch).forEach((pluginId, index) => {
|
||||||
const plugin = plugins.find((plugin) => plugin.id === pluginId);
|
const plugin = plugins.find((plugin) => plugin.id === pluginId);
|
||||||
|
// Datasource form always use server's copy
|
||||||
formConfigs[pluginId] = pluginFormData[index].form;
|
formConfigs[pluginId] = pluginFormData[index].form;
|
||||||
|
// Action editor form if not available use default
|
||||||
if (plugin && !pluginFormData[index].editor) {
|
if (plugin && !pluginFormData[index].editor) {
|
||||||
editorConfigs[pluginId] = defaultActionEditorConfigs[plugin.type];
|
editorConfigs[pluginId] = defaultActionEditorConfigs[plugin.type];
|
||||||
} else {
|
} else {
|
||||||
editorConfigs[pluginId] = pluginFormData[index].editor;
|
editorConfigs[pluginId] = pluginFormData[index].editor;
|
||||||
}
|
}
|
||||||
|
// Action settings form if not available use default
|
||||||
if (plugin && !pluginFormData[index].setting) {
|
if (plugin && !pluginFormData[index].setting) {
|
||||||
settingConfigs[pluginId] = defaultActionSettings[plugin.type];
|
settingConfigs[pluginId] = defaultActionSettings[plugin.type];
|
||||||
} else {
|
} else {
|
||||||
settingConfigs[pluginId] = pluginFormData[index].setting;
|
settingConfigs[pluginId] = pluginFormData[index].setting;
|
||||||
}
|
}
|
||||||
|
// Action dependencies config if not available use default
|
||||||
|
if (plugin && !pluginFormData[index].dependencies) {
|
||||||
|
dependencies[pluginId] = defaultActionDependenciesConfig[plugin.type];
|
||||||
|
} else {
|
||||||
|
dependencies[pluginId] = pluginFormData[index].dependencies;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
yield put(
|
yield put(
|
||||||
|
|
@ -98,6 +110,7 @@ function* fetchPluginFormConfigsSaga() {
|
||||||
formConfigs,
|
formConfigs,
|
||||||
editorConfigs,
|
editorConfigs,
|
||||||
settingConfigs,
|
settingConfigs,
|
||||||
|
dependencies,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -125,6 +138,10 @@ export function* checkAndGetPluginFormConfigsSaga(pluginId: string) {
|
||||||
formConfigResponse.data.editor =
|
formConfigResponse.data.editor =
|
||||||
defaultActionEditorConfigs[plugin.type];
|
defaultActionEditorConfigs[plugin.type];
|
||||||
}
|
}
|
||||||
|
if (!formConfigResponse.data.dependencies) {
|
||||||
|
formConfigResponse.data.dependencies =
|
||||||
|
defaultActionDependenciesConfig[plugin.type];
|
||||||
|
}
|
||||||
yield put(
|
yield put(
|
||||||
fetchPluginFormConfigSuccess({
|
fetchPluginFormConfigSuccess({
|
||||||
id: pluginId,
|
id: pluginId,
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ export function* addApiToPageSaga(
|
||||||
});
|
});
|
||||||
|
|
||||||
const applicationId = yield select(getCurrentApplicationId);
|
const applicationId = yield select(getCurrentApplicationId);
|
||||||
yield put(fetchActions(applicationId));
|
yield put(fetchActions(applicationId, []));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
yield put({
|
yield put({
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import {
|
||||||
getPluginTemplates,
|
getPluginTemplates,
|
||||||
getPlugin,
|
getPlugin,
|
||||||
} from "selectors/entitiesSelector";
|
} from "selectors/entitiesSelector";
|
||||||
import { Action, PluginType, QueryAction } from "entities/Action";
|
import { PluginType, QueryAction } from "entities/Action";
|
||||||
import { setActionProperty } from "actions/actionActions";
|
import { setActionProperty } from "actions/actionActions";
|
||||||
import { getQueryParams } from "utils/AppsmithUtils";
|
import { getQueryParams } from "utils/AppsmithUtils";
|
||||||
import { isEmpty, merge } from "lodash";
|
import { isEmpty, merge } from "lodash";
|
||||||
|
|
@ -37,7 +37,6 @@ import { Toaster } from "components/ads/Toast";
|
||||||
import { Datasource } from "entities/Datasource";
|
import { Datasource } from "entities/Datasource";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { createMessage, ERROR_ACTION_RENAME_FAIL } from "constants/messages";
|
import { createMessage, ERROR_ACTION_RENAME_FAIL } from "constants/messages";
|
||||||
import { changeQuery } from "actions/queryPaneActions";
|
|
||||||
|
|
||||||
function* changeQuerySaga(actionPayload: ReduxAction<{ id: string }>) {
|
function* changeQuerySaga(actionPayload: ReduxAction<{ id: string }>) {
|
||||||
const { id } = actionPayload.payload;
|
const { id } = actionPayload.payload;
|
||||||
|
|
@ -214,12 +213,6 @@ function* handleNameChangeFailureSaga(
|
||||||
yield put(change(QUERY_EDITOR_FORM_NAME, "name", action.payload.oldName));
|
yield put(change(QUERY_EDITOR_FORM_NAME, "name", action.payload.oldName));
|
||||||
}
|
}
|
||||||
|
|
||||||
function* updateFormValues(action: ReduxAction<{ data: Action }>) {
|
|
||||||
if (action.payload.data.pluginType === PluginType.DB) {
|
|
||||||
yield call(changeQuerySaga, changeQuery(action.payload.data.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function* root() {
|
export default function* root() {
|
||||||
yield all([
|
yield all([
|
||||||
takeEvery(ReduxActionTypes.CREATE_ACTION_SUCCESS, handleQueryCreatedSaga),
|
takeEvery(ReduxActionTypes.CREATE_ACTION_SUCCESS, handleQueryCreatedSaga),
|
||||||
|
|
@ -237,7 +230,6 @@ export default function* root() {
|
||||||
ReduxActionErrorTypes.SAVE_ACTION_NAME_ERROR,
|
ReduxActionErrorTypes.SAVE_ACTION_NAME_ERROR,
|
||||||
handleNameChangeFailureSaga,
|
handleNameChangeFailureSaga,
|
||||||
),
|
),
|
||||||
takeEvery(ReduxActionTypes.UPDATE_ACTION_SUCCESS, updateFormValues),
|
|
||||||
// Intercepting the redux-form change actionType
|
// Intercepting the redux-form change actionType
|
||||||
takeEvery(ReduxFormActionTypes.VALUE_CHANGE, formValueChangeSaga),
|
takeEvery(ReduxFormActionTypes.VALUE_CHANGE, formValueChangeSaga),
|
||||||
takeEvery(ReduxFormActionTypes.ARRAY_REMOVE, formValueChangeSaga),
|
takeEvery(ReduxFormActionTypes.ARRAY_REMOVE, formValueChangeSaga),
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { createSelector } from "reselect";
|
||||||
import {
|
import {
|
||||||
getActionsForCurrentPage,
|
getActionsForCurrentPage,
|
||||||
getAppData,
|
getAppData,
|
||||||
|
getPluginDependencyConfig,
|
||||||
getPluginEditorConfigs,
|
getPluginEditorConfigs,
|
||||||
} from "./entitiesSelector";
|
} from "./entitiesSelector";
|
||||||
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
||||||
|
|
@ -18,7 +19,16 @@ export const getUnevaluatedDataTree = createSelector(
|
||||||
getPageList,
|
getPageList,
|
||||||
getAppData,
|
getAppData,
|
||||||
getPluginEditorConfigs,
|
getPluginEditorConfigs,
|
||||||
(actions, widgets, widgetsMeta, pageListPayload, appData, editorConfigs) => {
|
getPluginDependencyConfig,
|
||||||
|
(
|
||||||
|
actions,
|
||||||
|
widgets,
|
||||||
|
widgetsMeta,
|
||||||
|
pageListPayload,
|
||||||
|
appData,
|
||||||
|
editorConfigs,
|
||||||
|
pluginDependencyConfig,
|
||||||
|
) => {
|
||||||
const pageList = pageListPayload || [];
|
const pageList = pageListPayload || [];
|
||||||
return DataTreeFactory.create({
|
return DataTreeFactory.create({
|
||||||
actions,
|
actions,
|
||||||
|
|
@ -27,6 +37,7 @@ export const getUnevaluatedDataTree = createSelector(
|
||||||
pageList,
|
pageList,
|
||||||
appData,
|
appData,
|
||||||
editorConfigs,
|
editorConfigs,
|
||||||
|
pluginDependencyConfig,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,9 @@ export const getPageSavingError = (state: AppState) => {
|
||||||
return state.ui.editor.loadingStates.savingError;
|
return state.ui.editor.loadingStates.savingError;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getLayoutOnLoadActions = (state: AppState) =>
|
||||||
|
state.ui.editor.pageActions || [];
|
||||||
|
|
||||||
export const getIsPublishingApplication = (state: AppState) =>
|
export const getIsPublishingApplication = (state: AppState) =>
|
||||||
state.ui.editor.loadingStates.publishing;
|
state.ui.editor.loadingStates.publishing;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,9 @@ export const getPluginByPackageName = (state: AppState, name: string) =>
|
||||||
export const getPluginEditorConfigs = (state: AppState) =>
|
export const getPluginEditorConfigs = (state: AppState) =>
|
||||||
state.entities.plugins.editorConfigs;
|
state.entities.plugins.editorConfigs;
|
||||||
|
|
||||||
|
export const getPluginDependencyConfig = (state: AppState) =>
|
||||||
|
state.entities.plugins.dependencies;
|
||||||
|
|
||||||
export const getPluginSettingConfigs = (state: AppState, pluginId: string) =>
|
export const getPluginSettingConfigs = (state: AppState, pluginId: string) =>
|
||||||
state.entities.plugins.settingConfigs[pluginId];
|
state.entities.plugins.settingConfigs[pluginId];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,11 @@ import {
|
||||||
generateTypeDef,
|
generateTypeDef,
|
||||||
dataTreeTypeDefCreator,
|
dataTreeTypeDefCreator,
|
||||||
} from "utils/autocomplete/dataTreeTypeDefCreator";
|
} from "utils/autocomplete/dataTreeTypeDefCreator";
|
||||||
import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
import {
|
||||||
|
DataTree,
|
||||||
|
ENTITY_TYPE,
|
||||||
|
EvaluationSubstitutionType,
|
||||||
|
} from "entities/DataTree/dataTreeFactory";
|
||||||
import { entityDefinitions } from "utils/autocomplete/EntityDefinitions";
|
import { entityDefinitions } from "utils/autocomplete/EntityDefinitions";
|
||||||
import { WidgetTypes } from "../../constants/WidgetConstants";
|
import { WidgetTypes } from "../../constants/WidgetConstants";
|
||||||
|
|
||||||
|
|
@ -26,7 +30,7 @@ describe("dataTreeTypeDefCreator", () => {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
version: 1,
|
version: 1,
|
||||||
bindingPaths: {
|
bindingPaths: {
|
||||||
defaultText: true,
|
defaultText: EvaluationSubstitutionType.TEMPLATE,
|
||||||
},
|
},
|
||||||
triggerPaths: {
|
triggerPaths: {
|
||||||
onTextChange: true,
|
onTextChange: true,
|
||||||
|
|
|
||||||
|
|
@ -81,272 +81,6 @@ const simpleDSL: any = {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const newDSL: any = {
|
|
||||||
widgetName: "MainContainer",
|
|
||||||
backgroundColor: "none",
|
|
||||||
rightColumn: 1224,
|
|
||||||
snapColumns: 16,
|
|
||||||
detachFromLayout: true,
|
|
||||||
widgetId: "0",
|
|
||||||
topRow: 0,
|
|
||||||
bottomRow: 4320,
|
|
||||||
containerStyle: "none",
|
|
||||||
snapRows: 33,
|
|
||||||
parentRowSpace: 1,
|
|
||||||
type: "CANVAS_WIDGET",
|
|
||||||
canExtend: true,
|
|
||||||
dynamicBindingPathList: [],
|
|
||||||
version: 6,
|
|
||||||
minHeight: 1292,
|
|
||||||
parentColumnSpace: 1,
|
|
||||||
leftColumn: 0,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
isVisible: true,
|
|
||||||
text: "Submit",
|
|
||||||
buttonStyle: "PRIMARY_BUTTON",
|
|
||||||
widgetName: "Button16",
|
|
||||||
isDisabled: false,
|
|
||||||
isDefaultClickDisabled: true,
|
|
||||||
type: "BUTTON_WIDGET",
|
|
||||||
isLoading: false,
|
|
||||||
parentColumnSpace: 74,
|
|
||||||
parentRowSpace: 40,
|
|
||||||
leftColumn: 0,
|
|
||||||
rightColumn: 10,
|
|
||||||
topRow: 43,
|
|
||||||
bottomRow: 50,
|
|
||||||
parentId: "0",
|
|
||||||
widgetId: "77rkwd5hm7",
|
|
||||||
dynamicTriggerPathList: [{ key: "onClick" }],
|
|
||||||
onClick: "{{showModal('Modal1')}}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isVisible: true,
|
|
||||||
text: "Submit",
|
|
||||||
buttonStyle: "PRIMARY_BUTTON",
|
|
||||||
widgetName: "Button17",
|
|
||||||
isDisabled: false,
|
|
||||||
isDefaultClickDisabled: true,
|
|
||||||
type: "BUTTON_WIDGET",
|
|
||||||
isLoading: false,
|
|
||||||
parentColumnSpace: 74,
|
|
||||||
parentRowSpace: 40,
|
|
||||||
leftColumn: 0,
|
|
||||||
rightColumn: 10,
|
|
||||||
topRow: 51,
|
|
||||||
bottomRow: 58,
|
|
||||||
parentId: "0",
|
|
||||||
widgetId: "atvf7cgber",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isVisible: true,
|
|
||||||
text: "Submit",
|
|
||||||
buttonStyle: "PRIMARY_BUTTON",
|
|
||||||
widgetName: "Button20",
|
|
||||||
isDisabled: false,
|
|
||||||
isDefaultClickDisabled: true,
|
|
||||||
type: "BUTTON_WIDGET",
|
|
||||||
isLoading: false,
|
|
||||||
parentColumnSpace: 74,
|
|
||||||
parentRowSpace: 40,
|
|
||||||
leftColumn: 0,
|
|
||||||
rightColumn: 10,
|
|
||||||
topRow: 59,
|
|
||||||
bottomRow: 66,
|
|
||||||
parentId: "0",
|
|
||||||
widgetId: "c09qn063tc",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isVisible: true,
|
|
||||||
text: "Submit",
|
|
||||||
buttonStyle: "PRIMARY_BUTTON",
|
|
||||||
widgetName: "Button21",
|
|
||||||
isDisabled: false,
|
|
||||||
isDefaultClickDisabled: true,
|
|
||||||
type: "BUTTON_WIDGET",
|
|
||||||
isLoading: false,
|
|
||||||
parentColumnSpace: 74,
|
|
||||||
parentRowSpace: 40,
|
|
||||||
leftColumn: 0,
|
|
||||||
rightColumn: 10,
|
|
||||||
topRow: 67,
|
|
||||||
bottomRow: 74,
|
|
||||||
parentId: "0",
|
|
||||||
widgetId: "cu7873x1s6",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isVisible: true,
|
|
||||||
text: "Submit",
|
|
||||||
buttonStyle: "PRIMARY_BUTTON",
|
|
||||||
widgetName: "Button22",
|
|
||||||
isDisabled: false,
|
|
||||||
isDefaultClickDisabled: true,
|
|
||||||
type: "BUTTON_WIDGET",
|
|
||||||
isLoading: false,
|
|
||||||
parentColumnSpace: 74,
|
|
||||||
parentRowSpace: 40,
|
|
||||||
leftColumn: 0,
|
|
||||||
rightColumn: 10,
|
|
||||||
topRow: 75,
|
|
||||||
bottomRow: 82,
|
|
||||||
parentId: "0",
|
|
||||||
widgetId: "qgxdk87yiw",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isVisible: true,
|
|
||||||
text: "Submit",
|
|
||||||
buttonStyle: "PRIMARY_BUTTON",
|
|
||||||
widgetName: "Button23",
|
|
||||||
isDisabled: false,
|
|
||||||
isDefaultClickDisabled: true,
|
|
||||||
type: "BUTTON_WIDGET",
|
|
||||||
isLoading: false,
|
|
||||||
parentColumnSpace: 74,
|
|
||||||
parentRowSpace: 40,
|
|
||||||
leftColumn: 0,
|
|
||||||
rightColumn: 10,
|
|
||||||
topRow: 83,
|
|
||||||
bottomRow: 90,
|
|
||||||
parentId: "0",
|
|
||||||
widgetId: "oeu2eud3q4",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isVisible: true,
|
|
||||||
text: "Submit",
|
|
||||||
buttonStyle: "PRIMARY_BUTTON",
|
|
||||||
widgetName: "Button24",
|
|
||||||
isDisabled: false,
|
|
||||||
isDefaultClickDisabled: true,
|
|
||||||
type: "BUTTON_WIDGET",
|
|
||||||
isLoading: false,
|
|
||||||
parentColumnSpace: 74,
|
|
||||||
parentRowSpace: 40,
|
|
||||||
leftColumn: 0,
|
|
||||||
rightColumn: 10,
|
|
||||||
topRow: 91,
|
|
||||||
bottomRow: 98,
|
|
||||||
parentId: "0",
|
|
||||||
widgetId: "11sgnzdckq",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isVisible: true,
|
|
||||||
text: "Submit",
|
|
||||||
buttonStyle: "PRIMARY_BUTTON",
|
|
||||||
widgetName: "Button25",
|
|
||||||
isDisabled: false,
|
|
||||||
isDefaultClickDisabled: true,
|
|
||||||
type: "BUTTON_WIDGET",
|
|
||||||
isLoading: false,
|
|
||||||
parentColumnSpace: 74,
|
|
||||||
parentRowSpace: 40,
|
|
||||||
leftColumn: 0,
|
|
||||||
rightColumn: 10,
|
|
||||||
topRow: 99,
|
|
||||||
bottomRow: 106,
|
|
||||||
parentId: "0",
|
|
||||||
widgetId: "rs2c4g4g0o",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isVisible: true,
|
|
||||||
text: "Submit",
|
|
||||||
buttonStyle: "PRIMARY_BUTTON",
|
|
||||||
widgetName: "Button13",
|
|
||||||
isDisabled: false,
|
|
||||||
isDefaultClickDisabled: true,
|
|
||||||
type: "BUTTON_WIDGET",
|
|
||||||
isLoading: false,
|
|
||||||
parentColumnSpace: 34.5,
|
|
||||||
parentRowSpace: 40,
|
|
||||||
leftColumn: 7,
|
|
||||||
rightColumn: 9,
|
|
||||||
topRow: 7,
|
|
||||||
bottomRow: 8,
|
|
||||||
parentId: "0",
|
|
||||||
widgetId: "iwsi8fleku",
|
|
||||||
dynamicTriggerPathList: [{ key: "onClick" }],
|
|
||||||
onClick: "{{showModal('Modal1')}}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isVisible: true,
|
|
||||||
shouldScrollContents: false,
|
|
||||||
widgetName: "Tabs1",
|
|
||||||
tabs:
|
|
||||||
'[{"id":"tab2","widgetId":"377zsl4rgg","label":"Tab 2"},{"id":"tab1","widgetId":"9augj62fwd","label":"Tab 1"}]',
|
|
||||||
shouldShowTabs: true,
|
|
||||||
defaultTab: "Tab 1",
|
|
||||||
blueprint: { operations: [{ type: "MODIFY_PROPS" }] },
|
|
||||||
type: "TABS_WIDGET",
|
|
||||||
isLoading: false,
|
|
||||||
parentColumnSpace: 74,
|
|
||||||
parentRowSpace: 40,
|
|
||||||
leftColumn: 3,
|
|
||||||
rightColumn: 11,
|
|
||||||
topRow: 11,
|
|
||||||
bottomRow: 18,
|
|
||||||
parentId: "0",
|
|
||||||
widgetId: "g3s5k86c8v",
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
type: "CANVAS_WIDGET",
|
|
||||||
tabId: "tab2",
|
|
||||||
tabName: "Tab 2",
|
|
||||||
widgetId: "377zsl4rgg",
|
|
||||||
parentId: "g3s5k86c8v",
|
|
||||||
detachFromLayout: true,
|
|
||||||
children: [],
|
|
||||||
parentRowSpace: 1,
|
|
||||||
parentColumnSpace: 1,
|
|
||||||
leftColumn: 0,
|
|
||||||
rightColumn: 592,
|
|
||||||
topRow: 0,
|
|
||||||
bottomRow: 280,
|
|
||||||
isLoading: false,
|
|
||||||
widgetName: "Canvas1",
|
|
||||||
renderMode: "CANVAS",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "CANVAS_WIDGET",
|
|
||||||
tabId: "tab1",
|
|
||||||
tabName: "Tab 1",
|
|
||||||
widgetId: "9augj62fwd",
|
|
||||||
parentId: "g3s5k86c8v",
|
|
||||||
detachFromLayout: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
isVisible: true,
|
|
||||||
text: "Submit",
|
|
||||||
buttonStyle: "PRIMARY_BUTTON",
|
|
||||||
widgetName: "Button26",
|
|
||||||
isDisabled: false,
|
|
||||||
isDefaultClickDisabled: true,
|
|
||||||
type: "BUTTON_WIDGET",
|
|
||||||
isLoading: false,
|
|
||||||
parentColumnSpace: 34.5,
|
|
||||||
parentRowSpace: 40,
|
|
||||||
leftColumn: 2,
|
|
||||||
rightColumn: 4,
|
|
||||||
topRow: 1,
|
|
||||||
bottomRow: 2,
|
|
||||||
parentId: "9augj62fwd",
|
|
||||||
widgetId: "o87mpa118i",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
parentRowSpace: 1,
|
|
||||||
parentColumnSpace: 1,
|
|
||||||
leftColumn: 0,
|
|
||||||
rightColumn: 592,
|
|
||||||
topRow: 0,
|
|
||||||
bottomRow: 280,
|
|
||||||
isLoading: false,
|
|
||||||
widgetName: "Canvas1",
|
|
||||||
renderMode: "CANVAS",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
dynamicBindingPathList: [{ key: "selectedTab" }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
describe("Immutable Canvas structures", () => {
|
describe("Immutable Canvas structures", () => {
|
||||||
it("generates the same object if it is run with the same dsl", () => {
|
it("generates the same object if it is run with the same dsl", () => {
|
||||||
const nextState = compareAndGenerateImmutableCanvasStructure(
|
const nextState = compareAndGenerateImmutableCanvasStructure(
|
||||||
|
|
@ -356,15 +90,6 @@ describe("Immutable Canvas structures", () => {
|
||||||
|
|
||||||
expect(nextState).toBe(canvasStructure);
|
expect(nextState).toBe(canvasStructure);
|
||||||
});
|
});
|
||||||
it("calculates 100 simple diffs in less than 30ms", () => {
|
|
||||||
const start = performance.now();
|
|
||||||
for (let i = 0; i < 100; i++) {
|
|
||||||
compareAndGenerateImmutableCanvasStructure(canvasStructure, newDSL);
|
|
||||||
}
|
|
||||||
console.log("Time taken for 100 runs: ", performance.now() - start, "ms");
|
|
||||||
const timeTaken = performance.now() - start;
|
|
||||||
expect(timeTaken).toBeLessThanOrEqual(100);
|
|
||||||
});
|
|
||||||
it("updates the diff appropriately", () => {
|
it("updates the diff appropriately", () => {
|
||||||
const dsl: any = {
|
const dsl: any = {
|
||||||
widgetId: "x",
|
widgetId: "x",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import {
|
import {
|
||||||
DependencyMap,
|
DependencyMap,
|
||||||
EntityWithBindings,
|
|
||||||
EvalError,
|
EvalError,
|
||||||
EvalErrorTypes,
|
EvalErrorTypes,
|
||||||
getDynamicBindings,
|
getDynamicBindings,
|
||||||
|
|
@ -17,6 +16,7 @@ import {
|
||||||
DataTreeObjectEntity,
|
DataTreeObjectEntity,
|
||||||
DataTreeWidget,
|
DataTreeWidget,
|
||||||
ENTITY_TYPE,
|
ENTITY_TYPE,
|
||||||
|
EvaluationSubstitutionType,
|
||||||
} from "entities/DataTree/dataTreeFactory";
|
} from "entities/DataTree/dataTreeFactory";
|
||||||
import {
|
import {
|
||||||
addDependantsOfNestedPropertyPaths,
|
addDependantsOfNestedPropertyPaths,
|
||||||
|
|
@ -43,6 +43,7 @@ import {
|
||||||
} from "constants/AppsmithActionConstants/ActionConstants";
|
} from "constants/AppsmithActionConstants/ActionConstants";
|
||||||
import { DATA_BIND_REGEX } from "constants/BindingsConstants";
|
import { DATA_BIND_REGEX } from "constants/BindingsConstants";
|
||||||
import evaluate, { EvalResult } from "workers/evaluate";
|
import evaluate, { EvalResult } from "workers/evaluate";
|
||||||
|
import { substituteDynamicBindingWithValues } from "workers/evaluationSubstitution";
|
||||||
|
|
||||||
export default class DataTreeEvaluator {
|
export default class DataTreeEvaluator {
|
||||||
dependencyMap: DependencyMap = {};
|
dependencyMap: DependencyMap = {};
|
||||||
|
|
@ -308,7 +309,6 @@ export default class DataTreeEvaluator {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
// TODO make this run only for widgets and not actions
|
|
||||||
dependencyMap = makeParentsDependOnChildren(dependencyMap);
|
dependencyMap = makeParentsDependOnChildren(dependencyMap);
|
||||||
return dependencyMap;
|
return dependencyMap;
|
||||||
}
|
}
|
||||||
|
|
@ -331,15 +331,26 @@ export default class DataTreeEvaluator {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (entity.ENTITY_TYPE === ENTITY_TYPE.WIDGET) {
|
if (isWidget(entity)) {
|
||||||
// Set default property dependency
|
// Set default property dependency
|
||||||
const defaultProperties = this.widgetConfigMap[entity.type]
|
const defaultProperties = this.widgetConfigMap[entity.type]
|
||||||
.defaultProperties;
|
.defaultProperties;
|
||||||
Object.keys(defaultProperties).forEach((property) => {
|
Object.entries(defaultProperties).forEach(
|
||||||
dependencies[`${entityName}.${property}`] = [
|
([property, defaultPropertyPath]) => {
|
||||||
`${entityName}.${defaultProperties[property]}`,
|
dependencies[`${entityName}.${property}`] = [
|
||||||
];
|
`${entityName}.${defaultPropertyPath}`,
|
||||||
});
|
];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isAction(entity)) {
|
||||||
|
Object.entries(entity.dependencyMap).forEach(
|
||||||
|
([dependent, entityDependencies]) => {
|
||||||
|
dependencies[`${entityName}.${dependent}`] = entityDependencies.map(
|
||||||
|
(propertyPath) => `${entityName}.${propertyPath}`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return dependencies;
|
return dependencies;
|
||||||
}
|
}
|
||||||
|
|
@ -356,7 +367,9 @@ export default class DataTreeEvaluator {
|
||||||
const { entityName, propertyPath } = getEntityNameAndPropertyPath(
|
const { entityName, propertyPath } = getEntityNameAndPropertyPath(
|
||||||
fullPropertyPath,
|
fullPropertyPath,
|
||||||
);
|
);
|
||||||
const entity: DataTreeEntity = currentTree[entityName];
|
const entity = currentTree[entityName] as
|
||||||
|
| DataTreeWidget
|
||||||
|
| DataTreeAction;
|
||||||
const unEvalPropertyValue = _.get(
|
const unEvalPropertyValue = _.get(
|
||||||
currentTree as any,
|
currentTree as any,
|
||||||
fullPropertyPath,
|
fullPropertyPath,
|
||||||
|
|
@ -368,10 +381,15 @@ export default class DataTreeEvaluator {
|
||||||
const requiresEval =
|
const requiresEval =
|
||||||
isABindingPath && isDynamicValue(unEvalPropertyValue);
|
isABindingPath && isDynamicValue(unEvalPropertyValue);
|
||||||
if (requiresEval) {
|
if (requiresEval) {
|
||||||
|
const evaluationSubstitutionType =
|
||||||
|
entity.bindingPaths[propertyPath] ||
|
||||||
|
EvaluationSubstitutionType.TEMPLATE;
|
||||||
try {
|
try {
|
||||||
evalPropertyValue = this.evaluateDynamicProperty(
|
evalPropertyValue = this.getDynamicValue(
|
||||||
currentTree,
|
|
||||||
unEvalPropertyValue,
|
unEvalPropertyValue,
|
||||||
|
currentTree,
|
||||||
|
evaluationSubstitutionType,
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.errors.push({
|
this.errors.push({
|
||||||
|
|
@ -512,6 +530,7 @@ export default class DataTreeEvaluator {
|
||||||
getDynamicValue(
|
getDynamicValue(
|
||||||
dynamicBinding: string,
|
dynamicBinding: string,
|
||||||
data: DataTree,
|
data: DataTree,
|
||||||
|
evaluationSubstitutionType: EvaluationSubstitutionType,
|
||||||
returnTriggers: boolean,
|
returnTriggers: boolean,
|
||||||
callBackData?: Array<any>,
|
callBackData?: Array<any>,
|
||||||
) {
|
) {
|
||||||
|
|
@ -540,10 +559,15 @@ export default class DataTreeEvaluator {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// if it is just one binding, no need to create template string
|
// if it is just one binding, return that directly
|
||||||
if (stringSegments.length === 1) return values[0];
|
if (stringSegments.length === 1) return values[0];
|
||||||
// else return a string template with bindings
|
// else return a combined value according to the evaluation type
|
||||||
return createDynamicValueString(dynamicBinding, stringSegments, values);
|
return substituteDynamicBindingWithValues(
|
||||||
|
dynamicBinding,
|
||||||
|
stringSegments,
|
||||||
|
values,
|
||||||
|
evaluationSubstitutionType,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
@ -569,13 +593,6 @@ export default class DataTreeEvaluator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluateDynamicProperty(
|
|
||||||
currentTree: DataTree,
|
|
||||||
unEvalPropertyValue: any,
|
|
||||||
): any {
|
|
||||||
return this.getDynamicValue(unEvalPropertyValue, currentTree, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
validateAndParseWidgetProperty(
|
validateAndParseWidgetProperty(
|
||||||
fullPropertyPath: string,
|
fullPropertyPath: string,
|
||||||
widget: DataTreeWidget,
|
widget: DataTreeWidget,
|
||||||
|
|
@ -590,6 +607,7 @@ export default class DataTreeEvaluator {
|
||||||
const { triggers } = this.getDynamicValue(
|
const { triggers } = this.getDynamicValue(
|
||||||
unEvalPropertyValue,
|
unEvalPropertyValue,
|
||||||
currentTree,
|
currentTree,
|
||||||
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
true,
|
true,
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
|
|
@ -695,21 +713,37 @@ export default class DataTreeEvaluator {
|
||||||
if (entityType !== "noop") {
|
if (entityType !== "noop") {
|
||||||
switch (dataTreeDiff.event) {
|
switch (dataTreeDiff.event) {
|
||||||
case DataTreeDiffEvent.NEW: {
|
case DataTreeDiffEvent.NEW: {
|
||||||
// If a new widget was added, add all the internal bindings for this widget to the global dependency map
|
// If a new entity/property was added, add all the internal bindings for this entity to the global dependency map
|
||||||
if (
|
if (
|
||||||
isWidget(entity) &&
|
(isWidget(entity) || isAction(entity)) &&
|
||||||
!this.isDynamicLeaf(
|
!this.isDynamicLeaf(
|
||||||
unEvalDataTree,
|
unEvalDataTree,
|
||||||
dataTreeDiff.payload.propertyPath,
|
dataTreeDiff.payload.propertyPath,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
const widgetDependencyMap: DependencyMap = this.listEntityDependencies(
|
const entityDependencyMap: DependencyMap = this.listEntityDependencies(
|
||||||
entity as DataTreeWidget,
|
entity,
|
||||||
entityName,
|
entityName,
|
||||||
);
|
);
|
||||||
if (Object.keys(widgetDependencyMap).length) {
|
if (Object.keys(entityDependencyMap).length) {
|
||||||
didUpdateDependencyMap = true;
|
didUpdateDependencyMap = true;
|
||||||
Object.assign(this.dependencyMap, widgetDependencyMap);
|
// The entity might already have some dependencies,
|
||||||
|
// so we just want to update those
|
||||||
|
Object.entries(entityDependencyMap).forEach(
|
||||||
|
([entityDependent, entityDependencies]) => {
|
||||||
|
if (this.dependencyMap[entityDependent]) {
|
||||||
|
this.dependencyMap[
|
||||||
|
entityDependent
|
||||||
|
] = this.dependencyMap[entityDependent].concat(
|
||||||
|
entityDependencies,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.dependencyMap[
|
||||||
|
entityDependent
|
||||||
|
] = entityDependencies;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Either a new entity or a new property path has been added. Go through existing dynamic bindings and
|
// Either a new entity or a new property path has been added. Go through existing dynamic bindings and
|
||||||
|
|
@ -735,14 +769,14 @@ export default class DataTreeEvaluator {
|
||||||
removedPaths.push(dataTreeDiff.payload.propertyPath);
|
removedPaths.push(dataTreeDiff.payload.propertyPath);
|
||||||
// If an existing widget was deleted, remove all the bindings from the global dependency map
|
// If an existing widget was deleted, remove all the bindings from the global dependency map
|
||||||
if (
|
if (
|
||||||
isWidget(entity) &&
|
(isWidget(entity) || isAction(entity)) &&
|
||||||
dataTreeDiff.payload.propertyPath === entityName
|
dataTreeDiff.payload.propertyPath === entityName
|
||||||
) {
|
) {
|
||||||
const widgetBindings = this.listEntityDependencies(
|
const entityDependencies = this.listEntityDependencies(
|
||||||
entity,
|
entity,
|
||||||
entityName,
|
entityName,
|
||||||
);
|
);
|
||||||
Object.keys(widgetBindings).forEach((widgetDep) => {
|
Object.keys(entityDependencies).forEach((widgetDep) => {
|
||||||
didUpdateDependencyMap = true;
|
didUpdateDependencyMap = true;
|
||||||
delete this.dependencyMap[widgetDep];
|
delete this.dependencyMap[widgetDep];
|
||||||
});
|
});
|
||||||
|
|
@ -783,23 +817,22 @@ export default class DataTreeEvaluator {
|
||||||
}
|
}
|
||||||
|
|
||||||
case DataTreeDiffEvent.EDIT: {
|
case DataTreeDiffEvent.EDIT: {
|
||||||
// We only care about dependencies for a widget. This is because in case a dependency of an action changes,
|
// We only care if the difference is in dynamic bindings since static values do not need
|
||||||
// that shouldn't trigger an evaluation.
|
|
||||||
// Also for a widget, we only care if the difference is in dynamic bindings since static values do not need
|
|
||||||
// an evaluation.
|
// an evaluation.
|
||||||
if (
|
if (
|
||||||
(entityType === ENTITY_TYPE.WIDGET ||
|
(isWidget(entity) || isAction(entity)) &&
|
||||||
entityType === ENTITY_TYPE.ACTION) &&
|
|
||||||
typeof dataTreeDiff.payload.value === "string"
|
typeof dataTreeDiff.payload.value === "string"
|
||||||
) {
|
) {
|
||||||
const entity: EntityWithBindings = unEvalDataTree[
|
const entity: DataTreeAction | DataTreeWidget = unEvalDataTree[
|
||||||
entityName
|
entityName
|
||||||
] as EntityWithBindings;
|
] as DataTreeAction | DataTreeWidget;
|
||||||
|
const fullPropertyPath = dataTreeDiff.payload.propertyPath;
|
||||||
|
const entityPropertyPath = fullPropertyPath.substring(
|
||||||
|
fullPropertyPath.indexOf(".") + 1,
|
||||||
|
);
|
||||||
const isABindingPath = isPathADynamicBinding(
|
const isABindingPath = isPathADynamicBinding(
|
||||||
entity,
|
entity,
|
||||||
dataTreeDiff.payload.propertyPath.substring(
|
entityPropertyPath,
|
||||||
dataTreeDiff.payload.propertyPath.indexOf(".") + 1,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (isABindingPath) {
|
if (isABindingPath) {
|
||||||
didUpdateDependencyMap = true;
|
didUpdateDependencyMap = true;
|
||||||
|
|
@ -813,15 +846,30 @@ export default class DataTreeEvaluator {
|
||||||
// We found a new dynamic binding for this property path. We update the dependency map by overwriting the
|
// We found a new dynamic binding for this property path. We update the dependency map by overwriting the
|
||||||
// dependencies for this property path with the newly found dependencies
|
// dependencies for this property path with the newly found dependencies
|
||||||
if (correctSnippets.length) {
|
if (correctSnippets.length) {
|
||||||
this.dependencyMap[
|
this.dependencyMap[fullPropertyPath] = correctSnippets;
|
||||||
dataTreeDiff.payload.propertyPath
|
|
||||||
] = correctSnippets;
|
|
||||||
} else {
|
} else {
|
||||||
// The dependency on this property path has been removed. Delete this property path from the global
|
// The dependency on this property path has been removed. Delete this property path from the global
|
||||||
// dependency map
|
// dependency map
|
||||||
delete this.dependencyMap[
|
delete this.dependencyMap[fullPropertyPath];
|
||||||
dataTreeDiff.payload.propertyPath
|
}
|
||||||
];
|
if (isAction(entity)) {
|
||||||
|
// Actions have a defined dependency map that should always be maintained
|
||||||
|
if (entityPropertyPath in entity.dependencyMap) {
|
||||||
|
const entityDependenciesName = entity.dependencyMap[
|
||||||
|
entityPropertyPath
|
||||||
|
].map((dep) => `${entityName}.${dep}`);
|
||||||
|
if (fullPropertyPath in this.dependencyMap) {
|
||||||
|
this.dependencyMap[
|
||||||
|
fullPropertyPath
|
||||||
|
] = this.dependencyMap[fullPropertyPath].concat(
|
||||||
|
entityDependenciesName,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.dependencyMap[
|
||||||
|
fullPropertyPath
|
||||||
|
] = entityDependenciesName;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -838,9 +886,11 @@ export default class DataTreeEvaluator {
|
||||||
if (didUpdateDependencyMap) {
|
if (didUpdateDependencyMap) {
|
||||||
// TODO Optimise
|
// TODO Optimise
|
||||||
Object.keys(this.dependencyMap).forEach((key) => {
|
Object.keys(this.dependencyMap).forEach((key) => {
|
||||||
this.dependencyMap[key] = _.flatten(
|
this.dependencyMap[key] = _.uniq(
|
||||||
this.dependencyMap[key].map((path) =>
|
_.flatten(
|
||||||
extractReferencesFromBinding(path, this.allKeys),
|
this.dependencyMap[key].map((path) =>
|
||||||
|
extractReferencesFromBinding(path, this.allKeys),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -1003,6 +1053,7 @@ export default class DataTreeEvaluator {
|
||||||
evaluatedExecutionParams = this.getDynamicValue(
|
evaluatedExecutionParams = this.getDynamicValue(
|
||||||
`{{${JSON.stringify(executionParams)}}}`,
|
`{{${JSON.stringify(executionParams)}}}`,
|
||||||
this.evalTree,
|
this.evalTree,
|
||||||
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1021,6 +1072,7 @@ export default class DataTreeEvaluator {
|
||||||
this.getDynamicValue(
|
this.getDynamicValue(
|
||||||
`{{${binding}}}`,
|
`{{${binding}}}`,
|
||||||
dataTreeWithExecutionParams,
|
dataTreeWithExecutionParams,
|
||||||
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -1070,31 +1122,6 @@ const extractReferencesFromBinding = (
|
||||||
// referencing DATA_BIND_REGEX fails for the value "{{Table1.tableData[Table1.selectedRowIndex]}}" if you run it multiple times and don't recreate
|
// referencing DATA_BIND_REGEX fails for the value "{{Table1.tableData[Table1.selectedRowIndex]}}" if you run it multiple times and don't recreate
|
||||||
const isDynamicValue = (value: string): boolean => DATA_BIND_REGEX.test(value);
|
const isDynamicValue = (value: string): boolean => DATA_BIND_REGEX.test(value);
|
||||||
|
|
||||||
// For creating a final value where bindings could be in a template format
|
|
||||||
const createDynamicValueString = (
|
|
||||||
binding: string,
|
|
||||||
subBindings: string[],
|
|
||||||
subValues: string[],
|
|
||||||
): string => {
|
|
||||||
// Replace the string with the data tree values
|
|
||||||
let finalValue = binding;
|
|
||||||
subBindings.forEach((b, i) => {
|
|
||||||
let value = subValues[i];
|
|
||||||
if (Array.isArray(value) || _.isObject(value)) {
|
|
||||||
value = JSON.stringify(value);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (JSON.parse(value)) {
|
|
||||||
value = value.replace(/\\([\s\S])|(")/g, "\\$1$2");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
finalValue = finalValue.replace(b, value);
|
|
||||||
});
|
|
||||||
return finalValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
function isValidEntity(entity: DataTreeEntity): entity is DataTreeObjectEntity {
|
function isValidEntity(entity: DataTreeEntity): entity is DataTreeObjectEntity {
|
||||||
if (!_.isObject(entity)) {
|
if (!_.isObject(entity)) {
|
||||||
// ERRORS.push({
|
// ERRORS.push({
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import {
|
||||||
DataTreeAction,
|
DataTreeAction,
|
||||||
DataTreeWidget,
|
DataTreeWidget,
|
||||||
ENTITY_TYPE,
|
ENTITY_TYPE,
|
||||||
|
EvaluationSubstitutionType,
|
||||||
} from "../entities/DataTree/dataTreeFactory";
|
} from "../entities/DataTree/dataTreeFactory";
|
||||||
import { WidgetTypeConfigMap } from "../utils/WidgetFactory";
|
import { WidgetTypeConfigMap } from "../utils/WidgetFactory";
|
||||||
import { RenderModes, WidgetTypes } from "../constants/WidgetConstants";
|
import { RenderModes, WidgetTypes } from "../constants/WidgetConstants";
|
||||||
|
|
@ -238,9 +239,10 @@ const BASE_ACTION: DataTreeAction = {
|
||||||
data: {},
|
data: {},
|
||||||
ENTITY_TYPE: ENTITY_TYPE.ACTION,
|
ENTITY_TYPE: ENTITY_TYPE.ACTION,
|
||||||
bindingPaths: {
|
bindingPaths: {
|
||||||
isLoading: true,
|
isLoading: EvaluationSubstitutionType.TEMPLATE,
|
||||||
data: true,
|
data: EvaluationSubstitutionType.TEMPLATE,
|
||||||
},
|
},
|
||||||
|
dependencyMap: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("DataTreeEvaluator", () => {
|
describe("DataTreeEvaluator", () => {
|
||||||
|
|
@ -251,7 +253,10 @@ describe("DataTreeEvaluator", () => {
|
||||||
text: "Label",
|
text: "Label",
|
||||||
type: WidgetTypes.TEXT_WIDGET,
|
type: WidgetTypes.TEXT_WIDGET,
|
||||||
bindingPaths: {
|
bindingPaths: {
|
||||||
text: true,
|
text: EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
},
|
||||||
|
validationPaths: {
|
||||||
|
text: VALIDATION_TYPES.TEXT,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Text2: {
|
Text2: {
|
||||||
|
|
@ -261,7 +266,7 @@ describe("DataTreeEvaluator", () => {
|
||||||
dynamicBindingPathList: [{ key: "text" }],
|
dynamicBindingPathList: [{ key: "text" }],
|
||||||
type: WidgetTypes.TEXT_WIDGET,
|
type: WidgetTypes.TEXT_WIDGET,
|
||||||
bindingPaths: {
|
bindingPaths: {
|
||||||
text: true,
|
text: EvaluationSubstitutionType.TEMPLATE,
|
||||||
},
|
},
|
||||||
validationPaths: {
|
validationPaths: {
|
||||||
text: VALIDATION_TYPES.TEXT,
|
text: VALIDATION_TYPES.TEXT,
|
||||||
|
|
@ -274,7 +279,7 @@ describe("DataTreeEvaluator", () => {
|
||||||
dynamicBindingPathList: [{ key: "text" }],
|
dynamicBindingPathList: [{ key: "text" }],
|
||||||
type: WidgetTypes.TEXT_WIDGET,
|
type: WidgetTypes.TEXT_WIDGET,
|
||||||
bindingPaths: {
|
bindingPaths: {
|
||||||
text: true,
|
text: EvaluationSubstitutionType.TEMPLATE,
|
||||||
},
|
},
|
||||||
validationPaths: {
|
validationPaths: {
|
||||||
text: VALIDATION_TYPES.TEXT,
|
text: VALIDATION_TYPES.TEXT,
|
||||||
|
|
@ -294,18 +299,18 @@ describe("DataTreeEvaluator", () => {
|
||||||
],
|
],
|
||||||
type: WidgetTypes.DROP_DOWN_WIDGET,
|
type: WidgetTypes.DROP_DOWN_WIDGET,
|
||||||
bindingPaths: {
|
bindingPaths: {
|
||||||
options: true,
|
options: EvaluationSubstitutionType.TEMPLATE,
|
||||||
defaultOptionValue: true,
|
defaultOptionValue: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isRequired: true,
|
isRequired: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isVisible: true,
|
isVisible: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isDisabled: true,
|
isDisabled: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isValid: true,
|
isValid: EvaluationSubstitutionType.TEMPLATE,
|
||||||
selectedOption: true,
|
selectedOption: EvaluationSubstitutionType.TEMPLATE,
|
||||||
selectedOptionArr: true,
|
selectedOptionArr: EvaluationSubstitutionType.TEMPLATE,
|
||||||
selectedIndex: true,
|
selectedIndex: EvaluationSubstitutionType.TEMPLATE,
|
||||||
selectedIndexArr: true,
|
selectedIndexArr: EvaluationSubstitutionType.TEMPLATE,
|
||||||
value: true,
|
value: EvaluationSubstitutionType.TEMPLATE,
|
||||||
selectedOptionValues: true,
|
selectedOptionValues: EvaluationSubstitutionType.TEMPLATE,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Table1: {
|
Table1: {
|
||||||
|
|
@ -314,9 +319,9 @@ describe("DataTreeEvaluator", () => {
|
||||||
dynamicBindingPathList: [{ key: "tableData" }],
|
dynamicBindingPathList: [{ key: "tableData" }],
|
||||||
type: WidgetTypes.TABLE_WIDGET,
|
type: WidgetTypes.TABLE_WIDGET,
|
||||||
bindingPaths: {
|
bindingPaths: {
|
||||||
tableData: true,
|
tableData: EvaluationSubstitutionType.TEMPLATE,
|
||||||
selectedRow: true,
|
selectedRow: EvaluationSubstitutionType.TEMPLATE,
|
||||||
selectedRows: true,
|
selectedRows: EvaluationSubstitutionType.TEMPLATE,
|
||||||
},
|
},
|
||||||
validationPaths: {
|
validationPaths: {
|
||||||
tableData: VALIDATION_TYPES.TABLE_DATA,
|
tableData: VALIDATION_TYPES.TABLE_DATA,
|
||||||
|
|
@ -328,7 +333,7 @@ describe("DataTreeEvaluator", () => {
|
||||||
dynamicBindingPathList: [{ key: "text" }],
|
dynamicBindingPathList: [{ key: "text" }],
|
||||||
type: WidgetTypes.TEXT_WIDGET,
|
type: WidgetTypes.TEXT_WIDGET,
|
||||||
bindingPaths: {
|
bindingPaths: {
|
||||||
text: true,
|
text: EvaluationSubstitutionType.TEMPLATE,
|
||||||
},
|
},
|
||||||
validationPaths: {
|
validationPaths: {
|
||||||
text: VALIDATION_TYPES.TEXT,
|
text: VALIDATION_TYPES.TEXT,
|
||||||
|
|
@ -431,10 +436,10 @@ describe("DataTreeEvaluator", () => {
|
||||||
widgetName: "Input1",
|
widgetName: "Input1",
|
||||||
type: WidgetTypes.INPUT_WIDGET,
|
type: WidgetTypes.INPUT_WIDGET,
|
||||||
bindingPaths: {
|
bindingPaths: {
|
||||||
defaultText: true,
|
defaultText: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isValid: true,
|
isValid: EvaluationSubstitutionType.TEMPLATE,
|
||||||
value: true,
|
value: EvaluationSubstitutionType.TEMPLATE,
|
||||||
text: true,
|
text: EvaluationSubstitutionType.TEMPLATE,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -460,18 +465,18 @@ describe("DataTreeEvaluator", () => {
|
||||||
],
|
],
|
||||||
type: WidgetTypes.DROP_DOWN_WIDGET,
|
type: WidgetTypes.DROP_DOWN_WIDGET,
|
||||||
bindingPaths: {
|
bindingPaths: {
|
||||||
options: true,
|
options: EvaluationSubstitutionType.TEMPLATE,
|
||||||
defaultOptionValue: true,
|
defaultOptionValue: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isRequired: true,
|
isRequired: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isVisible: true,
|
isVisible: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isDisabled: true,
|
isDisabled: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isValid: true,
|
isValid: EvaluationSubstitutionType.TEMPLATE,
|
||||||
selectedOption: true,
|
selectedOption: EvaluationSubstitutionType.TEMPLATE,
|
||||||
selectedOptionArr: true,
|
selectedOptionArr: EvaluationSubstitutionType.TEMPLATE,
|
||||||
selectedIndex: true,
|
selectedIndex: EvaluationSubstitutionType.TEMPLATE,
|
||||||
selectedIndexArr: true,
|
selectedIndexArr: EvaluationSubstitutionType.TEMPLATE,
|
||||||
value: true,
|
value: EvaluationSubstitutionType.TEMPLATE,
|
||||||
selectedOptionValues: true,
|
selectedOptionValues: EvaluationSubstitutionType.TEMPLATE,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -604,4 +609,86 @@ describe("DataTreeEvaluator", () => {
|
||||||
"Text4.text": ["Table1.selectedRow.test"],
|
"Text4.text": ["Table1.selectedRow.test"],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Honors predefined action dependencyMap", () => {
|
||||||
|
const updatedTree1 = {
|
||||||
|
...unEvalTree,
|
||||||
|
Text1: {
|
||||||
|
...BASE_WIDGET,
|
||||||
|
text: "Test",
|
||||||
|
},
|
||||||
|
Api2: {
|
||||||
|
...BASE_ACTION,
|
||||||
|
dependencyMap: {
|
||||||
|
"config.body": ["config.pluginSpecifiedTemplates[0].value"],
|
||||||
|
},
|
||||||
|
bindingPaths: {
|
||||||
|
...BASE_ACTION.bindingPaths,
|
||||||
|
"config.body": EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
...BASE_ACTION.config,
|
||||||
|
body: "",
|
||||||
|
pluginSpecifiedTemplates: [
|
||||||
|
{
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
evaluator.updateDataTree(updatedTree1);
|
||||||
|
expect(evaluator.dependencyMap["Api2.config.body"]).toStrictEqual([
|
||||||
|
"Api2.config.pluginSpecifiedTemplates[0].value",
|
||||||
|
]);
|
||||||
|
const updatedTree2 = {
|
||||||
|
...updatedTree1,
|
||||||
|
Api2: {
|
||||||
|
...updatedTree1.Api2,
|
||||||
|
dynamicBindingPathList: [
|
||||||
|
{
|
||||||
|
key: "config.body",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
config: {
|
||||||
|
...updatedTree1.Api2.config,
|
||||||
|
body: "{ 'name': {{ Text1.text }} }",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const evaluatedDataTree2 = evaluator.updateDataTree(updatedTree2);
|
||||||
|
expect(evaluator.dependencyMap["Api2.config.body"]).toStrictEqual([
|
||||||
|
"Text1.text",
|
||||||
|
"Api2.config.pluginSpecifiedTemplates[0].value",
|
||||||
|
]);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
expect(evaluatedDataTree2.Api2.config.body).toBe("{ 'name': Test }");
|
||||||
|
const updatedTree3 = {
|
||||||
|
...updatedTree2,
|
||||||
|
Api2: {
|
||||||
|
...updatedTree2.Api2,
|
||||||
|
bindingPaths: {
|
||||||
|
...updatedTree2.Api2.bindingPaths,
|
||||||
|
"config.body": EvaluationSubstitutionType.SMART_SUBSTITUTE,
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
...updatedTree2.Api2.config,
|
||||||
|
pluginSpecifiedTemplates: [
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const evaluatedDataTree3 = evaluator.updateDataTree(updatedTree3);
|
||||||
|
expect(evaluator.dependencyMap["Api2.config.body"]).toStrictEqual([
|
||||||
|
"Text1.text",
|
||||||
|
"Api2.config.pluginSpecifiedTemplates[0].value",
|
||||||
|
]);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
expect(evaluatedDataTree3.Api2.config.body).toBe("{ 'name': \"Test\" }");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
import {
|
||||||
|
DataTree,
|
||||||
|
EvaluationSubstitutionType,
|
||||||
|
} from "entities/DataTree/dataTreeFactory";
|
||||||
import {
|
import {
|
||||||
DependencyMap,
|
DependencyMap,
|
||||||
EVAL_WORKER_ACTIONS,
|
EVAL_WORKER_ACTIONS,
|
||||||
|
|
@ -109,6 +112,7 @@ ctx.addEventListener(
|
||||||
const triggers = dataTreeEvaluator.getDynamicValue(
|
const triggers = dataTreeEvaluator.getDynamicValue(
|
||||||
dynamicTrigger,
|
dynamicTrigger,
|
||||||
evalTree,
|
evalTree,
|
||||||
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
true,
|
true,
|
||||||
callbackData,
|
callbackData,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
339
app/client/src/workers/evaluationSubstitution.test.ts
Normal file
339
app/client/src/workers/evaluationSubstitution.test.ts
Normal file
|
|
@ -0,0 +1,339 @@
|
||||||
|
import { substituteDynamicBindingWithValues } from "workers/evaluationSubstitution";
|
||||||
|
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||||
|
|
||||||
|
describe("substituteDynamicBindingWithValues", () => {
|
||||||
|
describe("template substitution", () => {
|
||||||
|
it("substitutes strings values", () => {
|
||||||
|
const binding = "Hello {{name}}";
|
||||||
|
const subBindings = ["Hello ", "{{name}}"];
|
||||||
|
const subValues = ["Hello ", "Tester"];
|
||||||
|
const expected = "Hello Tester";
|
||||||
|
const result = substituteDynamicBindingWithValues(
|
||||||
|
binding,
|
||||||
|
subBindings,
|
||||||
|
subValues,
|
||||||
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("substitute number values", () => {
|
||||||
|
const binding = "My age is {{age}}";
|
||||||
|
const subBindings = ["My age is ", "{{age}}"];
|
||||||
|
const subValues = ["My age is ", 16];
|
||||||
|
const expected = "My age is 16";
|
||||||
|
const result = substituteDynamicBindingWithValues(
|
||||||
|
binding,
|
||||||
|
subBindings,
|
||||||
|
subValues,
|
||||||
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("substitute objects/ arrays values", () => {
|
||||||
|
const binding = "Response was {{response}}";
|
||||||
|
const subBindings = ["Response was ", "{{response}}"];
|
||||||
|
const subValues = ["Response was ", { message: "Unauthorised user" }];
|
||||||
|
const expected = 'Response was {\\"message\\":\\"Unauthorised user\\"}';
|
||||||
|
const result = substituteDynamicBindingWithValues(
|
||||||
|
binding,
|
||||||
|
subBindings,
|
||||||
|
subValues,
|
||||||
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("substitute multiple values values", () => {
|
||||||
|
const binding =
|
||||||
|
"My name is {{name}}. My age is {{age}}. Response: {{response}}";
|
||||||
|
const subBindings = [
|
||||||
|
"My name is ",
|
||||||
|
"{{name}}",
|
||||||
|
". My age is ",
|
||||||
|
"{{age}}",
|
||||||
|
". Response: ",
|
||||||
|
"{{response}}",
|
||||||
|
];
|
||||||
|
const subValues = [
|
||||||
|
"My name is ",
|
||||||
|
"Tester",
|
||||||
|
". My age is ",
|
||||||
|
16,
|
||||||
|
". Response: ",
|
||||||
|
{ message: "Unauthorised user" },
|
||||||
|
];
|
||||||
|
const expected =
|
||||||
|
'My name is Tester. My age is 16. Response: {\\"message\\":\\"Unauthorised user\\"}';
|
||||||
|
const result = substituteDynamicBindingWithValues(
|
||||||
|
binding,
|
||||||
|
subBindings,
|
||||||
|
subValues,
|
||||||
|
EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("parameter substitution", () => {
|
||||||
|
it("replaces bindings with $variables", () => {
|
||||||
|
const binding = "SELECT * from {{tableName}} LIMIT {{limit}}";
|
||||||
|
const subBindings = [
|
||||||
|
"SELECT * from ",
|
||||||
|
"{{tableName}}",
|
||||||
|
" LIMIT ",
|
||||||
|
"{{limit}}",
|
||||||
|
];
|
||||||
|
const subValues = ["SELECT * from ", "users", " LIMIT ", 10];
|
||||||
|
const expected = {
|
||||||
|
value: "SELECT * from $1 LIMIT $2",
|
||||||
|
parameters: {
|
||||||
|
$1: "users",
|
||||||
|
$2: 10,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = substituteDynamicBindingWithValues(
|
||||||
|
binding,
|
||||||
|
subBindings,
|
||||||
|
subValues,
|
||||||
|
EvaluationSubstitutionType.PARAMETER,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toHaveProperty("value");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
expect(result.value).toBe(expected.value);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
expect(result.parameters).toStrictEqual(expected.parameters);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removed quotes around bindings", () => {
|
||||||
|
const binding =
|
||||||
|
'SELECT * from users WHERE lastname = "{{lastname}}" LIMIT {{limit}}';
|
||||||
|
const subBindings = [
|
||||||
|
'SELECT * from users WHERE lastname = "',
|
||||||
|
"{{lastname}}",
|
||||||
|
'" LIMIT ',
|
||||||
|
"{{limit}}",
|
||||||
|
];
|
||||||
|
const subValues = [
|
||||||
|
'SELECT * from users WHERE lastname = "',
|
||||||
|
"Smith",
|
||||||
|
'" LIMIT ',
|
||||||
|
10,
|
||||||
|
];
|
||||||
|
const expected = {
|
||||||
|
value: "SELECT * from users WHERE lastname = $1 LIMIT $2",
|
||||||
|
parameters: {
|
||||||
|
$1: "Smith",
|
||||||
|
$2: 10,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = substituteDynamicBindingWithValues(
|
||||||
|
binding,
|
||||||
|
subBindings,
|
||||||
|
subValues,
|
||||||
|
EvaluationSubstitutionType.PARAMETER,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toHaveProperty("value");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
expect(result.value).toBe(expected.value);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
expect(result.parameters).toStrictEqual(expected.parameters);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stringifies objects and arrays", () => {
|
||||||
|
const binding = "SELECT * from {{testObject}} WHERE {{testArray}}";
|
||||||
|
const subBindings = [
|
||||||
|
"SELECT * from ",
|
||||||
|
"{{testObject}}",
|
||||||
|
" WHERE ",
|
||||||
|
"{{testArray}}",
|
||||||
|
];
|
||||||
|
const subValues = [
|
||||||
|
"SELECT * from ",
|
||||||
|
{ name: "tester" },
|
||||||
|
" WHERE ",
|
||||||
|
[42, "meaning", false],
|
||||||
|
];
|
||||||
|
const expected = {
|
||||||
|
value: "SELECT * from $1 WHERE $2",
|
||||||
|
parameters: {
|
||||||
|
$1: `{\n \"name\": \"tester\"\n}`,
|
||||||
|
$2: `[\n 42,\n \"meaning\",\n false\n]`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = substituteDynamicBindingWithValues(
|
||||||
|
binding,
|
||||||
|
subBindings,
|
||||||
|
subValues,
|
||||||
|
EvaluationSubstitutionType.PARAMETER,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toHaveProperty("value");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
expect(result.value).toBe(expected.value);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
expect(result.parameters).toStrictEqual(expected.parameters);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("smart substitution", () => {
|
||||||
|
it("substitutes strings, numbers, boolean, undefined, null values correctly", () => {
|
||||||
|
const binding = `{
|
||||||
|
"name": {{name}},
|
||||||
|
"age": {{age}},
|
||||||
|
"isHuman": {{isHuman}},
|
||||||
|
"wrongBinding": {{wrongBinding}},
|
||||||
|
"emptyBinding": {{emptyBinding}},
|
||||||
|
}`;
|
||||||
|
const subBindings = [
|
||||||
|
'{\n "name": ',
|
||||||
|
"{{name}}",
|
||||||
|
',\n "age": ',
|
||||||
|
"{{age}}",
|
||||||
|
',\n "isHuman": ',
|
||||||
|
"{{isHuman}}",
|
||||||
|
',\n "wrongBinding": ',
|
||||||
|
"{{wrongBinding}}",
|
||||||
|
',\n "emptyBinding": ',
|
||||||
|
"{{emptyBinding}}",
|
||||||
|
",\n }",
|
||||||
|
];
|
||||||
|
const subValues = [
|
||||||
|
'{\n "name": ',
|
||||||
|
"Tester",
|
||||||
|
',\n "age": ',
|
||||||
|
42,
|
||||||
|
',\n "isHuman": ',
|
||||||
|
false,
|
||||||
|
',\n "wrongBinding": ',
|
||||||
|
undefined,
|
||||||
|
',\n "emptyBinding": ',
|
||||||
|
null,
|
||||||
|
",\n }",
|
||||||
|
];
|
||||||
|
const expected = `{
|
||||||
|
"name": "Tester",
|
||||||
|
"age": 42,
|
||||||
|
"isHuman": false,
|
||||||
|
"wrongBinding": undefined,
|
||||||
|
"emptyBinding": null,
|
||||||
|
}`;
|
||||||
|
const result = substituteDynamicBindingWithValues(
|
||||||
|
binding,
|
||||||
|
subBindings,
|
||||||
|
subValues,
|
||||||
|
EvaluationSubstitutionType.SMART_SUBSTITUTE,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("substitute objects/ arrays values", () => {
|
||||||
|
const binding = `{\n "data": {{formData}}\n}`;
|
||||||
|
const subBindings = ["{\n data: ", "{{formData}}", "\n}"];
|
||||||
|
const subValues = [
|
||||||
|
'{\n "data": ',
|
||||||
|
{
|
||||||
|
name: "Tester",
|
||||||
|
age: 42,
|
||||||
|
isHuman: false,
|
||||||
|
wrongBinding: undefined,
|
||||||
|
emptyBinding: null,
|
||||||
|
},
|
||||||
|
"\n}",
|
||||||
|
];
|
||||||
|
const expected =
|
||||||
|
'{\n "data": {\n "name": "Tester",\n "age": 42,\n "isHuman": false,\n "emptyBinding": null\n}\n}';
|
||||||
|
const result = substituteDynamicBindingWithValues(
|
||||||
|
binding,
|
||||||
|
subBindings,
|
||||||
|
subValues,
|
||||||
|
EvaluationSubstitutionType.SMART_SUBSTITUTE,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("substitute correctly when quotes are surrounding the binding", () => {
|
||||||
|
const binding = `{
|
||||||
|
"name": "{{name}}",
|
||||||
|
"age": "{{age}}",
|
||||||
|
isHuman: {{isHuman}},
|
||||||
|
"wrongBinding": {{wrongBinding}},
|
||||||
|
"emptyBinding": "{{emptyBinding}}",
|
||||||
|
}`;
|
||||||
|
const subBindings = [
|
||||||
|
'{\n "name": "',
|
||||||
|
"{{name}}",
|
||||||
|
'",\n "age": "',
|
||||||
|
"{{age}}",
|
||||||
|
'",\n isHuman: ',
|
||||||
|
"{{isHuman}}",
|
||||||
|
',\n "wrongBinding": ',
|
||||||
|
"{{wrongBinding}}",
|
||||||
|
',\n "emptyBinding": "',
|
||||||
|
"{{emptyBinding}}",
|
||||||
|
'",\n }',
|
||||||
|
];
|
||||||
|
const subValues = [
|
||||||
|
'{\n "name": "',
|
||||||
|
"Tester",
|
||||||
|
'",\n "age": "',
|
||||||
|
42,
|
||||||
|
'",\n isHuman: ',
|
||||||
|
false,
|
||||||
|
',\n "wrongBinding": ',
|
||||||
|
undefined,
|
||||||
|
',\n "emptyBinding": "',
|
||||||
|
null,
|
||||||
|
'",\n }',
|
||||||
|
];
|
||||||
|
const expected = `{
|
||||||
|
"name": "Tester",
|
||||||
|
"age": 42,
|
||||||
|
isHuman: false,
|
||||||
|
"wrongBinding": undefined,
|
||||||
|
"emptyBinding": null,
|
||||||
|
}`;
|
||||||
|
debugger;
|
||||||
|
const result = substituteDynamicBindingWithValues(
|
||||||
|
binding,
|
||||||
|
subBindings,
|
||||||
|
subValues,
|
||||||
|
EvaluationSubstitutionType.SMART_SUBSTITUTE,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("escapes strings before substitution", () => {
|
||||||
|
const binding = `{\n "paragraph": {{paragraph}},\n}`;
|
||||||
|
const subBindings = ['{\n "paragraph": ', "{{paragraph}}", ",\n}"];
|
||||||
|
const subValues = [
|
||||||
|
'{\n "paragraph": ',
|
||||||
|
`This is a \f string \b with \n many different " characters that are not \n all. these \r\t`,
|
||||||
|
",\n}",
|
||||||
|
];
|
||||||
|
const expected = `{\n "paragraph": "This is a \\f string \\b with \\n many different \\" characters that are not \\n all. these \\r\\t",\n}`;
|
||||||
|
const result = substituteDynamicBindingWithValues(
|
||||||
|
binding,
|
||||||
|
subBindings,
|
||||||
|
subValues,
|
||||||
|
EvaluationSubstitutionType.SMART_SUBSTITUTE,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
147
app/client/src/workers/evaluationSubstitution.ts
Normal file
147
app/client/src/workers/evaluationSubstitution.ts
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
import { getType, Types } from "utils/TypeHelpers";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||||
|
import { isDynamicValue } from "utils/DynamicBindingUtils";
|
||||||
|
import { QUOTED_BINDING_REGEX } from "constants/BindingsConstants";
|
||||||
|
|
||||||
|
const filterBindingSegmentsAndRemoveQuotes = (
|
||||||
|
binding: string,
|
||||||
|
subSegments: string[],
|
||||||
|
subSegmentValues: unknown[],
|
||||||
|
) => {
|
||||||
|
const bindingStrippedQuotes = binding.replace(
|
||||||
|
QUOTED_BINDING_REGEX,
|
||||||
|
(original, firstGroup) => {
|
||||||
|
return firstGroup;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const subBindings: string[] = [];
|
||||||
|
const subValues: unknown[] = [];
|
||||||
|
subSegments.forEach((segment, i) => {
|
||||||
|
if (isDynamicValue(segment)) {
|
||||||
|
subBindings.push(segment);
|
||||||
|
subValues.push(subSegmentValues[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { binding: bindingStrippedQuotes, subBindings, subValues };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const smartSubstituteDynamicValues = (
|
||||||
|
originalBinding: string,
|
||||||
|
subSegments: string[],
|
||||||
|
subSegmentValues: unknown[],
|
||||||
|
): string => {
|
||||||
|
const {
|
||||||
|
binding,
|
||||||
|
subValues,
|
||||||
|
subBindings,
|
||||||
|
} = filterBindingSegmentsAndRemoveQuotes(
|
||||||
|
originalBinding,
|
||||||
|
subSegments,
|
||||||
|
subSegmentValues,
|
||||||
|
);
|
||||||
|
let finalBinding = binding;
|
||||||
|
subBindings.forEach((b, i) => {
|
||||||
|
const value = subValues[i];
|
||||||
|
switch (getType(value)) {
|
||||||
|
case Types.NUMBER:
|
||||||
|
case Types.BOOLEAN:
|
||||||
|
case Types.NULL:
|
||||||
|
case Types.UNDEFINED:
|
||||||
|
// Direct substitution
|
||||||
|
finalBinding = finalBinding.replace(b, `${value}`);
|
||||||
|
break;
|
||||||
|
case Types.STRING:
|
||||||
|
// Add quotes to a string
|
||||||
|
// JSON.stringify string to escape any unsupported characters
|
||||||
|
finalBinding = finalBinding.replace(b, `${JSON.stringify(value)}`);
|
||||||
|
break;
|
||||||
|
case Types.ARRAY:
|
||||||
|
case Types.OBJECT:
|
||||||
|
// Stringify and substitute
|
||||||
|
finalBinding = finalBinding.replace(b, JSON.stringify(value, null, 2));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return finalBinding;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parameterSubstituteDynamicValues = (
|
||||||
|
originalBinding: string,
|
||||||
|
subSegments: string[],
|
||||||
|
subSegmentValues: unknown[],
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
binding,
|
||||||
|
subValues,
|
||||||
|
subBindings,
|
||||||
|
} = filterBindingSegmentsAndRemoveQuotes(
|
||||||
|
originalBinding,
|
||||||
|
subSegments,
|
||||||
|
subSegmentValues,
|
||||||
|
);
|
||||||
|
let finalBinding = binding;
|
||||||
|
const parameters: Record<string, unknown> = {};
|
||||||
|
subBindings.forEach((b, i) => {
|
||||||
|
// Replace binding with $1, $2;
|
||||||
|
const key = `$${i + 1}`;
|
||||||
|
finalBinding = finalBinding.replace(b, key);
|
||||||
|
parameters[key] =
|
||||||
|
typeof subValues[i] === "object"
|
||||||
|
? JSON.stringify(subValues[i], null, 2)
|
||||||
|
: subValues[i];
|
||||||
|
});
|
||||||
|
return { value: finalBinding, parameters };
|
||||||
|
};
|
||||||
|
// For creating a final value where bindings could be in a template format
|
||||||
|
export const templateSubstituteDynamicValues = (
|
||||||
|
binding: string,
|
||||||
|
subBindings: string[],
|
||||||
|
subValues: unknown[],
|
||||||
|
): string => {
|
||||||
|
// Replace the string with the data tree values
|
||||||
|
let finalValue = binding;
|
||||||
|
subBindings.forEach((b, i) => {
|
||||||
|
let value = subValues[i];
|
||||||
|
if (Array.isArray(value) || _.isObject(value)) {
|
||||||
|
value = JSON.stringify(value);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (typeof value === "string" && JSON.parse(value)) {
|
||||||
|
value = value.replace(/\\([\s\S])|(")/g, "\\$1$2");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
finalValue = finalValue.replace(b, `${value}`);
|
||||||
|
});
|
||||||
|
return finalValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const substituteDynamicBindingWithValues = (
|
||||||
|
binding: string,
|
||||||
|
subSegments: string[],
|
||||||
|
subSegmentValues: unknown[],
|
||||||
|
evaluationSubstitutionType: EvaluationSubstitutionType,
|
||||||
|
): string | { value: string; parameters: Record<string, unknown> } => {
|
||||||
|
switch (evaluationSubstitutionType) {
|
||||||
|
case EvaluationSubstitutionType.TEMPLATE:
|
||||||
|
return templateSubstituteDynamicValues(
|
||||||
|
binding,
|
||||||
|
subSegments,
|
||||||
|
subSegmentValues,
|
||||||
|
);
|
||||||
|
case EvaluationSubstitutionType.SMART_SUBSTITUTE:
|
||||||
|
return smartSubstituteDynamicValues(
|
||||||
|
binding,
|
||||||
|
subSegments,
|
||||||
|
subSegmentValues,
|
||||||
|
);
|
||||||
|
case EvaluationSubstitutionType.PARAMETER:
|
||||||
|
return parameterSubstituteDynamicValues(
|
||||||
|
binding,
|
||||||
|
subSegments,
|
||||||
|
subSegmentValues,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -56,6 +56,7 @@ describe("Add functions", () => {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
run: {},
|
run: {},
|
||||||
ENTITY_TYPE: ENTITY_TYPE.ACTION,
|
ENTITY_TYPE: ENTITY_TYPE.ACTION,
|
||||||
|
dependencyMap: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const dataTreeWithFunctions = addFunctions(dataTree);
|
const dataTreeWithFunctions = addFunctions(dataTree);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user