From cb2867069f8d933d0f73599a641da34a1e90e868 Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Wed, 10 Jun 2020 12:16:50 +0000 Subject: [PATCH] Action creator evaluated value fixes --- app/client/cypress/support/commands.js | 5 + .../DynamicAutocompleteInput.tsx | 3 +- .../actioncreator/ActionCreator.tsx | 1 + .../propertyControls/BaseControl.tsx | 2 +- .../propertyControls/ChartDataControl.tsx | 24 +--- .../propertyControls/CodeEditorControl.tsx | 7 +- .../src/constants/FieldExpectedValue.ts | 27 ++-- .../Editor/PropertyPane/PropertyControl.tsx | 59 +-------- .../Editor/PropertyPane/PropertyHelpLabel.tsx | 54 ++++++++ app/client/src/sagas/ActionSagas.ts | 3 + app/client/src/sagas/ApiPaneSagas.ts | 1 - app/client/src/sagas/ConfigsSagas.tsx | 1 + app/client/src/utils/DynamicBindingUtils.ts | 120 ++++++++++++------ app/client/src/utils/Validators.ts | 25 +++- 14 files changed, 190 insertions(+), 142 deletions(-) create mode 100644 app/client/src/pages/Editor/PropertyPane/PropertyHelpLabel.tsx diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index 0a773d2f5a..c61b68609b 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -38,6 +38,11 @@ Cypress.Commands.add("DeleteApp", appName => { "response.body.responseMeta.status", 200, ); + cy.wait("@organizations").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); cy.get('button span[icon="chevron-down"]').should("be.visible"); cy.get(homePage.searchInput).type(appName, { force: true }); cy.get(homePage.appMoreIcon) diff --git a/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx b/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx index 957481d21e..8778e326ee 100644 --- a/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx +++ b/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx @@ -574,7 +574,8 @@ class DynamicAutocompleteInput extends Component { } const showEvaluatedValue = this.state.isFocused && - ("evaluatedValue" in this.props || "dataTreePath" in this.props); + ("evaluatedValue" in this.props || + ("dataTreePath" in this.props && !!this.props.dataTreePath)); return ( diff --git a/app/client/src/components/editorComponents/actioncreator/ActionCreator.tsx b/app/client/src/components/editorComponents/actioncreator/ActionCreator.tsx index 01c61accf9..9673601420 100644 --- a/app/client/src/components/editorComponents/actioncreator/ActionCreator.tsx +++ b/app/client/src/components/editorComponents/actioncreator/ActionCreator.tsx @@ -195,6 +195,7 @@ const views = { props.set(event); } }} + dataTreePath={""} isValid={props.isValid} errorMessage={props.validationMessage} /> diff --git a/app/client/src/components/propertyControls/BaseControl.tsx b/app/client/src/components/propertyControls/BaseControl.tsx index 97acbd760b..b3440fd518 100644 --- a/app/client/src/components/propertyControls/BaseControl.tsx +++ b/app/client/src/components/propertyControls/BaseControl.tsx @@ -37,7 +37,7 @@ export interface ControlData { expected: string; evaluatedValue: any; validationMessage?: string; - dataTreePath: string; + dataTreePath?: string; } export interface ControlFunctions { diff --git a/app/client/src/components/propertyControls/ChartDataControl.tsx b/app/client/src/components/propertyControls/ChartDataControl.tsx index f73d15f673..f64c22dc41 100644 --- a/app/client/src/components/propertyControls/ChartDataControl.tsx +++ b/app/client/src/components/propertyControls/ChartDataControl.tsx @@ -1,11 +1,7 @@ import React from "react"; import _ from "lodash"; import BaseControl, { ControlProps } from "./BaseControl"; -import { - ControlWrapper, - StyledInputGroup, - StyledPropertyPaneButton, -} from "./StyledControls"; +import { ControlWrapper, StyledPropertyPaneButton } from "./StyledControls"; import styled from "constants/DefaultTheme"; import { FormIcons } from "icons/FormIcons"; import { AnyStyledComponent } from "styled-components"; @@ -18,24 +14,6 @@ const StyledOptionControlWrapper = styled(ControlWrapper)` width: 100%; `; -const StyledOptionControlInputGroup = styled(StyledInputGroup)` - margin-right: 2px; - width: 100%; - margin-bottom: 0; - &&& { - input { - border: none; - color: ${props => props.theme.colors.textOnDarkBG}; - background: ${props => props.theme.colors.paneInputBG}; - &:focus { - border: none; - color: ${props => props.theme.colors.textOnDarkBG}; - background: ${props => props.theme.colors.paneInputBG}; - } - } - } -`; - const StyledDynamicInput = styled.div` width: 100%; &&& { diff --git a/app/client/src/components/propertyControls/CodeEditorControl.tsx b/app/client/src/components/propertyControls/CodeEditorControl.tsx index 8f8daf94b4..0a6d13b647 100644 --- a/app/client/src/components/propertyControls/CodeEditorControl.tsx +++ b/app/client/src/components/propertyControls/CodeEditorControl.tsx @@ -5,20 +5,23 @@ import { EventOrValueHandler } from "redux-form"; class CodeEditorControl extends BaseControl { render() { const { - errorMessage, + validationMessage, expected, propertyValue, isValid, dataTreePath, + evaluatedValue, } = this.props; + return ( ", @@ -30,8 +30,8 @@ const FIELD_VALUES: Record< exportPDF: "boolean", exportExcel: "boolean", exportCsv: "boolean", - onRowSelected: "undefined", - onPageChange: "undefined", + onRowSelected: "Function Call", + onPageChange: "Function Call", }, IMAGE_WIDGET: { image: "string", @@ -43,7 +43,7 @@ const FIELD_VALUES: Record< defaultOptionValue: "string", isRequired: "boolean", isVisible: "boolean", - onSelectionChange: "undefined", + onSelectionChange: "Function Call", }, TABS_WIDGET: { tabs: "Array<{ label: string, id: string }>", @@ -53,7 +53,6 @@ const FIELD_VALUES: Record< CHART_WIDGET: { chartName: "string", chartType: "LINE_CHART | BAR_CHART | PIE_CHART | COLUMN_CHART | AREA_CHART", - singleChartData: "Array<{ x: string, y: number }>", xAxisName: "string", yAxisName: "string", isVisible: "boolean", @@ -71,7 +70,7 @@ const FIELD_VALUES: Record< isRequired: "boolean", isVisible: "boolean", isDisabled: "boolean", - onTextChanged: "undefined", + onTextChanged: "Function Call", }, DROP_DOWN_WIDGET: { label: "string", @@ -80,7 +79,7 @@ const FIELD_VALUES: Record< defaultOptionValue: "string", isRequired: "boolean", isVisible: "boolean", - onOptionChange: "boolean", + onOptionChange: "Function Call", }, FORM_BUTTON_WIDGET: { text: "string", @@ -88,7 +87,7 @@ const FIELD_VALUES: Record< disabledWhenInvalid: "boolean", resetFormOnClick: "boolean", isVisible: "boolean", - onClick: "boolean", + onClick: "Function Call", }, MAP_WIDGET: { defaultMarkers: "Array<{ lat: number, long: number }>", @@ -96,20 +95,20 @@ const FIELD_VALUES: Record< enablePickLocation: "boolean", enableCreateMarker: "boolean", isVisible: "boolean", - onMarkerClick: "undefined", - onCreateMarker: "undefined", + onMarkerClick: "Function Call", + onCreateMarker: "Function Call", }, BUTTON_WIDGET: { text: "string", buttonStyle: "PRIMARY_BUTTON | SECONDARY_BUTTON | DANGER_BUTTON", isVisible: "boolean", - onClick: "boolean", + onClick: "Function Call", }, RICH_TEXT_EDITOR_WIDGET: { defaultText: "string", isVisible: "boolean", isDisabled: "boolean", - onTextChange: "undefined", + onTextChange: "Function Call", }, FILE_PICKER_WIDGET: { label: "string", @@ -120,7 +119,7 @@ const FIELD_VALUES: Record< isRequired: "boolean", isVisible: "boolean", uploadedFileUrls: "string", - onFilesSelected: "undefined", + onFilesSelected: "Function Call", }, CHECKBOX_WIDGET: { label: "string", @@ -128,7 +127,7 @@ const FIELD_VALUES: Record< isRequired: "boolean", isDisabled: "boolean", isVisible: "boolean", - onCheckChange: "undefined", + onCheckChange: "Function Call", }, FORM_WIDGET: { backgroundColor: "string", diff --git a/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx b/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx index 13aa20aa5f..64ee97bbc8 100644 --- a/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx +++ b/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx @@ -9,7 +9,7 @@ import { ControlIcons } from "icons/ControlIcons"; import PropertyControlFactory from "utils/PropertyControlFactory"; import { WidgetProps } from "widgets/BaseWidget"; import { PropertyControlPropsType } from "components/propertyControls"; -import { Tooltip, Position } from "@blueprintjs/core"; +import PropertyHelpLabel from "pages/Editor/PropertyPane/PropertyHelpLabel"; import FIELD_EXPECTED_VALUE from "constants/FieldExpectedValue"; type Props = { @@ -19,57 +19,6 @@ type Props = { onPropertyChange: (propertyName: string, propertyValue: any) => void; }; -function UnderlinedLabel({ - tooltip, - label, -}: { - tooltip?: string; - label: string; -}) { - const toolTipDefined = tooltip !== undefined; - return ( - -
- - -
-
- ); -} - const PropertyControl = (props: Props) => { const { widgetProperties, @@ -136,8 +85,10 @@ const PropertyControl = (props: Props) => { } > - - + {isConvertible && ( { + const toolTipDefined = props.tooltip !== undefined; + return ( + +
+ + +
+
+ ); +}; + +export default PropertyHelpLabel; diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index e39ffd83c8..ed0af9fdb5 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -369,8 +369,11 @@ export function* executeActionTriggers( export function* executeAppAction(action: ReduxAction) { const { dynamicString, event, responseData } = action.payload; log.debug("Evaluating data tree to get action trigger"); + log.debug({ dynamicString }); const tree = yield select(evaluateDataTree); + log.debug({ tree }); const { triggers } = getDynamicValue(dynamicString, tree, responseData, true); + log.debug({ triggers }); if (triggers && triggers.length) { yield all( triggers.map(trigger => call(executeActionTriggers, trigger, event)), diff --git a/app/client/src/sagas/ApiPaneSagas.ts b/app/client/src/sagas/ApiPaneSagas.ts index affd847461..cf2128c262 100644 --- a/app/client/src/sagas/ApiPaneSagas.ts +++ b/app/client/src/sagas/ApiPaneSagas.ts @@ -36,7 +36,6 @@ import { AppState } from "reducers"; import { Property } from "api/ActionAPI"; import { changeApi, setDatasourceFieldText } from "actions/apiPaneActions"; import { - API_PATH_START_WITH_SLASH_ERROR, FIELD_REQUIRED_ERROR, UNIQUE_NAME_ERROR, VALID_FUNCTION_NAME_ERROR, diff --git a/app/client/src/sagas/ConfigsSagas.tsx b/app/client/src/sagas/ConfigsSagas.tsx index 034539caa0..060f3f3454 100644 --- a/app/client/src/sagas/ConfigsSagas.tsx +++ b/app/client/src/sagas/ConfigsSagas.tsx @@ -27,6 +27,7 @@ const generateConfigWithIds = (config: PropertyConfig) => { return config; }; +// eslint-disable-next-line @typescript-eslint/no-unused-vars function* getLocalPropertyPaneConfigSaga() { // FOR DEV WORK ONLY try { diff --git a/app/client/src/utils/DynamicBindingUtils.ts b/app/client/src/utils/DynamicBindingUtils.ts index dad28a9b7b..46c478eef0 100644 --- a/app/client/src/utils/DynamicBindingUtils.ts +++ b/app/client/src/utils/DynamicBindingUtils.ts @@ -160,9 +160,7 @@ export const getDynamicValue = ( includeTriggers = false, ): JSExecutorResult => { // Get the {{binding}} bound values - const { stringSegments: stringSegments, jsSnippets } = getDynamicBindings( - dynamicBinding, - ); + const { stringSegments, jsSnippets } = getDynamicBindings(dynamicBinding); if (stringSegments.length) { // Get the Data Tree value of those "binding "paths const values = jsSnippets.map((jsSnippet, index) => { @@ -199,32 +197,55 @@ export const getValidatedTree = (tree: any) => { if (entity && entity.type) { const parsedEntity = { ...entity }; Object.keys(entity).forEach((property: string) => { - const value = entity[property]; - // Pass it through parse - const { - parsed, - isValid, - message, - } = ValidationFactory.validateWidgetProperty( - entity.type, - property, - value, - entity, - tree, + const hasEvaluatedValue = _.has( + parsedEntity, + `evaluatedValues.${property}`, ); - parsedEntity[property] = parsed; - if (property !== "evaluatedValues") { - if (!("evaluatedValues" in parsedEntity)) { - _.set(parsedEntity, "evaluatedValues", {}); - } - if (!(property in parsedEntity.evaluatedValues)) { - _.set(parsedEntity, `evaluatedValues.${property}`, value); - } - } + const hasValidation = _.has(parsedEntity, `invalidProps.${property}`); + const isSpecialField = [ + "dynamicBindings", + "dynamicTriggers", + "dynamicProperties", + "evaluatedValues", + "invalidProps", + "validationMessages", + ].includes(property); + const isDynamicField = + _.has(parsedEntity, `dynamicBindings.${property}`) || + _.has(parsedEntity, `dynamicTriggers.${property}`); - if (!isValid) { - _.set(parsedEntity, `invalidProps.${property}`, true); - _.set(parsedEntity, `validationMessages.${property}`, message); + if ( + !isSpecialField && + !isDynamicField && + (!hasValidation || !hasEvaluatedValue) + ) { + const value = entity[property]; + // Pass it through parse + const { + parsed, + isValid, + message, + transformed, + } = ValidationFactory.validateWidgetProperty( + entity.type, + property, + value, + entity, + tree, + ); + parsedEntity[property] = parsed; + if (!hasEvaluatedValue) { + const evaluatedValue = _.isUndefined(transformed) + ? value + : transformed; + _.set(parsedEntity, `evaluatedValues.${property}`, evaluatedValue); + } + + const hasValidation = _.has(parsedEntity, `invalidProps.${property}`); + if (!hasValidation && !isValid) { + _.set(parsedEntity, `invalidProps.${property}`, true); + _.set(parsedEntity, `validationMessages.${property}`, message); + } } }); return { ...tree, [entityKey]: parsedEntity }; @@ -317,6 +338,11 @@ export const createDependencyTree = ( ); }); } + if (entity.dynamicTriggers) { + Object.keys(entity.dynamicTriggers).forEach(prop => { + dependencyMap[`${entityKey}.${prop}`] = []; + }); + } } if (entity.ENTITY_TYPE === ENTITY_TYPE.ACTION) { if (entity.dynamicBindingPathList.length) { @@ -548,10 +574,21 @@ function validateAndParseWidgetProperty( widget: DataTreeWidget, currentTree: DataTree, evalPropertyValue: any, + unEvalPropertyValue: string, currentDependencyValues: Array, cachedDependencyValues?: Array, ): any { const propertyName = propertyPath.split(".")[1]; + let valueToValidate = evalPropertyValue; + if (widget.dynamicTriggers && propertyName in widget.dynamicTriggers) { + const { triggers } = getDynamicValue( + unEvalPropertyValue, + currentTree, + undefined, + true, + ); + valueToValidate = triggers; + } const { parsed, isValid, @@ -560,7 +597,7 @@ function validateAndParseWidgetProperty( } = ValidationFactory.validateWidgetProperty( widget.type, propertyName, - evalPropertyValue, + valueToValidate, widget, currentTree, ); @@ -572,18 +609,22 @@ function validateAndParseWidgetProperty( _.set(widget, `invalidProps.${propertyName}`, true); _.set(widget, `validationMessages.${propertyName}`, message); } - const parsedCache = getParsedValueCache(propertyPath); - if ( - !equal(parsedCache.value, parsed) || - (cachedDependencyValues !== undefined && - !equal(currentDependencyValues, cachedDependencyValues)) - ) { - parsedValueCache.set(propertyPath, { - value: parsed, - version: Date.now(), - }); + if (widget.dynamicTriggers && propertyName in widget.dynamicTriggers) { + return unEvalPropertyValue; + } else { + const parsedCache = getParsedValueCache(propertyPath); + if ( + !equal(parsedCache.value, parsed) || + (cachedDependencyValues !== undefined && + !equal(currentDependencyValues, cachedDependencyValues)) + ) { + parsedValueCache.set(propertyPath, { + value: parsed, + version: Date.now(), + }); + } + return parsed; } - return parsed; } function isWidget(entity: DataTreeEntity): boolean { @@ -641,6 +682,7 @@ export function dependencySortedEvaluateDataTree( widgetEntity, currentTree, evalPropertyValue, + unEvalPropertyValue, currentDependencyValues, cachedDependencyValues, ); diff --git a/app/client/src/utils/Validators.ts b/app/client/src/utils/Validators.ts index 061df2a670..b53a55e58a 100644 --- a/app/client/src/utils/Validators.ts +++ b/app/client/src/utils/Validators.ts @@ -8,14 +8,14 @@ import { import moment from "moment"; import { WIDGET_TYPE_VALIDATION_ERROR, - NAVIGATE_TO_VALIDATION_ERROR, + // NAVIGATE_TO_VALIDATION_ERROR, } from "constants/messages"; -import { modalGetter } from "components/editorComponents/actioncreator/ActionCreator"; +// import { modalGetter } from "components/editorComponents/actioncreator/ActionCreator"; import { WidgetProps } from "widgets/BaseWidget"; import { DataTree } from "entities/DataTree/dataTreeFactory"; -import { PageListPayload } from "constants/ReduxActionConstants"; -import { isDynamicValue } from "./DynamicBindingUtils"; -const URL_REGEX = /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/; +// import { PageListPayload } from "constants/ReduxActionConstants"; +// import { isDynamicValue } from "./DynamicBindingUtils"; +// const URL_REGEX = /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/; export const VALIDATORS: Record = { [VALIDATION_TYPES.TEXT]: ( @@ -415,6 +415,14 @@ export const VALIDATORS: Record = { props: WidgetProps, dataTree?: DataTree, ): ValidationResponse => { + if (Array.isArray(value) && value.length) { + return { + isValid: true, + parsed: undefined, + transformed: "Function Call", + }; + } + /* if (_.isString(value)) { if (value.indexOf("navigateTo") !== -1) { const pageNameOrUrl = modalGetter(value); @@ -440,9 +448,12 @@ export const VALIDATORS: Record = { } } } + */ return { - isValid: true, - parsed: value, + isValid: false, + parsed: undefined, + transformed: "undefined", + message: "Not a function call", }; }, [VALIDATION_TYPES.ARRAY_ACTION_SELECTOR]: (