Introduce different evaluation types for different binding fields (#3834)
This commit is contained in:
parent
05e935b0a0
commit
c92eb0e5a4
|
|
@ -3,6 +3,8 @@ import {
|
|||
ReduxActionTypes,
|
||||
ReduxAction,
|
||||
ReduxActionErrorTypes,
|
||||
EvaluationReduxAction,
|
||||
ReduxActionWithoutPayload,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { Action } from "entities/Action";
|
||||
import { batchAction } from "actions/batchActions";
|
||||
|
|
@ -27,10 +29,12 @@ export type FetchActionsPayload = {
|
|||
|
||||
export const fetchActions = (
|
||||
applicationId: string,
|
||||
): ReduxAction<FetchActionsPayload> => {
|
||||
postEvalActions: Array<ReduxAction<unknown> | ReduxActionWithoutPayload>,
|
||||
): EvaluationReduxAction<unknown> => {
|
||||
return {
|
||||
type: ReduxActionTypes.FETCH_ACTIONS_INIT,
|
||||
payload: { applicationId },
|
||||
postEvalActions,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
EvaluationReduxAction,
|
||||
ReduxAction,
|
||||
ReduxActionTypes,
|
||||
ReduxActionWithoutPayload,
|
||||
UpdateCanvasPayload,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
|
|
@ -46,18 +47,14 @@ export const fetchPublishedPage = (pageId: string, bustCache = false) => ({
|
|||
},
|
||||
});
|
||||
|
||||
export const fetchPageSuccess = (
|
||||
postEvalActions: ReduxAction<unknown>[],
|
||||
): EvaluationReduxAction<unknown> => {
|
||||
export const fetchPageSuccess = (): ReduxActionWithoutPayload => {
|
||||
return {
|
||||
type: ReduxActionTypes.FETCH_PAGE_SUCCESS,
|
||||
payload: {},
|
||||
postEvalActions,
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchPublishedPageSuccess = (
|
||||
postEvalActions: ReduxAction<unknown>[],
|
||||
postEvalActions: Array<ReduxAction<unknown> | ReduxActionWithoutPayload>,
|
||||
): EvaluationReduxAction<undefined> => ({
|
||||
type: ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS,
|
||||
postEvalActions,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
ReduxActionWithoutPayload,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { PluginFormPayload } from "api/PluginApi";
|
||||
import { DependencyMap } from "utils/DynamicBindingUtils";
|
||||
|
||||
export const fetchPlugins = (): ReduxActionWithoutPayload => ({
|
||||
type: ReduxActionTypes.FETCH_PLUGINS_REQUEST,
|
||||
|
|
@ -17,6 +18,7 @@ export type PluginFormsPayload = {
|
|||
formConfigs: Record<string, any[]>;
|
||||
editorConfigs: Record<string, any[]>;
|
||||
settingConfigs: Record<string, any[]>;
|
||||
dependencies: Record<string, DependencyMap>;
|
||||
};
|
||||
|
||||
export const fetchPluginFormConfigsSuccess = (
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ import {
|
|||
ReduxActionTypes,
|
||||
ReduxAction,
|
||||
ReduxActionErrorTypes,
|
||||
ReduxActionWithoutPayload,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import {
|
||||
ExecuteActionPayload,
|
||||
ExecuteErrorPayload,
|
||||
PageAction,
|
||||
} from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import { BatchAction, batchAction } from "actions/batchActions";
|
||||
import PerformanceTracker, {
|
||||
|
|
@ -30,11 +30,8 @@ export const executeActionError = (
|
|||
};
|
||||
};
|
||||
|
||||
export const executePageLoadActions = (
|
||||
payload: PageAction[][],
|
||||
): ReduxAction<PageAction[][]> => ({
|
||||
export const executePageLoadActions = (): ReduxActionWithoutPayload => ({
|
||||
type: ReduxActionTypes.EXECUTE_PAGE_LOAD_ACTIONS,
|
||||
payload,
|
||||
});
|
||||
|
||||
export const disableDragAction = (
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import Api from "api/Api";
|
|||
import { AxiosPromise } from "axios";
|
||||
import { GenericApiResponse } from "api/ApiResponses";
|
||||
import { PluginType } from "entities/Action";
|
||||
import { DependencyMap } from "utils/DynamicBindingUtils";
|
||||
|
||||
export interface Plugin {
|
||||
id: string;
|
||||
|
|
@ -21,6 +22,7 @@ export interface PluginFormPayload {
|
|||
form: any[];
|
||||
editor: any[];
|
||||
setting: any[];
|
||||
dependencies: DependencyMap;
|
||||
}
|
||||
|
||||
class PluginsApi extends Api {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import { theme } from "constants/DefaultTheme";
|
|||
import { Placement } from "popper.js";
|
||||
import ScrollIndicator from "components/ads/ScrollIndicator";
|
||||
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`
|
||||
position: relative;
|
||||
|
|
@ -112,6 +115,7 @@ interface Props {
|
|||
error?: string;
|
||||
useValidationMessage?: boolean;
|
||||
hideEvaluatedValue?: boolean;
|
||||
evaluationSubstitutionType?: EvaluationSubstitutionType;
|
||||
}
|
||||
|
||||
interface PopoverContentProps {
|
||||
|
|
@ -124,12 +128,56 @@ interface PopoverContentProps {
|
|||
onMouseEnter: () => void;
|
||||
onMouseLeave: () => void;
|
||||
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: {
|
||||
theme: EditorTheme;
|
||||
evaluatedValue: any;
|
||||
hideLabel?: boolean;
|
||||
preparedStatementViewer?: boolean;
|
||||
}) => {
|
||||
const currentValueWrapperRef = React.createRef<HTMLDivElement>();
|
||||
const codeWrapperRef = React.createRef<HTMLPreElement>();
|
||||
|
|
@ -145,19 +193,31 @@ export const CurrentValueViewer = (props: {
|
|||
_.isObject(props.evaluatedValue) ||
|
||||
Array.isArray(props.evaluatedValue)
|
||||
) {
|
||||
const reactJsonProps = {
|
||||
theme: 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} />;
|
||||
if (props.preparedStatementViewer) {
|
||||
content = (
|
||||
<CodeWrapper colorTheme={props.theme} ref={codeWrapperRef}>
|
||||
<PreparedStatementViewer
|
||||
evaluatedValue={props.evaluatedValue as PreparedStatementValue}
|
||||
/>
|
||||
<ScrollIndicator containerRef={codeWrapperRef} />
|
||||
</CodeWrapper>
|
||||
);
|
||||
} else {
|
||||
const reactJsonProps = {
|
||||
theme:
|
||||
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 {
|
||||
content = (
|
||||
<CodeWrapper colorTheme={props.theme} ref={codeWrapperRef}>
|
||||
|
|
@ -222,6 +282,7 @@ const PopoverContent = (props: PopoverContentProps) => {
|
|||
<CurrentValueViewer
|
||||
theme={props.theme}
|
||||
evaluatedValue={props.evaluatedValue}
|
||||
preparedStatementViewer={props.preparedStatementViewer}
|
||||
/>
|
||||
)}
|
||||
</ContentWrapper>
|
||||
|
|
@ -246,7 +307,7 @@ const EvaluatedValuePopup = (props: Props) => {
|
|||
<Popper
|
||||
targetNode={wrapperRef.current || undefined}
|
||||
isOpen
|
||||
zIndex={15}
|
||||
zIndex={5}
|
||||
placement={placement}
|
||||
modifiers={{
|
||||
offset: {
|
||||
|
|
@ -263,6 +324,12 @@ const EvaluatedValuePopup = (props: Props) => {
|
|||
hasError={props.hasError}
|
||||
theme={props.theme}
|
||||
hideEvaluatedValue={props.hideEvaluatedValue}
|
||||
preparedStatementViewer={
|
||||
props.evaluationSubstitutionType
|
||||
? props.evaluationSubstitutionType ===
|
||||
EvaluationSubstitutionType.PARAMETER
|
||||
: false
|
||||
}
|
||||
onMouseLeave={() => {
|
||||
setContentHovered(false);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ import { getDataTreeForAutocomplete } from "selectors/dataTreeSelectors";
|
|||
import EvaluatedValuePopup from "components/editorComponents/CodeEditor/EvaluatedValuePopup";
|
||||
import { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form";
|
||||
import _ from "lodash";
|
||||
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
||||
import {
|
||||
DataTree,
|
||||
EvaluationSubstitutionType,
|
||||
} from "entities/DataTree/dataTreeFactory";
|
||||
import { Skin } from "constants/DefaultTheme";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import "components/editorComponents/CodeEditor/modes";
|
||||
|
|
@ -84,6 +87,7 @@ export type EditorStyleProps = {
|
|||
hoverInteraction?: boolean;
|
||||
fill?: boolean;
|
||||
useValidationMessage?: boolean;
|
||||
evaluationSubstitutionType?: EvaluationSubstitutionType;
|
||||
};
|
||||
|
||||
export type EditorProps = EditorStyleProps &
|
||||
|
|
@ -352,6 +356,7 @@ class CodeEditor extends Component<Props, State> {
|
|||
fill,
|
||||
useValidationMessage,
|
||||
hideEvaluatedValue,
|
||||
evaluationSubstitutionType,
|
||||
} = this.props;
|
||||
const hasError = !!(meta && meta.error);
|
||||
let evaluated = evaluatedValue;
|
||||
|
|
@ -398,6 +403,7 @@ class CodeEditor extends Component<Props, State> {
|
|||
error={meta?.error}
|
||||
useValidationMessage={useValidationMessage}
|
||||
hideEvaluatedValue={hideEvaluatedValue}
|
||||
evaluationSubstitutionType={evaluationSubstitutionType}
|
||||
>
|
||||
<EditorWrapper
|
||||
editorTheme={this.props.theme}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { mockCodemirrorRender } from "test/__mocks__/CodeMirrorEditorMock";
|
|||
import { PluginType } from "entities/Action";
|
||||
import { waitFor } from "@testing-library/dom";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||
|
||||
const TestForm = (props: any) => <div>{props.children}</div>;
|
||||
|
||||
|
|
@ -33,6 +34,7 @@ describe("DynamicTextFieldControl", () => {
|
|||
id={"test"}
|
||||
isValid={true}
|
||||
pluginId="123"
|
||||
evaluationSubstitutionType={EvaluationSubstitutionType.TEMPLATE}
|
||||
/>
|
||||
</ReduxFormDecorator>,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
getQueryParams,
|
||||
} from "utils/AppsmithUtils";
|
||||
import { actionPathFromName } from "components/formControls/utils";
|
||||
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
.dynamic-text-field {
|
||||
|
|
@ -64,6 +65,7 @@ class DynamicTextControl extends BaseControl<
|
|||
placeholderText,
|
||||
actionName,
|
||||
configProperty,
|
||||
evaluationSubstitutionType,
|
||||
} = this.props;
|
||||
const dataTreePath = actionPathFromName(actionName, configProperty);
|
||||
const isNewQuery =
|
||||
|
|
@ -104,6 +106,7 @@ class DynamicTextControl extends BaseControl<
|
|||
mode={mode}
|
||||
tabBehaviour={TabBehaviour.INDENT}
|
||||
placeholder={placeholderText}
|
||||
evaluationSubstitutionType={evaluationSubstitutionType}
|
||||
/>
|
||||
)}
|
||||
</Wrapper>
|
||||
|
|
@ -117,6 +120,7 @@ export interface DynamicTextFieldProps extends ControlProps {
|
|||
pluginId: string;
|
||||
responseType: string;
|
||||
placeholderText?: string;
|
||||
evaluationSubstitutionType: EvaluationSubstitutionType;
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState, props: DynamicTextFieldProps) => {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,13 @@ export const DEFAULT_API_ACTION_CONFIG: ApiActionConfig = {
|
|||
httpMethod: HTTP_METHODS[0],
|
||||
headers: 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";
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import queryActionSettingsConfig from "constants/AppsmithActionConstants/formCon
|
|||
import apiActionSettingsConfig from "constants/AppsmithActionConstants/formConfig/ApiSettingsConfig";
|
||||
import apiActionEditorConfig from "constants/AppsmithActionConstants/formConfig/ApiEditorConfigs";
|
||||
import saasActionSettingsConfig from "constants/AppsmithActionConstants/formConfig/GoogleSheetsSettingsConfig";
|
||||
import apiActionDependencyConfig from "constants/AppsmithActionConstants/formConfig/ApiDependencyConfigs";
|
||||
|
||||
export type ExecuteActionPayloadEvent = {
|
||||
type: EventType;
|
||||
|
|
@ -123,3 +124,12 @@ export const defaultActionEditorConfigs: Record<PluginType, any> = {
|
|||
[PluginType.DB]: [],
|
||||
[PluginType.SAAS]: [],
|
||||
};
|
||||
|
||||
export const defaultActionDependenciesConfig: Record<
|
||||
PluginType,
|
||||
Record<string, string[]>
|
||||
> = {
|
||||
[PluginType.API]: apiActionDependencyConfig,
|
||||
[PluginType.DB]: {},
|
||||
[PluginType.SAAS]: {},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
export default [
|
||||
{
|
||||
dependencies: {
|
||||
"actionConfiguration.body": [
|
||||
"actionConfiguration.pluginSpecifiedTemplates[0].value",
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
export default {
|
||||
"actionConfiguration.body": [
|
||||
"actionConfiguration.pluginSpecifiedTemplates[0].value",
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,6 +12,23 @@ export default [
|
|||
label: "Body",
|
||||
configProperty: "actionConfiguration.body",
|
||||
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",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export const DATA_BIND_REGEX = /{{([\s\S]*?)}}/;
|
||||
export const DATA_BIND_REGEX_GLOBAL = /{{([\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> {
|
||||
postEvalActions?: ReduxAction<any>[];
|
||||
postEvalActions?: Array<ReduxAction<any> | ReduxActionWithoutPayload>;
|
||||
}
|
||||
|
||||
export interface PromisePayload {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Action, PluginType } from "entities/Action/index";
|
||||
import { getBindingPathsOfAction } from "entities/Action/actionProperties";
|
||||
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||
|
||||
const DEFAULT_ACTION: Action = {
|
||||
actionConfiguration: {},
|
||||
|
|
@ -24,9 +25,9 @@ describe("getBindingPathsOfAction", () => {
|
|||
it("returns default list of no config is sent", () => {
|
||||
const response = getBindingPathsOfAction(DEFAULT_ACTION, undefined);
|
||||
expect(response).toStrictEqual({
|
||||
data: true,
|
||||
isLoading: true,
|
||||
config: true,
|
||||
data: EvaluationSubstitutionType.TEMPLATE,
|
||||
isLoading: EvaluationSubstitutionType.TEMPLATE,
|
||||
config: EvaluationSubstitutionType.TEMPLATE,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -46,6 +47,18 @@ describe("getBindingPathsOfAction", () => {
|
|||
configProperty: "actionConfiguration.body2",
|
||||
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: {
|
||||
body: "basic action",
|
||||
body2: "another body",
|
||||
field1: "test",
|
||||
field2: "anotherTest",
|
||||
},
|
||||
};
|
||||
|
||||
const response = getBindingPathsOfAction(basicAction, config);
|
||||
expect(response).toStrictEqual({
|
||||
data: true,
|
||||
isLoading: true,
|
||||
"config.body": true,
|
||||
"config.body2": true,
|
||||
data: EvaluationSubstitutionType.TEMPLATE,
|
||||
isLoading: EvaluationSubstitutionType.TEMPLATE,
|
||||
"config.body": EvaluationSubstitutionType.TEMPLATE,
|
||||
"config.body2": EvaluationSubstitutionType.TEMPLATE,
|
||||
"config.field1": EvaluationSubstitutionType.SMART_SUBSTITUTE,
|
||||
"config.field2": EvaluationSubstitutionType.PARAMETER,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -122,12 +139,12 @@ describe("getBindingPathsOfAction", () => {
|
|||
// @ts-ignore
|
||||
const response = getBindingPathsOfAction(basicAction, config);
|
||||
expect(response).toStrictEqual({
|
||||
data: true,
|
||||
isLoading: true,
|
||||
"config.params[0].key": true,
|
||||
"config.params[0].value": true,
|
||||
"config.params[1].key": true,
|
||||
"config.params[1].value": true,
|
||||
data: EvaluationSubstitutionType.TEMPLATE,
|
||||
isLoading: EvaluationSubstitutionType.TEMPLATE,
|
||||
"config.params[0].key": EvaluationSubstitutionType.TEMPLATE,
|
||||
"config.params[0].value": EvaluationSubstitutionType.TEMPLATE,
|
||||
"config.params[1].key": EvaluationSubstitutionType.TEMPLATE,
|
||||
"config.params[1].value": EvaluationSubstitutionType.TEMPLATE,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -177,10 +194,76 @@ describe("getBindingPathsOfAction", () => {
|
|||
// @ts-ignore
|
||||
const response = getBindingPathsOfAction(basicAction, config);
|
||||
expect(response).toStrictEqual({
|
||||
data: true,
|
||||
isLoading: true,
|
||||
"config.key": true,
|
||||
"config.value": true,
|
||||
data: EvaluationSubstitutionType.TEMPLATE,
|
||||
isLoading: EvaluationSubstitutionType.TEMPLATE,
|
||||
"config.key": EvaluationSubstitutionType.TEMPLATE,
|
||||
"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 _ from "lodash";
|
||||
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||
import { isHidden } from "components/formControls/utils";
|
||||
|
||||
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 = (
|
||||
action: Action,
|
||||
formConfig?: any[],
|
||||
): Record<string, true> => {
|
||||
const bindingPaths: Record<string, true> = {
|
||||
data: true,
|
||||
isLoading: true,
|
||||
): Record<string, EvaluationSubstitutionType> => {
|
||||
const bindingPaths: Record<string, EvaluationSubstitutionType> = {
|
||||
data: EvaluationSubstitutionType.TEMPLATE,
|
||||
isLoading: EvaluationSubstitutionType.TEMPLATE,
|
||||
};
|
||||
if (!formConfig) {
|
||||
return {
|
||||
...bindingPaths,
|
||||
config: true,
|
||||
config: EvaluationSubstitutionType.TEMPLATE,
|
||||
};
|
||||
}
|
||||
const recursiveFindBindingPaths = (formConfig: any) => {
|
||||
if (formConfig.children) {
|
||||
formConfig.children.forEach(recursiveFindBindingPaths);
|
||||
} else {
|
||||
const configPath = formConfig.configProperty.replace(
|
||||
"actionConfiguration.",
|
||||
"config.",
|
||||
);
|
||||
const configPath = getDataTreeActionConfigPath(formConfig.configProperty);
|
||||
if (dynamicFields.includes(formConfig.controlType)) {
|
||||
bindingPaths[configPath] = true;
|
||||
if (!isHidden(action, formConfig.hidden)) {
|
||||
bindingPaths[configPath] = getCorrectEvaluationSubstitutionType(
|
||||
formConfig.evaluationSubstitutionType,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (formConfig.controlType === "ARRAY_FIELD") {
|
||||
const actionValue = _.get(action, formConfig.configProperty);
|
||||
|
|
@ -38,7 +52,11 @@ export const getBindingPathsOfAction = (
|
|||
dynamicFields.includes(schemaField.controlType)
|
||||
) {
|
||||
const arrayConfigPath = `${configPath}[${i}].${schemaField.key}`;
|
||||
bindingPaths[arrayConfigPath] = true;
|
||||
bindingPaths[
|
||||
arrayConfigPath
|
||||
] = getCorrectEvaluationSubstitutionType(
|
||||
formConfig.evaluationSubstitutionType,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -51,3 +69,6 @@ export const getBindingPathsOfAction = (
|
|||
|
||||
return bindingPaths;
|
||||
};
|
||||
|
||||
export const getDataTreeActionConfigPath = (propertyPath: string) =>
|
||||
propertyPath.replace("actionConfiguration.", "config.");
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export enum PaginationType {
|
|||
export interface ActionConfig {
|
||||
timeoutInMillisecond?: number;
|
||||
paginationType?: PaginationType;
|
||||
pluginSpecifiedTemplates?: Array<{ key?: string; value?: unknown }>;
|
||||
}
|
||||
|
||||
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 { ActionData } from "reducers/entityReducers/actionsReducer";
|
||||
import { getBindingPathsOfAction } from "entities/Action/actionProperties";
|
||||
import {
|
||||
getBindingPathsOfAction,
|
||||
getDataTreeActionConfigPath,
|
||||
} from "entities/Action/actionProperties";
|
||||
|
||||
export const generateDataTreeAction = (
|
||||
action: ActionData,
|
||||
editorConfig: any[],
|
||||
dependencyConfig: DependencyMap = {},
|
||||
): DataTreeAction => {
|
||||
let dynamicBindingPathList: DynamicPath[] = [];
|
||||
// update paths
|
||||
|
|
@ -18,6 +22,12 @@ export const generateDataTreeAction = (
|
|||
key: `config.${d.key}`,
|
||||
}));
|
||||
}
|
||||
const dependencyMap: DependencyMap = {};
|
||||
Object.entries(dependencyConfig).forEach(([dependent, dependencies]) => {
|
||||
dependencyMap[getDataTreeActionConfigPath(dependent)] = dependencies.map(
|
||||
getDataTreeActionConfigPath,
|
||||
);
|
||||
});
|
||||
return {
|
||||
run: {},
|
||||
actionId: action.config.id,
|
||||
|
|
@ -29,5 +39,6 @@ export const generateDataTreeAction = (
|
|||
ENTITY_TYPE: ENTITY_TYPE.ACTION,
|
||||
isLoading: action.isLoading,
|
||||
bindingPaths: getBindingPathsOfAction(action.config, editorConfig),
|
||||
dependencyMap,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { MetaState } from "reducers/entityReducers/metaReducer";
|
|||
import { PageListPayload } from "constants/ReduxActionConstants";
|
||||
import { ActionConfig, PluginType } from "entities/Action";
|
||||
import { AppDataState } from "reducers/entityReducers/appReducer";
|
||||
import { DynamicPath } from "utils/DynamicBindingUtils";
|
||||
import { DependencyMap, DynamicPath } from "utils/DynamicBindingUtils";
|
||||
import { generateDataTreeAction } from "entities/DataTree/dataTreeAction";
|
||||
import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget";
|
||||
import { VALIDATION_TYPES } from "constants/WidgetValidation";
|
||||
|
|
@ -36,6 +36,12 @@ export type RunActionPayload = {
|
|||
params: Record<string, any> | string;
|
||||
};
|
||||
|
||||
export enum EvaluationSubstitutionType {
|
||||
TEMPLATE = "TEMPLATE",
|
||||
PARAMETER = "PARAMETER",
|
||||
SMART_SUBSTITUTE = "SMART_SUBSTITUTE",
|
||||
}
|
||||
|
||||
export interface DataTreeAction extends Omit<ActionData, "data" | "config"> {
|
||||
data: ActionResponse["body"];
|
||||
actionId: string;
|
||||
|
|
@ -46,12 +52,13 @@ export interface DataTreeAction extends Omit<ActionData, "data" | "config"> {
|
|||
| ActionDispatcher<RunActionPayload, [string, string, string]>
|
||||
| Record<string, any>;
|
||||
dynamicBindingPathList: DynamicPath[];
|
||||
bindingPaths: Record<string, boolean>;
|
||||
bindingPaths: Record<string, EvaluationSubstitutionType>;
|
||||
ENTITY_TYPE: ENTITY_TYPE.ACTION;
|
||||
dependencyMap: DependencyMap;
|
||||
}
|
||||
|
||||
export interface DataTreeWidget extends WidgetProps {
|
||||
bindingPaths: Record<string, boolean>;
|
||||
bindingPaths: Record<string, EvaluationSubstitutionType>;
|
||||
triggerPaths: Record<string, boolean>;
|
||||
validationPaths: Record<string, VALIDATION_TYPES>;
|
||||
ENTITY_TYPE: ENTITY_TYPE.WIDGET;
|
||||
|
|
@ -79,6 +86,7 @@ export type DataTree = {
|
|||
type DataTreeSeed = {
|
||||
actions: ActionDataState;
|
||||
editorConfigs: Record<string, any[]>;
|
||||
pluginDependencyConfig: Record<string, DependencyMap>;
|
||||
widgets: CanvasWidgetsReduxState;
|
||||
widgetsMeta: MetaState;
|
||||
pageList: PageListPayload;
|
||||
|
|
@ -93,13 +101,16 @@ export class DataTreeFactory {
|
|||
pageList,
|
||||
appData,
|
||||
editorConfigs,
|
||||
pluginDependencyConfig,
|
||||
}: DataTreeSeed): DataTree {
|
||||
const dataTree: DataTree = {};
|
||||
actions.forEach((action) => {
|
||||
const editorConfig = editorConfigs[action.config.pluginId];
|
||||
const dependencyConfig = pluginDependencyConfig[action.config.pluginId];
|
||||
dataTree[action.config.name] = generateDataTreeAction(
|
||||
action,
|
||||
editorConfig,
|
||||
dependencyConfig,
|
||||
);
|
||||
});
|
||||
Object.values(widgets).forEach((widget) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
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 WidgetFactory from "utils/WidgetFactory";
|
||||
import { VALIDATION_TYPES } from "constants/WidgetValidation";
|
||||
|
|
@ -181,19 +185,19 @@ describe("generateDataTreeWidget", () => {
|
|||
|
||||
const expected: DataTreeWidget = {
|
||||
bindingPaths: {
|
||||
defaultText: true,
|
||||
errorMessage: true,
|
||||
isDirty: true,
|
||||
isDisabled: true,
|
||||
isFocused: true,
|
||||
isRequired: true,
|
||||
isValid: true,
|
||||
isVisible: true,
|
||||
placeholderText: true,
|
||||
regex: true,
|
||||
resetOnSubmit: true,
|
||||
text: true,
|
||||
value: true,
|
||||
defaultText: EvaluationSubstitutionType.TEMPLATE,
|
||||
errorMessage: EvaluationSubstitutionType.TEMPLATE,
|
||||
isDirty: EvaluationSubstitutionType.TEMPLATE,
|
||||
isDisabled: EvaluationSubstitutionType.TEMPLATE,
|
||||
isFocused: EvaluationSubstitutionType.TEMPLATE,
|
||||
isRequired: EvaluationSubstitutionType.TEMPLATE,
|
||||
isValid: EvaluationSubstitutionType.TEMPLATE,
|
||||
isVisible: EvaluationSubstitutionType.TEMPLATE,
|
||||
placeholderText: EvaluationSubstitutionType.TEMPLATE,
|
||||
regex: EvaluationSubstitutionType.TEMPLATE,
|
||||
resetOnSubmit: EvaluationSubstitutionType.TEMPLATE,
|
||||
text: EvaluationSubstitutionType.TEMPLATE,
|
||||
value: EvaluationSubstitutionType.TEMPLATE,
|
||||
},
|
||||
triggerPaths: {
|
||||
onSubmit: true,
|
||||
|
|
|
|||
|
|
@ -19,26 +19,10 @@ export const generateDataTreeWidget = (
|
|||
const propertyPaneConfigs = WidgetFactory.getWidgetPropertyPaneConfig(
|
||||
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 dynamicBindingPathList = getEntityDynamicBindingPathList(widget);
|
||||
dynamicBindingPathList.forEach((dynamicPath) => {
|
||||
const propertyPath = dynamicPath.key;
|
||||
// Add any dynamically generated dynamic bindings in the binding paths
|
||||
bindingPaths[propertyPath] = true;
|
||||
const propertyValue = _.get(widget, propertyPath);
|
||||
if (_.isObject(propertyValue)) {
|
||||
// Stringify this because composite controls may have bindings in the sub controls
|
||||
|
|
@ -54,7 +38,6 @@ export const generateDataTreeWidget = (
|
|||
dynamicBindingPathList.push({
|
||||
key: propertyName,
|
||||
});
|
||||
bindingPaths[propertyName] = true;
|
||||
});
|
||||
const unInitializedDefaultProps: Record<string, undefined> = {};
|
||||
Object.values(defaultProps).forEach((propertyName) => {
|
||||
|
|
@ -62,6 +45,16 @@ export const generateDataTreeWidget = (
|
|||
unInitializedDefaultProps[propertyName] = undefined;
|
||||
}
|
||||
});
|
||||
const {
|
||||
bindingPaths,
|
||||
triggerPaths,
|
||||
validationPaths,
|
||||
} = getAllPathsFromPropertyConfig(widget, propertyPaneConfigs, {
|
||||
...derivedPropertyMap,
|
||||
...defaultMetaProps,
|
||||
...unInitializedDefaultProps,
|
||||
..._.keyBy(dynamicBindingPathList, "key"),
|
||||
});
|
||||
return {
|
||||
...widget,
|
||||
...defaultMetaProps,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { getAllPathsFromPropertyConfig } from "./utils";
|
|||
import { RenderModes, WidgetTypes } from "../../constants/WidgetConstants";
|
||||
import tablePropertyPaneConfig from "widgets/TableWidget/TablePropertyPaneConfig";
|
||||
import chartPorpertyConfig from "widgets/ChartWidget/propertyConfig";
|
||||
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||
|
||||
describe("getAllPathsFromPropertyConfig", () => {
|
||||
it("works as expected for table widget", () => {
|
||||
|
|
@ -112,31 +113,47 @@ describe("getAllPathsFromPropertyConfig", () => {
|
|||
|
||||
const expected = {
|
||||
bindingPaths: {
|
||||
selectedRow: true,
|
||||
selectedRows: true,
|
||||
tableData: true,
|
||||
defaultSearchText: true,
|
||||
defaultSelectedRow: true,
|
||||
isVisible: true,
|
||||
"primaryColumns.name.computedValue": true,
|
||||
"primaryColumns.name.horizontalAlignment": true,
|
||||
"primaryColumns.name.verticalAlignment": true,
|
||||
"primaryColumns.name.textSize": true,
|
||||
"primaryColumns.name.fontStyle": true,
|
||||
"primaryColumns.name.textColor": true,
|
||||
"primaryColumns.name.cellBackground": true,
|
||||
"primaryColumns.createdAt.inputFormat": true,
|
||||
"primaryColumns.createdAt.outputFormat": true,
|
||||
"primaryColumns.createdAt.computedValue": true,
|
||||
"primaryColumns.createdAt.horizontalAlignment": true,
|
||||
"primaryColumns.createdAt.verticalAlignment": true,
|
||||
"primaryColumns.createdAt.textSize": true,
|
||||
"primaryColumns.createdAt.fontStyle": true,
|
||||
"primaryColumns.createdAt.textColor": true,
|
||||
"primaryColumns.createdAt.cellBackground": true,
|
||||
"primaryColumns.status.buttonLabel": true,
|
||||
"primaryColumns.status.buttonStyle": true,
|
||||
"primaryColumns.status.buttonLabelColor": true,
|
||||
selectedRow: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedRows: EvaluationSubstitutionType.TEMPLATE,
|
||||
tableData: EvaluationSubstitutionType.TEMPLATE,
|
||||
defaultSearchText: EvaluationSubstitutionType.TEMPLATE,
|
||||
defaultSelectedRow: EvaluationSubstitutionType.TEMPLATE,
|
||||
isVisible: EvaluationSubstitutionType.TEMPLATE,
|
||||
"primaryColumns.name.computedValue":
|
||||
EvaluationSubstitutionType.TEMPLATE,
|
||||
"primaryColumns.name.horizontalAlignment":
|
||||
EvaluationSubstitutionType.TEMPLATE,
|
||||
"primaryColumns.name.verticalAlignment":
|
||||
EvaluationSubstitutionType.TEMPLATE,
|
||||
"primaryColumns.name.textSize": EvaluationSubstitutionType.TEMPLATE,
|
||||
"primaryColumns.name.fontStyle": EvaluationSubstitutionType.TEMPLATE,
|
||||
"primaryColumns.name.textColor": EvaluationSubstitutionType.TEMPLATE,
|
||||
"primaryColumns.name.cellBackground":
|
||||
EvaluationSubstitutionType.TEMPLATE,
|
||||
"primaryColumns.createdAt.inputFormat":
|
||||
EvaluationSubstitutionType.TEMPLATE,
|
||||
"primaryColumns.createdAt.outputFormat":
|
||||
EvaluationSubstitutionType.TEMPLATE,
|
||||
"primaryColumns.createdAt.computedValue":
|
||||
EvaluationSubstitutionType.TEMPLATE,
|
||||
"primaryColumns.createdAt.horizontalAlignment":
|
||||
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: {
|
||||
onRowSelected: true,
|
||||
|
|
@ -197,13 +214,13 @@ describe("getAllPathsFromPropertyConfig", () => {
|
|||
|
||||
const expected = {
|
||||
bindingPaths: {
|
||||
chartType: true,
|
||||
chartName: true,
|
||||
"chartData[0].seriesName": true,
|
||||
"chartData[0].data": true,
|
||||
xAxisName: true,
|
||||
yAxisName: true,
|
||||
isVisible: true,
|
||||
chartType: EvaluationSubstitutionType.TEMPLATE,
|
||||
chartName: EvaluationSubstitutionType.TEMPLATE,
|
||||
"chartData[0].seriesName": EvaluationSubstitutionType.TEMPLATE,
|
||||
"chartData[0].data": EvaluationSubstitutionType.TEMPLATE,
|
||||
xAxisName: EvaluationSubstitutionType.TEMPLATE,
|
||||
yAxisName: EvaluationSubstitutionType.TEMPLATE,
|
||||
isVisible: EvaluationSubstitutionType.TEMPLATE,
|
||||
},
|
||||
triggerPaths: {
|
||||
onDataPointClick: true,
|
||||
|
|
|
|||
|
|
@ -3,17 +3,22 @@ import { PropertyPaneConfig } from "constants/PropertyControlConstants";
|
|||
import { get } from "lodash";
|
||||
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
import { VALIDATION_TYPES } from "constants/WidgetValidation";
|
||||
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||
|
||||
export const getAllPathsFromPropertyConfig = (
|
||||
widget: WidgetProps,
|
||||
widgetConfig: readonly PropertyPaneConfig[],
|
||||
derivedProperties: Record<string, true>,
|
||||
defaultProperties: Record<string, any>,
|
||||
): {
|
||||
bindingPaths: Record<string, true>;
|
||||
bindingPaths: Record<string, EvaluationSubstitutionType>;
|
||||
triggerPaths: Record<string, true>;
|
||||
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 validationPaths: Record<any, VALIDATION_TYPES> = {};
|
||||
widgetConfig.forEach((config) => {
|
||||
|
|
@ -29,7 +34,8 @@ export const getAllPathsFromPropertyConfig = (
|
|||
controlConfig.isBindProperty &&
|
||||
!controlConfig.isTriggerProperty
|
||||
) {
|
||||
bindingPaths[controlConfig.propertyName] = true;
|
||||
bindingPaths[controlConfig.propertyName] =
|
||||
EvaluationSubstitutionType.TEMPLATE;
|
||||
if (controlConfig.validation) {
|
||||
validationPaths[controlConfig.propertyName] =
|
||||
controlConfig.validation;
|
||||
|
|
@ -72,7 +78,8 @@ export const getAllPathsFromPropertyConfig = (
|
|||
panelColumnControlConfig.isBindProperty &&
|
||||
!panelColumnControlConfig.isTriggerProperty
|
||||
) {
|
||||
bindingPaths[panelPropertyPath] = true;
|
||||
bindingPaths[panelPropertyPath] =
|
||||
EvaluationSubstitutionType.TEMPLATE;
|
||||
if (panelColumnControlConfig.validation) {
|
||||
validationPaths[panelPropertyPath] =
|
||||
panelColumnControlConfig.validation;
|
||||
|
|
@ -107,7 +114,8 @@ export const getAllPathsFromPropertyConfig = (
|
|||
childPropertyConfig.isBindProperty &&
|
||||
!childPropertyConfig.isTriggerProperty
|
||||
) {
|
||||
bindingPaths[childArrayPropertyPath] = true;
|
||||
bindingPaths[childArrayPropertyPath] =
|
||||
EvaluationSubstitutionType.TEMPLATE;
|
||||
if (childPropertyConfig.validation) {
|
||||
validationPaths[childArrayPropertyPath] =
|
||||
childPropertyConfig.validation;
|
||||
|
|
@ -131,13 +139,13 @@ export const getAllPathsFromPropertyConfig = (
|
|||
};
|
||||
|
||||
export const nextAvailableRowInContainer = (
|
||||
parenContainertId: string,
|
||||
parentContainerId: string,
|
||||
canvasWidgets: { [widgetId: string]: FlattenedWidgetProps },
|
||||
) => {
|
||||
return (
|
||||
Object.values(canvasWidgets).reduce(
|
||||
(prev: number, next: any) =>
|
||||
next?.parentId === parenContainertId && next.bottomRow > prev
|
||||
next?.parentId === parentContainerId && next.bottomRow > prev
|
||||
? next.bottomRow
|
||||
: prev,
|
||||
0,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
PluginFormPayloadWithId,
|
||||
PluginFormsPayload,
|
||||
} from "actions/pluginActions";
|
||||
import { DependencyMap } from "utils/DynamicBindingUtils";
|
||||
|
||||
export interface PluginDataState {
|
||||
list: Plugin[];
|
||||
|
|
@ -16,6 +17,7 @@ export interface PluginDataState {
|
|||
formConfigs: Record<string, any[]>;
|
||||
editorConfigs: Record<string, any[]>;
|
||||
settingConfigs: Record<string, any[]>;
|
||||
dependencies: Record<string, DependencyMap>;
|
||||
}
|
||||
|
||||
const initialState: PluginDataState = {
|
||||
|
|
@ -24,6 +26,7 @@ const initialState: PluginDataState = {
|
|||
formConfigs: {},
|
||||
editorConfigs: {},
|
||||
settingConfigs: {},
|
||||
dependencies: {},
|
||||
};
|
||||
|
||||
const pluginsReducer = createReducer(initialState, {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
ReduxActionErrorTypes,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import moment from "moment";
|
||||
import { PageAction } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
|
||||
const initialState: EditorReduxState = {
|
||||
initialized: false,
|
||||
|
|
@ -106,6 +107,7 @@ const editorReducer = createReducer(initialState, {
|
|||
pageWidgetId,
|
||||
currentApplicationId,
|
||||
currentPageId,
|
||||
pageActions,
|
||||
} = action.payload;
|
||||
state.loadingStates.publishing = false;
|
||||
state.loadingStates.publishingError = false;
|
||||
|
|
@ -116,6 +118,7 @@ const editorReducer = createReducer(initialState, {
|
|||
pageWidgetId,
|
||||
currentApplicationId,
|
||||
currentPageId,
|
||||
pageActions,
|
||||
};
|
||||
},
|
||||
[ReduxActionTypes.CLONE_PAGE_INIT]: (state: EditorReduxState) => {
|
||||
|
|
@ -171,6 +174,7 @@ export interface EditorReduxState {
|
|||
currentLayoutId?: string;
|
||||
currentPageName?: string;
|
||||
currentPageId?: string;
|
||||
pageActions?: PageAction[][];
|
||||
loadingStates: {
|
||||
saving: boolean;
|
||||
savingError: boolean;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import { executeAction, executeActionError } from "actions/widgetActions";
|
|||
import {
|
||||
getCurrentApplicationId,
|
||||
getCurrentPageId,
|
||||
getLayoutOnLoadActions,
|
||||
getPageList,
|
||||
} from "selectors/editorSelectors";
|
||||
import _, { get, isString } from "lodash";
|
||||
|
|
@ -979,9 +980,9 @@ function* executePageLoadAction(pageAction: PageAction) {
|
|||
}
|
||||
}
|
||||
|
||||
function* executePageLoadActionsSaga(action: ReduxAction<PageAction[][]>) {
|
||||
function* executePageLoadActionsSaga() {
|
||||
try {
|
||||
const pageActions = action.payload;
|
||||
const pageActions: PageAction[][] = yield select(getLayoutOnLoadActions);
|
||||
const actionCount = _.flatten(pageActions).length;
|
||||
PerformanceTracker.startAsyncTracking(
|
||||
PerformanceTransactionName.EXECUTE_PAGE_LOAD_ACTIONS,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
EvaluationReduxAction,
|
||||
ReduxAction,
|
||||
ReduxActionErrorTypes,
|
||||
ReduxActionTypes,
|
||||
|
|
@ -158,7 +159,9 @@ export function* createActionSaga(
|
|||
}
|
||||
}
|
||||
|
||||
export function* fetchActionsSaga(action: ReduxAction<FetchActionsPayload>) {
|
||||
export function* fetchActionsSaga(
|
||||
action: EvaluationReduxAction<FetchActionsPayload>,
|
||||
) {
|
||||
const { applicationId } = action.payload;
|
||||
PerformanceTracker.startAsyncTracking(
|
||||
PerformanceTransactionName.FETCH_ACTIONS_API,
|
||||
|
|
@ -173,6 +176,7 @@ export function* fetchActionsSaga(action: ReduxAction<FetchActionsPayload>) {
|
|||
yield put({
|
||||
type: ReduxActionTypes.FETCH_ACTIONS_SUCCESS,
|
||||
payload: response.data,
|
||||
postEvalActions: action.postEvalActions,
|
||||
});
|
||||
PerformanceTracker.stopAsyncTracking(
|
||||
PerformanceTransactionName.FETCH_ACTIONS_API,
|
||||
|
|
|
|||
|
|
@ -67,7 +67,6 @@ import { createMessage, ERROR_ACTION_RENAME_FAIL } from "constants/messages";
|
|||
import { checkCurrentStep } from "./OnboardingSagas";
|
||||
import { OnboardingStep } from "constants/OnboardingConstants";
|
||||
import { getIndextoUpdate } from "utils/ApiPaneUtils";
|
||||
import { changeQuery } from "actions/queryPaneActions";
|
||||
|
||||
function* syncApiParamsSaga(
|
||||
actionPayload: ReduxActionWithMeta<string, { field: string }>,
|
||||
|
|
@ -208,29 +207,10 @@ function* initializeExtraFormDataSaga() {
|
|||
const { extraformData } = state.ui.apiPane;
|
||||
const formData = yield select(getFormData, API_EDITOR_FORM_NAME);
|
||||
const { values } = formData;
|
||||
const headers = get(
|
||||
values,
|
||||
"actionConfiguration.headers",
|
||||
DEFAULT_API_ACTION_CONFIG.headers,
|
||||
);
|
||||
const headers = get(values, "actionConfiguration.headers");
|
||||
|
||||
const queryParameters = get(
|
||||
values,
|
||||
"actionConfiguration.queryParameters",
|
||||
[],
|
||||
);
|
||||
if (!extraformData[values.id]) {
|
||||
yield put(
|
||||
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,
|
||||
),
|
||||
);
|
||||
yield call(setHeaderFormat, values.id, headers);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -270,6 +250,42 @@ function* changeApiSaga(actionPayload: ReduxAction<{ id: string }>) {
|
|||
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(
|
||||
actionPayload: ReduxActionWithMeta<string, { field: string }>,
|
||||
) {
|
||||
|
|
@ -318,35 +334,7 @@ function* updateFormFields(
|
|||
"actionConfiguration.headers",
|
||||
);
|
||||
const apiId = get(values, "id");
|
||||
let displayFormat;
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
});
|
||||
yield call(setHeaderFormat, apiId, actionConfigurationHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -562,12 +550,6 @@ function* handleApiNameChangeFailureSaga(
|
|||
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() {
|
||||
yield all([
|
||||
takeEvery(ReduxActionTypes.API_PANE_CHANGE_API, changeApiSaga),
|
||||
|
|
@ -597,7 +579,6 @@ export default function* root() {
|
|||
ReduxActionTypes.UPDATE_API_ACTION_BODY_CONTENT_TYPE,
|
||||
handleUpdateBodyContentType,
|
||||
),
|
||||
takeEvery(ReduxActionTypes.UPDATE_ACTION_SUCCESS, updateFormValues),
|
||||
// Intercepting the redux-form change actionType
|
||||
takeEvery(ReduxFormActionTypes.VALUE_CHANGE, formValueChangeSaga),
|
||||
takeEvery(ReduxFormActionTypes.ARRAY_REMOVE, formValueChangeSaga),
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
ReduxAction,
|
||||
ReduxActionErrorTypes,
|
||||
ReduxActionTypes,
|
||||
ReduxActionWithoutPayload,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { getUnevaluatedDataTree } from "selectors/dataTreeSelectors";
|
||||
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) {
|
||||
yield put(action);
|
||||
}
|
||||
}
|
||||
|
||||
function* evaluateTreeSaga(postEvalActions?: ReduxAction<unknown>[]) {
|
||||
function* evaluateTreeSaga(
|
||||
postEvalActions?: Array<ReduxAction<unknown> | ReduxActionWithoutPayload>,
|
||||
) {
|
||||
const unevalTree = yield select(getUnevaluatedDataTree);
|
||||
log.debug({ unevalTree });
|
||||
PerformanceTracker.startAsyncTracking(
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
ReduxAction,
|
||||
ReduxActionErrorTypes,
|
||||
ReduxActionTypes,
|
||||
ReduxActionWithoutPayload,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { ERROR_CODES } from "constants/ApiConstants";
|
||||
|
||||
|
|
@ -43,6 +44,41 @@ import { resetEditorSuccess } from "actions/initActions";
|
|||
import PerformanceTracker, {
|
||||
PerformanceTransactionName,
|
||||
} 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(
|
||||
initializeEditorAction: ReduxAction<InitializeEditorPayload>,
|
||||
|
|
@ -55,97 +91,61 @@ function* initializeEditorSaga(
|
|||
yield put(setAppMode(APP_MODE.EDIT));
|
||||
yield put(updateAppPersistentStore(getPersistentAppStore(applicationId)));
|
||||
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 resultOfPrimaryCalls = yield race({
|
||||
success: all([
|
||||
take(ReduxActionTypes.FETCH_PAGE_LIST_SUCCESS),
|
||||
take(ReduxActionTypes.FETCH_PAGE_SUCCESS),
|
||||
take(ReduxActionTypes.FETCH_APPLICATION_SUCCESS),
|
||||
take(ReduxActionTypes.FETCH_ACTIONS_SUCCESS),
|
||||
]),
|
||||
failure: take([
|
||||
const applicationAndLayoutCalls = yield failFastApiCalls(
|
||||
[
|
||||
fetchPageList(applicationId, APP_MODE.EDIT),
|
||||
fetchPage(pageId),
|
||||
fetchApplication(applicationId, APP_MODE.EDIT),
|
||||
],
|
||||
[
|
||||
ReduxActionTypes.FETCH_PAGE_LIST_SUCCESS,
|
||||
ReduxActionTypes.FETCH_PAGE_SUCCESS,
|
||||
ReduxActionTypes.FETCH_APPLICATION_SUCCESS,
|
||||
],
|
||||
[
|
||||
ReduxActionErrorTypes.FETCH_PAGE_LIST_ERROR,
|
||||
ReduxActionErrorTypes.FETCH_PAGE_ERROR,
|
||||
ReduxActionErrorTypes.FETCH_APPLICATION_ERROR,
|
||||
ReduxActionErrorTypes.FETCH_ACTIONS_ERROR,
|
||||
]),
|
||||
});
|
||||
],
|
||||
);
|
||||
if (!applicationAndLayoutCalls) return;
|
||||
|
||||
if (resultOfPrimaryCalls.failure) {
|
||||
yield put({
|
||||
type: ReduxActionTypes.SAFE_CRASH_APPSMITH_REQUEST,
|
||||
payload: {
|
||||
code: get(
|
||||
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([
|
||||
const pluginsAndDatasourcesCalls = yield failFastApiCalls(
|
||||
[fetchPlugins(), fetchDatasources()],
|
||||
[
|
||||
ReduxActionTypes.FETCH_PLUGINS_SUCCESS,
|
||||
ReduxActionTypes.FETCH_DATASOURCES_SUCCESS,
|
||||
],
|
||||
[
|
||||
ReduxActionErrorTypes.FETCH_PLUGINS_ERROR,
|
||||
ReduxActionErrorTypes.FETCH_DATASOURCES_ERROR,
|
||||
]),
|
||||
});
|
||||
],
|
||||
);
|
||||
if (!pluginsAndDatasourcesCalls) return;
|
||||
|
||||
if (resultOfSecondaryCalls.failure) {
|
||||
yield put({
|
||||
type: ReduxActionTypes.SAFE_CRASH_APPSMITH_REQUEST,
|
||||
payload: {
|
||||
code: get(
|
||||
resultOfSecondaryCalls,
|
||||
"failure.payload.error.code",
|
||||
ERROR_CODES.SERVER_ERROR,
|
||||
),
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
const pluginFormCall = yield failFastApiCalls(
|
||||
[fetchPluginFormConfigs()],
|
||||
[ReduxActionTypes.FETCH_PLUGIN_FORM_CONFIGS_SUCCESS],
|
||||
[ReduxActionErrorTypes.FETCH_PLUGIN_FORM_CONFIGS_ERROR],
|
||||
);
|
||||
if (!pluginFormCall) return;
|
||||
|
||||
yield put(fetchPluginFormConfigs());
|
||||
const actionsCall = yield failFastApiCalls(
|
||||
[fetchActions(applicationId, [executePageLoadActions()])],
|
||||
[ReduxActionTypes.FETCH_ACTIONS_SUCCESS],
|
||||
[ReduxActionErrorTypes.FETCH_ACTIONS_ERROR],
|
||||
);
|
||||
|
||||
const resultOfPluginFormsCall = yield race({
|
||||
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;
|
||||
}
|
||||
if (!actionsCall) return;
|
||||
|
||||
const currentApplication = yield select(getCurrentApplication);
|
||||
|
||||
const appName = currentApplication ? currentApplication.name : "";
|
||||
const appId = currentApplication ? currentApplication.id : "";
|
||||
|
||||
yield put(restoreRecentEntitiesRequest(applicationId));
|
||||
|
||||
AnalyticsUtil.logEvent("EDITOR_OPEN", {
|
||||
appId: appId,
|
||||
appName: appName,
|
||||
|
|
|
|||
|
|
@ -195,12 +195,7 @@ export function* fetchPageSaga(
|
|||
// set current page
|
||||
yield put(updateCurrentPage(id));
|
||||
// dispatch fetch page success
|
||||
yield put(
|
||||
fetchPageSuccess([
|
||||
// Execute page load actions after evaluation of fetch page
|
||||
executePageLoadActions(canvasWidgetsPayload.pageActions),
|
||||
]),
|
||||
);
|
||||
yield put(fetchPageSuccess());
|
||||
|
||||
yield put({
|
||||
type: ReduxActionTypes.UPDATE_CANVAS_STRUCTURE,
|
||||
|
|
@ -264,7 +259,7 @@ export function* fetchPublishedPageSaga(
|
|||
yield put(
|
||||
fetchPublishedPageSuccess(
|
||||
// Execute page load actions post published page eval
|
||||
[executePageLoadActions(canvasWidgetsPayload.pageActions)],
|
||||
[executePageLoadActions()],
|
||||
),
|
||||
);
|
||||
PerformanceTracker.stopAsyncTracking(
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {
|
|||
fetchPluginFormConfigSuccess,
|
||||
} from "actions/pluginActions";
|
||||
import {
|
||||
defaultActionDependenciesConfig,
|
||||
defaultActionEditorConfigs,
|
||||
defaultActionSettings,
|
||||
} from "constants/AppsmithActionConstants/ActionConstants";
|
||||
|
|
@ -26,6 +27,7 @@ import { GenericApiResponse } from "api/ApiResponses";
|
|||
import PluginApi from "api/PluginApi";
|
||||
import log from "loglevel";
|
||||
import { PluginType } from "entities/Action";
|
||||
import { DependencyMap } from "utils/DynamicBindingUtils";
|
||||
|
||||
function* fetchPluginsSaga() {
|
||||
try {
|
||||
|
|
@ -77,20 +79,30 @@ function* fetchPluginFormConfigsSaga() {
|
|||
const formConfigs: Record<string, any[]> = {};
|
||||
const editorConfigs: Record<string, any[]> = {};
|
||||
const settingConfigs: Record<string, any[]> = {};
|
||||
const dependencies: Record<string, DependencyMap> = {};
|
||||
|
||||
Array.from(pluginIdFormsToFetch).forEach((pluginId, index) => {
|
||||
const plugin = plugins.find((plugin) => plugin.id === pluginId);
|
||||
// Datasource form always use server's copy
|
||||
formConfigs[pluginId] = pluginFormData[index].form;
|
||||
// Action editor form if not available use default
|
||||
if (plugin && !pluginFormData[index].editor) {
|
||||
editorConfigs[pluginId] = defaultActionEditorConfigs[plugin.type];
|
||||
} else {
|
||||
editorConfigs[pluginId] = pluginFormData[index].editor;
|
||||
}
|
||||
// Action settings form if not available use default
|
||||
if (plugin && !pluginFormData[index].setting) {
|
||||
settingConfigs[pluginId] = defaultActionSettings[plugin.type];
|
||||
} else {
|
||||
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(
|
||||
|
|
@ -98,6 +110,7 @@ function* fetchPluginFormConfigsSaga() {
|
|||
formConfigs,
|
||||
editorConfigs,
|
||||
settingConfigs,
|
||||
dependencies,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
|
|
@ -125,6 +138,10 @@ export function* checkAndGetPluginFormConfigsSaga(pluginId: string) {
|
|||
formConfigResponse.data.editor =
|
||||
defaultActionEditorConfigs[plugin.type];
|
||||
}
|
||||
if (!formConfigResponse.data.dependencies) {
|
||||
formConfigResponse.data.dependencies =
|
||||
defaultActionDependenciesConfig[plugin.type];
|
||||
}
|
||||
yield put(
|
||||
fetchPluginFormConfigSuccess({
|
||||
id: pluginId,
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ export function* addApiToPageSaga(
|
|||
});
|
||||
|
||||
const applicationId = yield select(getCurrentApplicationId);
|
||||
yield put(fetchActions(applicationId));
|
||||
yield put(fetchActions(applicationId, []));
|
||||
}
|
||||
} catch (error) {
|
||||
yield put({
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import {
|
|||
getPluginTemplates,
|
||||
getPlugin,
|
||||
} from "selectors/entitiesSelector";
|
||||
import { Action, PluginType, QueryAction } from "entities/Action";
|
||||
import { PluginType, QueryAction } from "entities/Action";
|
||||
import { setActionProperty } from "actions/actionActions";
|
||||
import { getQueryParams } from "utils/AppsmithUtils";
|
||||
import { isEmpty, merge } from "lodash";
|
||||
|
|
@ -37,7 +37,6 @@ import { Toaster } from "components/ads/Toast";
|
|||
import { Datasource } from "entities/Datasource";
|
||||
import _ from "lodash";
|
||||
import { createMessage, ERROR_ACTION_RENAME_FAIL } from "constants/messages";
|
||||
import { changeQuery } from "actions/queryPaneActions";
|
||||
|
||||
function* changeQuerySaga(actionPayload: ReduxAction<{ id: string }>) {
|
||||
const { id } = actionPayload.payload;
|
||||
|
|
@ -214,12 +213,6 @@ function* handleNameChangeFailureSaga(
|
|||
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() {
|
||||
yield all([
|
||||
takeEvery(ReduxActionTypes.CREATE_ACTION_SUCCESS, handleQueryCreatedSaga),
|
||||
|
|
@ -237,7 +230,6 @@ export default function* root() {
|
|||
ReduxActionErrorTypes.SAVE_ACTION_NAME_ERROR,
|
||||
handleNameChangeFailureSaga,
|
||||
),
|
||||
takeEvery(ReduxActionTypes.UPDATE_ACTION_SUCCESS, updateFormValues),
|
||||
// Intercepting the redux-form change actionType
|
||||
takeEvery(ReduxFormActionTypes.VALUE_CHANGE, formValueChangeSaga),
|
||||
takeEvery(ReduxFormActionTypes.ARRAY_REMOVE, formValueChangeSaga),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { createSelector } from "reselect";
|
|||
import {
|
||||
getActionsForCurrentPage,
|
||||
getAppData,
|
||||
getPluginDependencyConfig,
|
||||
getPluginEditorConfigs,
|
||||
} from "./entitiesSelector";
|
||||
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
||||
|
|
@ -18,7 +19,16 @@ export const getUnevaluatedDataTree = createSelector(
|
|||
getPageList,
|
||||
getAppData,
|
||||
getPluginEditorConfigs,
|
||||
(actions, widgets, widgetsMeta, pageListPayload, appData, editorConfigs) => {
|
||||
getPluginDependencyConfig,
|
||||
(
|
||||
actions,
|
||||
widgets,
|
||||
widgetsMeta,
|
||||
pageListPayload,
|
||||
appData,
|
||||
editorConfigs,
|
||||
pluginDependencyConfig,
|
||||
) => {
|
||||
const pageList = pageListPayload || [];
|
||||
return DataTreeFactory.create({
|
||||
actions,
|
||||
|
|
@ -27,6 +37,7 @@ export const getUnevaluatedDataTree = createSelector(
|
|||
pageList,
|
||||
appData,
|
||||
editorConfigs,
|
||||
pluginDependencyConfig,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -63,6 +63,9 @@ export const getPageSavingError = (state: AppState) => {
|
|||
return state.ui.editor.loadingStates.savingError;
|
||||
};
|
||||
|
||||
export const getLayoutOnLoadActions = (state: AppState) =>
|
||||
state.ui.editor.pageActions || [];
|
||||
|
||||
export const getIsPublishingApplication = (state: AppState) =>
|
||||
state.ui.editor.loadingStates.publishing;
|
||||
|
||||
|
|
|
|||
|
|
@ -129,6 +129,9 @@ export const getPluginByPackageName = (state: AppState, name: string) =>
|
|||
export const getPluginEditorConfigs = (state: AppState) =>
|
||||
state.entities.plugins.editorConfigs;
|
||||
|
||||
export const getPluginDependencyConfig = (state: AppState) =>
|
||||
state.entities.plugins.dependencies;
|
||||
|
||||
export const getPluginSettingConfigs = (state: AppState, pluginId: string) =>
|
||||
state.entities.plugins.settingConfigs[pluginId];
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@ import {
|
|||
generateTypeDef,
|
||||
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 { WidgetTypes } from "../../constants/WidgetConstants";
|
||||
|
||||
|
|
@ -26,7 +30,7 @@ describe("dataTreeTypeDefCreator", () => {
|
|||
isLoading: false,
|
||||
version: 1,
|
||||
bindingPaths: {
|
||||
defaultText: true,
|
||||
defaultText: EvaluationSubstitutionType.TEMPLATE,
|
||||
},
|
||||
triggerPaths: {
|
||||
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", () => {
|
||||
it("generates the same object if it is run with the same dsl", () => {
|
||||
const nextState = compareAndGenerateImmutableCanvasStructure(
|
||||
|
|
@ -356,15 +90,6 @@ describe("Immutable Canvas structures", () => {
|
|||
|
||||
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", () => {
|
||||
const dsl: any = {
|
||||
widgetId: "x",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import {
|
||||
DependencyMap,
|
||||
EntityWithBindings,
|
||||
EvalError,
|
||||
EvalErrorTypes,
|
||||
getDynamicBindings,
|
||||
|
|
@ -17,6 +16,7 @@ import {
|
|||
DataTreeObjectEntity,
|
||||
DataTreeWidget,
|
||||
ENTITY_TYPE,
|
||||
EvaluationSubstitutionType,
|
||||
} from "entities/DataTree/dataTreeFactory";
|
||||
import {
|
||||
addDependantsOfNestedPropertyPaths,
|
||||
|
|
@ -43,6 +43,7 @@ import {
|
|||
} from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import { DATA_BIND_REGEX } from "constants/BindingsConstants";
|
||||
import evaluate, { EvalResult } from "workers/evaluate";
|
||||
import { substituteDynamicBindingWithValues } from "workers/evaluationSubstitution";
|
||||
|
||||
export default class DataTreeEvaluator {
|
||||
dependencyMap: DependencyMap = {};
|
||||
|
|
@ -308,7 +309,6 @@ export default class DataTreeEvaluator {
|
|||
),
|
||||
);
|
||||
});
|
||||
// TODO make this run only for widgets and not actions
|
||||
dependencyMap = makeParentsDependOnChildren(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
|
||||
const defaultProperties = this.widgetConfigMap[entity.type]
|
||||
.defaultProperties;
|
||||
Object.keys(defaultProperties).forEach((property) => {
|
||||
dependencies[`${entityName}.${property}`] = [
|
||||
`${entityName}.${defaultProperties[property]}`,
|
||||
];
|
||||
});
|
||||
Object.entries(defaultProperties).forEach(
|
||||
([property, defaultPropertyPath]) => {
|
||||
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;
|
||||
}
|
||||
|
|
@ -356,7 +367,9 @@ export default class DataTreeEvaluator {
|
|||
const { entityName, propertyPath } = getEntityNameAndPropertyPath(
|
||||
fullPropertyPath,
|
||||
);
|
||||
const entity: DataTreeEntity = currentTree[entityName];
|
||||
const entity = currentTree[entityName] as
|
||||
| DataTreeWidget
|
||||
| DataTreeAction;
|
||||
const unEvalPropertyValue = _.get(
|
||||
currentTree as any,
|
||||
fullPropertyPath,
|
||||
|
|
@ -368,10 +381,15 @@ export default class DataTreeEvaluator {
|
|||
const requiresEval =
|
||||
isABindingPath && isDynamicValue(unEvalPropertyValue);
|
||||
if (requiresEval) {
|
||||
const evaluationSubstitutionType =
|
||||
entity.bindingPaths[propertyPath] ||
|
||||
EvaluationSubstitutionType.TEMPLATE;
|
||||
try {
|
||||
evalPropertyValue = this.evaluateDynamicProperty(
|
||||
currentTree,
|
||||
evalPropertyValue = this.getDynamicValue(
|
||||
unEvalPropertyValue,
|
||||
currentTree,
|
||||
evaluationSubstitutionType,
|
||||
false,
|
||||
);
|
||||
} catch (e) {
|
||||
this.errors.push({
|
||||
|
|
@ -512,6 +530,7 @@ export default class DataTreeEvaluator {
|
|||
getDynamicValue(
|
||||
dynamicBinding: string,
|
||||
data: DataTree,
|
||||
evaluationSubstitutionType: EvaluationSubstitutionType,
|
||||
returnTriggers: boolean,
|
||||
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];
|
||||
// else return a string template with bindings
|
||||
return createDynamicValueString(dynamicBinding, stringSegments, values);
|
||||
// else return a combined value according to the evaluation type
|
||||
return substituteDynamicBindingWithValues(
|
||||
dynamicBinding,
|
||||
stringSegments,
|
||||
values,
|
||||
evaluationSubstitutionType,
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -569,13 +593,6 @@ export default class DataTreeEvaluator {
|
|||
}
|
||||
}
|
||||
|
||||
evaluateDynamicProperty(
|
||||
currentTree: DataTree,
|
||||
unEvalPropertyValue: any,
|
||||
): any {
|
||||
return this.getDynamicValue(unEvalPropertyValue, currentTree, false);
|
||||
}
|
||||
|
||||
validateAndParseWidgetProperty(
|
||||
fullPropertyPath: string,
|
||||
widget: DataTreeWidget,
|
||||
|
|
@ -590,6 +607,7 @@ export default class DataTreeEvaluator {
|
|||
const { triggers } = this.getDynamicValue(
|
||||
unEvalPropertyValue,
|
||||
currentTree,
|
||||
EvaluationSubstitutionType.TEMPLATE,
|
||||
true,
|
||||
undefined,
|
||||
);
|
||||
|
|
@ -695,21 +713,37 @@ export default class DataTreeEvaluator {
|
|||
if (entityType !== "noop") {
|
||||
switch (dataTreeDiff.event) {
|
||||
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 (
|
||||
isWidget(entity) &&
|
||||
(isWidget(entity) || isAction(entity)) &&
|
||||
!this.isDynamicLeaf(
|
||||
unEvalDataTree,
|
||||
dataTreeDiff.payload.propertyPath,
|
||||
)
|
||||
) {
|
||||
const widgetDependencyMap: DependencyMap = this.listEntityDependencies(
|
||||
entity as DataTreeWidget,
|
||||
const entityDependencyMap: DependencyMap = this.listEntityDependencies(
|
||||
entity,
|
||||
entityName,
|
||||
);
|
||||
if (Object.keys(widgetDependencyMap).length) {
|
||||
if (Object.keys(entityDependencyMap).length) {
|
||||
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
|
||||
|
|
@ -735,14 +769,14 @@ export default class DataTreeEvaluator {
|
|||
removedPaths.push(dataTreeDiff.payload.propertyPath);
|
||||
// If an existing widget was deleted, remove all the bindings from the global dependency map
|
||||
if (
|
||||
isWidget(entity) &&
|
||||
(isWidget(entity) || isAction(entity)) &&
|
||||
dataTreeDiff.payload.propertyPath === entityName
|
||||
) {
|
||||
const widgetBindings = this.listEntityDependencies(
|
||||
const entityDependencies = this.listEntityDependencies(
|
||||
entity,
|
||||
entityName,
|
||||
);
|
||||
Object.keys(widgetBindings).forEach((widgetDep) => {
|
||||
Object.keys(entityDependencies).forEach((widgetDep) => {
|
||||
didUpdateDependencyMap = true;
|
||||
delete this.dependencyMap[widgetDep];
|
||||
});
|
||||
|
|
@ -783,23 +817,22 @@ export default class DataTreeEvaluator {
|
|||
}
|
||||
|
||||
case DataTreeDiffEvent.EDIT: {
|
||||
// We only care about dependencies for a widget. This is because in case a dependency of an action changes,
|
||||
// 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
|
||||
// We only care if the difference is in dynamic bindings since static values do not need
|
||||
// an evaluation.
|
||||
if (
|
||||
(entityType === ENTITY_TYPE.WIDGET ||
|
||||
entityType === ENTITY_TYPE.ACTION) &&
|
||||
(isWidget(entity) || isAction(entity)) &&
|
||||
typeof dataTreeDiff.payload.value === "string"
|
||||
) {
|
||||
const entity: EntityWithBindings = unEvalDataTree[
|
||||
const entity: DataTreeAction | DataTreeWidget = unEvalDataTree[
|
||||
entityName
|
||||
] as EntityWithBindings;
|
||||
] as DataTreeAction | DataTreeWidget;
|
||||
const fullPropertyPath = dataTreeDiff.payload.propertyPath;
|
||||
const entityPropertyPath = fullPropertyPath.substring(
|
||||
fullPropertyPath.indexOf(".") + 1,
|
||||
);
|
||||
const isABindingPath = isPathADynamicBinding(
|
||||
entity,
|
||||
dataTreeDiff.payload.propertyPath.substring(
|
||||
dataTreeDiff.payload.propertyPath.indexOf(".") + 1,
|
||||
),
|
||||
entityPropertyPath,
|
||||
);
|
||||
if (isABindingPath) {
|
||||
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
|
||||
// dependencies for this property path with the newly found dependencies
|
||||
if (correctSnippets.length) {
|
||||
this.dependencyMap[
|
||||
dataTreeDiff.payload.propertyPath
|
||||
] = correctSnippets;
|
||||
this.dependencyMap[fullPropertyPath] = correctSnippets;
|
||||
} else {
|
||||
// The dependency on this property path has been removed. Delete this property path from the global
|
||||
// dependency map
|
||||
delete this.dependencyMap[
|
||||
dataTreeDiff.payload.propertyPath
|
||||
];
|
||||
delete this.dependencyMap[fullPropertyPath];
|
||||
}
|
||||
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) {
|
||||
// TODO Optimise
|
||||
Object.keys(this.dependencyMap).forEach((key) => {
|
||||
this.dependencyMap[key] = _.flatten(
|
||||
this.dependencyMap[key].map((path) =>
|
||||
extractReferencesFromBinding(path, this.allKeys),
|
||||
this.dependencyMap[key] = _.uniq(
|
||||
_.flatten(
|
||||
this.dependencyMap[key].map((path) =>
|
||||
extractReferencesFromBinding(path, this.allKeys),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
|
@ -1003,6 +1053,7 @@ export default class DataTreeEvaluator {
|
|||
evaluatedExecutionParams = this.getDynamicValue(
|
||||
`{{${JSON.stringify(executionParams)}}}`,
|
||||
this.evalTree,
|
||||
EvaluationSubstitutionType.TEMPLATE,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
|
@ -1021,6 +1072,7 @@ export default class DataTreeEvaluator {
|
|||
this.getDynamicValue(
|
||||
`{{${binding}}}`,
|
||||
dataTreeWithExecutionParams,
|
||||
EvaluationSubstitutionType.TEMPLATE,
|
||||
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
|
||||
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 {
|
||||
if (!_.isObject(entity)) {
|
||||
// ERRORS.push({
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
DataTreeAction,
|
||||
DataTreeWidget,
|
||||
ENTITY_TYPE,
|
||||
EvaluationSubstitutionType,
|
||||
} from "../entities/DataTree/dataTreeFactory";
|
||||
import { WidgetTypeConfigMap } from "../utils/WidgetFactory";
|
||||
import { RenderModes, WidgetTypes } from "../constants/WidgetConstants";
|
||||
|
|
@ -238,9 +239,10 @@ const BASE_ACTION: DataTreeAction = {
|
|||
data: {},
|
||||
ENTITY_TYPE: ENTITY_TYPE.ACTION,
|
||||
bindingPaths: {
|
||||
isLoading: true,
|
||||
data: true,
|
||||
isLoading: EvaluationSubstitutionType.TEMPLATE,
|
||||
data: EvaluationSubstitutionType.TEMPLATE,
|
||||
},
|
||||
dependencyMap: {},
|
||||
};
|
||||
|
||||
describe("DataTreeEvaluator", () => {
|
||||
|
|
@ -251,7 +253,10 @@ describe("DataTreeEvaluator", () => {
|
|||
text: "Label",
|
||||
type: WidgetTypes.TEXT_WIDGET,
|
||||
bindingPaths: {
|
||||
text: true,
|
||||
text: EvaluationSubstitutionType.TEMPLATE,
|
||||
},
|
||||
validationPaths: {
|
||||
text: VALIDATION_TYPES.TEXT,
|
||||
},
|
||||
},
|
||||
Text2: {
|
||||
|
|
@ -261,7 +266,7 @@ describe("DataTreeEvaluator", () => {
|
|||
dynamicBindingPathList: [{ key: "text" }],
|
||||
type: WidgetTypes.TEXT_WIDGET,
|
||||
bindingPaths: {
|
||||
text: true,
|
||||
text: EvaluationSubstitutionType.TEMPLATE,
|
||||
},
|
||||
validationPaths: {
|
||||
text: VALIDATION_TYPES.TEXT,
|
||||
|
|
@ -274,7 +279,7 @@ describe("DataTreeEvaluator", () => {
|
|||
dynamicBindingPathList: [{ key: "text" }],
|
||||
type: WidgetTypes.TEXT_WIDGET,
|
||||
bindingPaths: {
|
||||
text: true,
|
||||
text: EvaluationSubstitutionType.TEMPLATE,
|
||||
},
|
||||
validationPaths: {
|
||||
text: VALIDATION_TYPES.TEXT,
|
||||
|
|
@ -294,18 +299,18 @@ describe("DataTreeEvaluator", () => {
|
|||
],
|
||||
type: WidgetTypes.DROP_DOWN_WIDGET,
|
||||
bindingPaths: {
|
||||
options: true,
|
||||
defaultOptionValue: true,
|
||||
isRequired: true,
|
||||
isVisible: true,
|
||||
isDisabled: true,
|
||||
isValid: true,
|
||||
selectedOption: true,
|
||||
selectedOptionArr: true,
|
||||
selectedIndex: true,
|
||||
selectedIndexArr: true,
|
||||
value: true,
|
||||
selectedOptionValues: true,
|
||||
options: EvaluationSubstitutionType.TEMPLATE,
|
||||
defaultOptionValue: EvaluationSubstitutionType.TEMPLATE,
|
||||
isRequired: EvaluationSubstitutionType.TEMPLATE,
|
||||
isVisible: EvaluationSubstitutionType.TEMPLATE,
|
||||
isDisabled: EvaluationSubstitutionType.TEMPLATE,
|
||||
isValid: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedOption: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedOptionArr: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedIndex: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedIndexArr: EvaluationSubstitutionType.TEMPLATE,
|
||||
value: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedOptionValues: EvaluationSubstitutionType.TEMPLATE,
|
||||
},
|
||||
},
|
||||
Table1: {
|
||||
|
|
@ -314,9 +319,9 @@ describe("DataTreeEvaluator", () => {
|
|||
dynamicBindingPathList: [{ key: "tableData" }],
|
||||
type: WidgetTypes.TABLE_WIDGET,
|
||||
bindingPaths: {
|
||||
tableData: true,
|
||||
selectedRow: true,
|
||||
selectedRows: true,
|
||||
tableData: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedRow: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedRows: EvaluationSubstitutionType.TEMPLATE,
|
||||
},
|
||||
validationPaths: {
|
||||
tableData: VALIDATION_TYPES.TABLE_DATA,
|
||||
|
|
@ -328,7 +333,7 @@ describe("DataTreeEvaluator", () => {
|
|||
dynamicBindingPathList: [{ key: "text" }],
|
||||
type: WidgetTypes.TEXT_WIDGET,
|
||||
bindingPaths: {
|
||||
text: true,
|
||||
text: EvaluationSubstitutionType.TEMPLATE,
|
||||
},
|
||||
validationPaths: {
|
||||
text: VALIDATION_TYPES.TEXT,
|
||||
|
|
@ -431,10 +436,10 @@ describe("DataTreeEvaluator", () => {
|
|||
widgetName: "Input1",
|
||||
type: WidgetTypes.INPUT_WIDGET,
|
||||
bindingPaths: {
|
||||
defaultText: true,
|
||||
isValid: true,
|
||||
value: true,
|
||||
text: true,
|
||||
defaultText: EvaluationSubstitutionType.TEMPLATE,
|
||||
isValid: EvaluationSubstitutionType.TEMPLATE,
|
||||
value: EvaluationSubstitutionType.TEMPLATE,
|
||||
text: EvaluationSubstitutionType.TEMPLATE,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -460,18 +465,18 @@ describe("DataTreeEvaluator", () => {
|
|||
],
|
||||
type: WidgetTypes.DROP_DOWN_WIDGET,
|
||||
bindingPaths: {
|
||||
options: true,
|
||||
defaultOptionValue: true,
|
||||
isRequired: true,
|
||||
isVisible: true,
|
||||
isDisabled: true,
|
||||
isValid: true,
|
||||
selectedOption: true,
|
||||
selectedOptionArr: true,
|
||||
selectedIndex: true,
|
||||
selectedIndexArr: true,
|
||||
value: true,
|
||||
selectedOptionValues: true,
|
||||
options: EvaluationSubstitutionType.TEMPLATE,
|
||||
defaultOptionValue: EvaluationSubstitutionType.TEMPLATE,
|
||||
isRequired: EvaluationSubstitutionType.TEMPLATE,
|
||||
isVisible: EvaluationSubstitutionType.TEMPLATE,
|
||||
isDisabled: EvaluationSubstitutionType.TEMPLATE,
|
||||
isValid: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedOption: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedOptionArr: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedIndex: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedIndexArr: EvaluationSubstitutionType.TEMPLATE,
|
||||
value: EvaluationSubstitutionType.TEMPLATE,
|
||||
selectedOptionValues: EvaluationSubstitutionType.TEMPLATE,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -604,4 +609,86 @@ describe("DataTreeEvaluator", () => {
|
|||
"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 {
|
||||
DependencyMap,
|
||||
EVAL_WORKER_ACTIONS,
|
||||
|
|
@ -109,6 +112,7 @@ ctx.addEventListener(
|
|||
const triggers = dataTreeEvaluator.getDynamicValue(
|
||||
dynamicTrigger,
|
||||
evalTree,
|
||||
EvaluationSubstitutionType.TEMPLATE,
|
||||
true,
|
||||
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,
|
||||
run: {},
|
||||
ENTITY_TYPE: ENTITY_TYPE.ACTION,
|
||||
dependencyMap: {},
|
||||
},
|
||||
};
|
||||
const dataTreeWithFunctions = addFunctions(dataTree);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user