From 747c3d5d8bad7e665cb4e54eca7dbf6a179d22d0 Mon Sep 17 00:00:00 2001 From: Vicky Bansal Date: Fri, 29 May 2020 06:07:56 +0000 Subject: [PATCH] Chart validations, Tabs add new tab issue fix --- .../appsmith/DraggableListComponent.tsx | 10 ++ .../propertyControls/ChartDataControl.tsx | 107 ++++++++++++++---- .../propertyControls/TabControl.tsx | 39 ++++--- app/client/src/constants/WidgetValidation.ts | 1 + app/client/src/utils/DynamicBindingUtils.ts | 7 ++ app/client/src/utils/Validators.ts | 68 ++++++++--- app/client/src/widgets/TabsWidget.tsx | 8 ++ 7 files changed, 189 insertions(+), 51 deletions(-) diff --git a/app/client/src/components/designSystems/appsmith/DraggableListComponent.tsx b/app/client/src/components/designSystems/appsmith/DraggableListComponent.tsx index 3c018fa55b..256a8244c4 100644 --- a/app/client/src/components/designSystems/appsmith/DraggableListComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/DraggableListComponent.tsx @@ -43,6 +43,16 @@ export class DroppableComponent extends React.Component< }; } + componentDidUpdate(prevProps: DroppableComponentProps) { + if (this.props.items.length !== prevProps.items.length) { + this.setState({ items: this.props.items }); + } else if ( + JSON.stringify(this.props.items) !== JSON.stringify(prevProps.items) + ) { + this.setState({ items: this.props.items }); + } + } + onDragEnd = (result: any) => { const { destination, source } = result; if (!destination) { diff --git a/app/client/src/components/propertyControls/ChartDataControl.tsx b/app/client/src/components/propertyControls/ChartDataControl.tsx index 756bdbf64f..29f24070df 100644 --- a/app/client/src/components/propertyControls/ChartDataControl.tsx +++ b/app/client/src/components/propertyControls/ChartDataControl.tsx @@ -1,4 +1,5 @@ import React from "react"; +import _ from "lodash"; import BaseControl, { ControlProps } from "./BaseControl"; import { ControlWrapper, @@ -65,12 +66,22 @@ type RenderComponentProps = { data: Array<{ x: string; y: string }> | any; }; length: number; + isValid: boolean; + validationMessage: string; deleteOption: Function; updateOption: Function; }; function DataControlComponent(props: RenderComponentProps) { - const { deleteOption, updateOption, item, index, length } = props; + const { + deleteOption, + updateOption, + item, + index, + length, + isValid, + validationMessage, + } = props; return ( @@ -107,7 +118,7 @@ function DataControlComponent(props: RenderComponentProps) { }, }} meta={{ - error: "", + error: isValid ? "" : validationMessage, touched: true, }} theme={"DARK"} @@ -120,12 +131,52 @@ function DataControlComponent(props: RenderComponentProps) { } class ChartDataControl extends BaseControl { + getValidations = (message: string, isValid: boolean, len: number) => { + const validations: Array<{ + isValid: boolean; + validationMessage: string; + }> = []; + let index = -1; + let validationMessage = ""; + if (message.indexOf("##") !== -1) { + const messages = message.split("##"); + index = Number(messages[0]); + validationMessage = messages[1]; + } + for (let i = 0; i < len; i++) { + if (i === index) { + validations.push({ + isValid: false, + validationMessage: validationMessage, + }); + } else { + validations.push({ + isValid: true, + validationMessage: "", + }); + } + } + return validations; + }; + render() { const chartData: Array<{ seriesName: string; data: Array<{ x: string; y: string }> | any; - }> = this.props.propertyValue || []; + }> = + this.props.propertyValue && _.isString(this.props.propertyValue) + ? JSON.parse(this.props.propertyValue) + : this.props.propertyValue; const dataLength = chartData.length; + const { validationMessage, isValid } = this.props; + const validations: Array<{ + isValid: boolean; + validationMessage: string; + }> = this.getValidations( + validationMessage || "", + isValid, + chartData.length, + ); return ( {chartData.map((data, index) => { @@ -137,6 +188,8 @@ class ChartDataControl extends BaseControl { length={dataLength} deleteOption={this.deleteOption} updateOption={this.updateOption} + isValid={validations[index].isValid} + validationMessage={validations[index].validationMessage} /> ); })} @@ -152,9 +205,12 @@ class ChartDataControl extends BaseControl { } deleteOption = (index: number) => { - const chartData: object[] = this.props.propertyValue.slice(); + const chartData: object[] = + this.props.propertyValue && _.isString(this.props.propertyValue) + ? JSON.parse(this.props.propertyValue) + : this.props.propertyValue; chartData.splice(index, 1); - this.updateProperty(this.props.propertyName, chartData); + this.updateProperty(this.props.propertyName, JSON.stringify(chartData)); }; updateOption = ( @@ -165,23 +221,27 @@ class ChartDataControl extends BaseControl { const chartData: Array<{ seriesName: string; data: Array<{ x: string; y: string }> | any; - }> = this.props.propertyValue; - this.updateProperty( - this.props.propertyName, - chartData.map((item, i) => { - if (index === i) { - if (propertyName === "seriesName") { - item.seriesName = updatedValue; - } else { - try { - item.data = JSON.parse(updatedValue); - } catch (err) { - item.data = updatedValue; - } + }> = + this.props.propertyValue && _.isString(this.props.propertyValue) + ? JSON.parse(this.props.propertyValue) + : this.props.propertyValue; + const updatedChartData = chartData.map((item, i) => { + if (index === i) { + if (propertyName === "seriesName") { + item.seriesName = updatedValue; + } else { + try { + item.data = JSON.parse(updatedValue); + } catch (err) { + item.data = updatedValue; } } - return item; - }), + } + return item; + }); + this.updateProperty( + this.props.propertyName, + JSON.stringify(updatedChartData), ); }; @@ -189,9 +249,12 @@ class ChartDataControl extends BaseControl { const chartData: Array<{ seriesName: string; data: Array<{ x: string; y: string }> | any; - }> = this.props.propertyValue ? this.props.propertyValue.slice() : []; + }> = + this.props.propertyValue && _.isString(this.props.propertyValue) + ? JSON.parse(this.props.propertyValue) + : this.props.propertyValue; chartData.push({ seriesName: "", data: [{ x: "", y: "" }] }); - this.updateProperty(this.props.propertyName, chartData); + this.updateProperty(this.props.propertyName, JSON.stringify(chartData)); }; static getControlType() { diff --git a/app/client/src/components/propertyControls/TabControl.tsx b/app/client/src/components/propertyControls/TabControl.tsx index 98b6594c6d..97197ef6ff 100644 --- a/app/client/src/components/propertyControls/TabControl.tsx +++ b/app/client/src/components/propertyControls/TabControl.tsx @@ -7,6 +7,7 @@ import { ControlIcons } from "icons/ControlIcons"; import { AnyStyledComponent } from "styled-components"; import { generateReactKey } from "utils/generators"; import { DroppableComponent } from "../designSystems/appsmith/DraggableListComponent"; +import _ from "lodash"; const StyledDeleteIcon = styled(FormIcons.DELETE_ICON as AnyStyledComponent)` padding: 0; @@ -98,14 +99,16 @@ function TabControlComponent(props: RenderComponentProps) { class TabControl extends BaseControl { updateItems = (items: object[]) => { - this.updateProperty(this.props.propertyName, items); + this.updateProperty(this.props.propertyName, JSON.stringify(items)); }; render() { const tabs: Array<{ id: string; label: string; - }> = this.props.propertyValue || [{ id: "" }]; + }> = _.isString(this.props.propertyValue) + ? JSON.parse(this.props.propertyValue) + : this.props.propertyValue; return ( { } deleteOption = (index: number) => { - const tabs: object[] = this.props.propertyValue.slice(); + const tabs: object[] = _.isString(this.props.propertyValue) + ? JSON.parse(this.props.propertyValue).slice() + : this.props.propertyValue.slice(); tabs.splice(index, 1); - this.updateProperty(this.props.propertyName, tabs); + this.updateProperty(this.props.propertyName, JSON.stringify(tabs)); }; updateOption = (index: number, updatedLabel: string) => { const tabs: Array<{ id: string; label: string; - }> = this.props.propertyValue; - this.updateProperty( - this.props.propertyName, - tabs.map((tab, tabIndex) => { - if (index === tabIndex) { - tab.label = updatedLabel; - } - return tab; - }), - ); + }> = _.isString(this.props.propertyValue) + ? JSON.parse(this.props.propertyValue) + : this.props.propertyValue; + const updatedTabs = tabs.map((tab, tabIndex) => { + if (index === tabIndex) { + tab.label = updatedLabel; + } + return tab; + }); + this.updateProperty(this.props.propertyName, JSON.stringify(updatedTabs)); }; addOption = () => { const tabs: Array<{ id: string; label: string; - }> = this.props.propertyValue ? this.props.propertyValue.slice() : []; + }> = _.isString(this.props.propertyValue) + ? JSON.parse(this.props.propertyValue) + : this.props.propertyValue; const newTabId = generateReactKey({ prefix: "tab" }); tabs.push({ id: newTabId, label: `Tab ${tabs.length + 1}` }); - this.updateProperty(this.props.propertyName, tabs); + this.updateProperty(this.props.propertyName, JSON.stringify(tabs)); }; static getControlType() { diff --git a/app/client/src/constants/WidgetValidation.ts b/app/client/src/constants/WidgetValidation.ts index af23144c7a..0dc2ac1f5c 100644 --- a/app/client/src/constants/WidgetValidation.ts +++ b/app/client/src/constants/WidgetValidation.ts @@ -12,6 +12,7 @@ export const VALIDATION_TYPES = { TABLE_DATA: "TABLE_DATA", OPTIONS_DATA: "OPTIONS_DATA", DATE: "DATE", + TABS_DATA: "TABS_DATA", CHART_DATA: "CHART_DATA", MARKERS: "MARKERS", ACTION_SELECTOR: "ACTION_SELECTOR", diff --git a/app/client/src/utils/DynamicBindingUtils.ts b/app/client/src/utils/DynamicBindingUtils.ts index dd0c057c52..20b86e971a 100644 --- a/app/client/src/utils/DynamicBindingUtils.ts +++ b/app/client/src/utils/DynamicBindingUtils.ts @@ -143,6 +143,13 @@ export const createDynamicValueString = ( 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; diff --git a/app/client/src/utils/Validators.ts b/app/client/src/utils/Validators.ts index dbe7f15f7b..1ad1c70e7d 100644 --- a/app/client/src/utils/Validators.ts +++ b/app/client/src/utils/Validators.ts @@ -207,6 +207,31 @@ export const VALIDATORS: Record = { }; } }, + [VALIDATION_TYPES.TABS_DATA]: ( + value: any, + props: WidgetProps, + dataTree?: DataTree, + ): ValidationResponse => { + const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY]( + value, + props, + dataTree, + ); + if (!isValid) { + return { + isValid, + parsed, + message: `${WIDGET_TYPE_VALIDATION_ERROR}: Tabs Data`, + }; + } else if (!_.every(parsed, datum => _.isObject(datum))) { + return { + isValid: false, + parsed: [], + message: `${WIDGET_TYPE_VALIDATION_ERROR}: Tabs Data`, + }; + } + return { isValid, parsed }; + }, [VALIDATION_TYPES.TABLE_DATA]: ( value: any, props: WidgetProps, @@ -237,6 +262,10 @@ export const VALIDATORS: Record = { props: WidgetProps, dataTree?: DataTree, ): ValidationResponse => { + if (_.isString(value)) { + value = value.replace(/\s/g, ""); + value = `${value}`; + } const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY]( value, props, @@ -249,27 +278,40 @@ export const VALIDATORS: Record = { message: `${WIDGET_TYPE_VALIDATION_ERROR}: Chart Data`, }; } - const hasChartData = _.every( + let validationMessage = ""; + let index = 0; + const isValidChartData = _.every( parsed, - (datum: { seriesName: any; data: any }) => { - if (_.isObject(datum)) { - return ( - _.isString(datum.seriesName) && - _.isArray(datum.data) && - _.every(datum.data, (item: { x: any; y: any }) => { - return _.isString(item.x) && !_.isUndefined(item.y); - }) + (datum: { name: string; data: any }) => { + const validatedResponse: { + isValid: boolean; + parsed: object; + message?: string; + } = VALIDATORS[VALIDATION_TYPES.ARRAY](datum.data, props, dataTree); + validationMessage = `${index}##${WIDGET_TYPE_VALIDATION_ERROR}: [{ "x": "val", "y": "val" }]`; + let isValidChart = validatedResponse.isValid; + if (validatedResponse.isValid) { + datum.data = validatedResponse.parsed; + isValidChart = _.every( + datum.data, + (chartPoint: { x: string; y: any }) => { + return ( + _.isObject(chartPoint) && + _.isString(chartPoint.x) && + !_.isUndefined(chartPoint.y) + ); + }, ); - } else { - return false; } + index++; + return isValidChart; }, ); - if (!hasChartData) { + if (!isValidChartData) { return { isValid: false, parsed: [], - message: `${WIDGET_TYPE_VALIDATION_ERROR}: Chart Data`, + message: validationMessage, }; } return { isValid, parsed }; diff --git a/app/client/src/widgets/TabsWidget.tsx b/app/client/src/widgets/TabsWidget.tsx index 5bd33c1628..f52729f49e 100644 --- a/app/client/src/widgets/TabsWidget.tsx +++ b/app/client/src/widgets/TabsWidget.tsx @@ -4,11 +4,19 @@ import { WidgetType, WidgetTypes } from "constants/WidgetConstants"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import WidgetFactory from "utils/WidgetFactory"; import { generateReactKey } from "utils/generators"; +import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { VALIDATION_TYPES } from "constants/WidgetValidation"; class TabsWidget extends BaseWidget< TabsWidgetProps, WidgetState > { + static getPropertyValidationMap(): WidgetPropertyValidationType { + return { + tabs: VALIDATION_TYPES.TABS_DATA, + }; + } + onTabChange = (tabId: string) => { this.updateWidgetMetaProperty("selectedTabId", tabId); };