diff --git a/app/client/src/components/designSystems/appsmith/ChartComponent.tsx b/app/client/src/components/designSystems/appsmith/ChartComponent.tsx index faccb28c53..ca49f9b40b 100644 --- a/app/client/src/components/designSystems/appsmith/ChartComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/ChartComponent.tsx @@ -1,10 +1,10 @@ -import _, { isString } from "lodash"; +import _, { get } from "lodash"; import React from "react"; import styled from "styled-components"; import { getBorderCSSShorthand, invisible } from "constants/DefaultTheme"; import { getAppsmithConfigs } from "configs"; -import { ChartData, ChartDataPoint, ChartType } from "widgets/ChartWidget"; +import { AllChartData, ChartDataPoint, ChartType } from "widgets/ChartWidget"; import log from "loglevel"; export interface CustomFusionChartConfig { @@ -43,7 +43,7 @@ FusionCharts.options.license({ export interface ChartComponentProps { chartType: ChartType; - chartData: ChartData[]; + chartData: AllChartData; customFusionChartConfig: CustomFusionChartConfig; xAxisName: string; yAxisName: string; @@ -73,7 +73,8 @@ class ChartComponent extends React.Component { getChartType = () => { const { chartType, allowHorizontalScroll, chartData } = this.props; - const isMSChart = chartData.length > 1; + const dataLength = Object.keys(chartData).length; + const isMSChart = dataLength > 1; switch (chartType) { case "PIE_CHART": return "pie2d"; @@ -107,9 +108,11 @@ class ChartComponent extends React.Component { }; getChartData = () => { - const chartData: ChartData[] = this.props.chartData; + const chartData: AllChartData = this.props.chartData; + const dataLength = Object.keys(chartData).length; - if (chartData.length === 0) { + // if datalength is zero, just pass a empty datum + if (dataLength === 0) { return [ { label: "", @@ -118,14 +121,13 @@ class ChartComponent extends React.Component { ]; } - let data: ChartDataPoint[] = chartData[0].data; - if (isString(chartData[0].data)) { - try { - data = JSON.parse(chartData[0].data); - } catch (e) { - data = []; - } + const firstKey = Object.keys(chartData)[0] as string; + let data = get(chartData, `${firstKey}.data`, []) as ChartDataPoint[]; + + if (!Array.isArray(data)) { + data = []; } + if (data.length === 0) { return [ { @@ -134,6 +136,7 @@ class ChartComponent extends React.Component { }, ]; } + return data.map((item) => { return { label: item.x, @@ -142,22 +145,30 @@ class ChartComponent extends React.Component { }); }; - getChartCategoriesMutliSeries = (chartData: ChartData[]) => { + getChartCategoriesMutliSeries = (chartData: AllChartData) => { const categories: string[] = []; - for (let index = 0; index < chartData.length; index++) { - const data: ChartDataPoint[] = chartData[index].data; + + Object.keys(chartData).forEach((key: string) => { + let data = get(chartData, `${key}.data`, []) as ChartDataPoint[]; + + if (!Array.isArray(data)) { + data = []; + } + for (let dataIndex = 0; dataIndex < data.length; dataIndex++) { const category = data[dataIndex].x; if (!categories.includes(category)) { categories.push(category); } } - } + }); + return categories; }; - getChartCategories = (chartData: ChartData[]) => { + getChartCategories = (chartData: AllChartData) => { const categories: string[] = this.getChartCategoriesMutliSeries(chartData); + if (categories.length === 0) { return [ { @@ -192,9 +203,18 @@ class ChartComponent extends React.Component { }); }; - getChartDataset = (chartData: ChartData[]) => { + /** + * creates dataset need by fusion chart from widget object-data + * + * @param chartData + * @returns + */ + getChartDataset = (chartData: AllChartData) => { const categories: string[] = this.getChartCategoriesMutliSeries(chartData); - return chartData.map((item: ChartData) => { + + const dataset = Object.keys(chartData).map((key: string) => { + const item = get(chartData, `${key}`); + const seriesChartData: Array { data: seriesChartData, }; }); + + return dataset; }; getChartConfig = () => { @@ -219,10 +241,9 @@ class ChartComponent extends React.Component { }; getChartDataSource = () => { - if ( - this.props.chartData.length <= 1 || - this.props.chartType === "PIE_CHART" - ) { + const dataLength = Object.keys(this.props.chartData).length; + + if (dataLength <= 1 || this.props.chartType === "PIE_CHART") { return { chart: this.getChartConfig(), data: this.getChartData(), diff --git a/app/client/src/components/propertyControls/ChartDataControl.tsx b/app/client/src/components/propertyControls/ChartDataControl.tsx index 17175a783d..bd7a8cafa7 100644 --- a/app/client/src/components/propertyControls/ChartDataControl.tsx +++ b/app/client/src/components/propertyControls/ChartDataControl.tsx @@ -1,5 +1,5 @@ import React from "react"; -import _ from "lodash"; +import { get, has, isString } from "lodash"; import BaseControl, { ControlProps } from "./BaseControl"; import { ControlWrapper, StyledPropertyPaneButton } from "./StyledControls"; import styled from "constants/DefaultTheme"; @@ -13,6 +13,8 @@ import { TabBehaviour, } from "components/editorComponents/CodeEditor/EditorConfig"; import { Size, Category } from "components/ads/Button"; +import { AllChartData, ChartData } from "widgets/ChartWidget"; +import { generateReactKey } from "utils/generators"; const Wrapper = styled.div` background-color: ${(props) => @@ -76,16 +78,15 @@ const Box = styled.div` `; type RenderComponentProps = { - index: number; - item: { - seriesName: string; - data: Array<{ x: string; y: string }> | string; - }; + index: string; + item: ChartData; length: number; - isValid: boolean; - validationMessage: string; - deleteOption: (index: number) => void; - updateOption: (index: number, key: string, value: string) => void; + validationMessage: { + data: string; + seriesName: string; + }; + deleteOption: (index: string) => void; + updateOption: (index: string, key: string, value: string) => void; evaluated: { seriesName: string; data: Array<{ x: string; y: string }> | any; @@ -100,9 +101,10 @@ function DataControlComponent(props: RenderComponentProps) { item, index, length, - isValid, evaluated, + validationMessage, } = props; + return ( @@ -160,7 +162,9 @@ function DataControlComponent(props: RenderComponentProps) { }} evaluatedValue={evaluated?.data} meta={{ - error: isValid ? "" : "There is an error", + error: has(validationMessage, "data") + ? get(validationMessage, "data") + : "", touched: true, }} theme={props.theme} @@ -176,95 +180,55 @@ 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; - }; - - getEvaluatedValue = () => { - if (Array.isArray(this.props.evaluatedValue)) { - return this.props.evaluatedValue; - } - return []; - }; - render() { - const chartData: Array<{ seriesName: string; data: string }> = _.isString( - this.props.propertyValue, - ) - ? [] + const chartData: AllChartData = isString(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, - ); - const evaluatedValue = this.getEvaluatedValue(); + const dataLength = Object.keys(chartData).length; + const { validationMessage } = this.props; + + const evaluatedValue = this.props.evaluatedValue; + const firstKey = Object.keys(chartData)[0] as string; + if (this.props.widgetProperties.chartType === "PIE_CHART") { - const data = chartData.length - ? chartData[0] + const data = dataLength + ? get(chartData, `${firstKey}`) : { seriesName: "", - data: "", + data: [], }; + return ( ); } + return ( - {chartData.map((data, index) => { + {Object.keys(chartData).map((key: string) => { + const data = get(chartData, `${key}`); + return ( ); @@ -284,27 +248,28 @@ class ChartDataControl extends BaseControl { ); } - deleteOption = (index: number) => { - this.deleteProperties([`${this.props.propertyName}[${index}]`]); + deleteOption = (index: string) => { + this.deleteProperties([`${this.props.propertyName}.${index}`]); }; updateOption = ( - index: number, + index: string, propertyName: string, updatedValue: string, ) => { this.updateProperty( - `${this.props.propertyName}[${index}].${propertyName}`, + `${this.props.propertyName}.${index}.${propertyName}`, updatedValue, ); }; + /** + * it adds new series data object in the chartData + */ addOption = () => { - const chartData: Array<{ - seriesName: string; - data: string; - }> = this.props.propertyValue; - this.updateProperty(`${this.props.propertyName}[${chartData.length}]`, { + const randomString = generateReactKey(); + + this.updateProperty(`${this.props.propertyName}.${randomString}`, { seriesName: "", data: JSON.stringify([{ x: "label", y: 50 }]), }); diff --git a/app/client/src/constants/WidgetValidation.ts b/app/client/src/constants/WidgetValidation.ts index 82f089d83b..1f5d32dbbf 100644 --- a/app/client/src/constants/WidgetValidation.ts +++ b/app/client/src/constants/WidgetValidation.ts @@ -17,8 +17,8 @@ export enum VALIDATION_TYPES { MIN_DATE = "MIN_DATE", MAX_DATE = "MAX_DATE", TABS_DATA = "TABS_DATA", - CHART_DATA = "CHART_DATA", LIST_DATA = "LIST_DATA", + CHART_SERIES_DATA = "CHART_SERIES_DATA", CUSTOM_FUSION_CHARTS_DATA = "CUSTOM_FUSION_CHARTS_DATA", MARKERS = "MARKERS", ACTION_SELECTOR = "ACTION_SELECTOR", diff --git a/app/client/src/entities/Widget/utils.test.ts b/app/client/src/entities/Widget/utils.test.ts index 286dde8a7a..99285b6d9d 100644 --- a/app/client/src/entities/Widget/utils.test.ts +++ b/app/client/src/entities/Widget/utils.test.ts @@ -186,12 +186,12 @@ describe("getAllPathsFromPropertyConfig", () => { chartName: "Sales on working days", allowHorizontalScroll: false, version: 1, - chartData: [ - { + chartData: { + "random-id": { seriesName: "", data: "{{Api1.data}}", }, - ], + }, xAxisName: "Last Week", yAxisName: "Total Order Revenue $", type: WidgetTypes.CHART_WIDGET, @@ -206,7 +206,7 @@ describe("getAllPathsFromPropertyConfig", () => { widgetId: "x1naz9is2b", dynamicBindingPathList: [ { - key: "chartData[0].data", + key: "chartData.random-id.data", }, ], }; @@ -216,8 +216,8 @@ describe("getAllPathsFromPropertyConfig", () => { bindingPaths: { chartType: EvaluationSubstitutionType.TEMPLATE, chartName: EvaluationSubstitutionType.TEMPLATE, - "chartData[0].seriesName": EvaluationSubstitutionType.TEMPLATE, - "chartData[0].data": EvaluationSubstitutionType.TEMPLATE, + "chartData.random-id.seriesName": EvaluationSubstitutionType.TEMPLATE, + "chartData.random-id.data": EvaluationSubstitutionType.TEMPLATE, xAxisName: EvaluationSubstitutionType.TEMPLATE, yAxisName: EvaluationSubstitutionType.TEMPLATE, isVisible: EvaluationSubstitutionType.TEMPLATE, @@ -226,8 +226,8 @@ describe("getAllPathsFromPropertyConfig", () => { onDataPointClick: true, }, validationPaths: { - "chartData[0].data": "CHART_DATA", - "chartData[0].seriesName": "TEXT", + "chartData.random-id.data": "CHART_SERIES_DATA", + "chartData.random-id.seriesName": "TEXT", chartName: "TEXT", isVisible: "BOOLEAN", xAxisName: "TEXT", diff --git a/app/client/src/entities/Widget/utils.ts b/app/client/src/entities/Widget/utils.ts index 538218fd3d..b7965cd813 100644 --- a/app/client/src/entities/Widget/utils.ts +++ b/app/client/src/entities/Widget/utils.ts @@ -1,6 +1,6 @@ import { WidgetProps } from "widgets/BaseWidget"; import { PropertyPaneConfig } from "constants/PropertyControlConstants"; -import { get } from "lodash"; +import { get, isObject, isUndefined } from "lodash"; import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; @@ -29,6 +29,7 @@ export const getAllPathsFromPropertyConfig = ( if ("hidden" in controlConfig) { isHidden = controlConfig.hidden(widget, basePath); } + if (!isHidden) { if ( controlConfig.isBindProperty && @@ -101,34 +102,36 @@ export const getAllPathsFromPropertyConfig = ( } } if (controlConfig.children) { - // Property in array structure const basePropertyPath = controlConfig.propertyName; const widgetPropertyValue = get(widget, basePropertyPath, []); - if (Array.isArray(widgetPropertyValue)) { - widgetPropertyValue.forEach( - (arrayPropertyValue: any, index: number) => { - const arrayIndexPropertyPath = `${basePropertyPath}[${index}]`; - controlConfig.children.forEach((childPropertyConfig: any) => { - const childArrayPropertyPath = `${arrayIndexPropertyPath}.${childPropertyConfig.propertyName}`; - if ( - childPropertyConfig.isBindProperty && - !childPropertyConfig.isTriggerProperty - ) { - bindingPaths[childArrayPropertyPath] = - EvaluationSubstitutionType.TEMPLATE; - if (childPropertyConfig.validation) { - validationPaths[childArrayPropertyPath] = - childPropertyConfig.validation; - } - } else if ( - childPropertyConfig.isBindProperty && - childPropertyConfig.isTriggerProperty - ) { - triggerPaths[childArrayPropertyPath] = true; + // Property in object structure + if ( + !isUndefined(widgetPropertyValue) && + isObject(widgetPropertyValue) + ) { + Object.keys(widgetPropertyValue).map((key: string) => { + const objectIndexPropertyPath = `${basePropertyPath}.${key}`; + controlConfig.children.forEach((childPropertyConfig: any) => { + const childArrayPropertyPath = `${objectIndexPropertyPath}.${childPropertyConfig.propertyName}`; + + if ( + childPropertyConfig.isBindProperty && + !childPropertyConfig.isTriggerProperty + ) { + bindingPaths[childArrayPropertyPath] = + EvaluationSubstitutionType.TEMPLATE; + if (childPropertyConfig.validation) { + validationPaths[childArrayPropertyPath] = + childPropertyConfig.validation; } - }); - }, - ); + } else if ( + childPropertyConfig.isBindProperty && + childPropertyConfig.isTriggerProperty + ) { + triggerPaths[childArrayPropertyPath] = true; + } + }); + }); } } }); diff --git a/app/client/src/mockResponses/WidgetConfigResponse.tsx b/app/client/src/mockResponses/WidgetConfigResponse.tsx index d9dcfb2062..61951129e5 100644 --- a/app/client/src/mockResponses/WidgetConfigResponse.tsx +++ b/app/client/src/mockResponses/WidgetConfigResponse.tsx @@ -384,8 +384,8 @@ const WidgetConfigResponse: WidgetConfigReducerState = { chartName: "Sales on working days", allowHorizontalScroll: false, version: 1, - chartData: [ - { + chartData: { + [generateReactKey()]: { seriesName: "Sales", data: [ { @@ -418,7 +418,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = { }, ], }, - ], + }, xAxisName: "Last Week", yAxisName: "Total Order Revenue $", }, diff --git a/app/client/src/utils/WidgetPropsUtils.test.tsx b/app/client/src/utils/WidgetPropsUtils.test.tsx new file mode 100644 index 0000000000..592130a3b0 --- /dev/null +++ b/app/client/src/utils/WidgetPropsUtils.test.tsx @@ -0,0 +1,90 @@ +import * as generators from "../utils/generators"; +import { RenderModes, WidgetTypes } from "constants/WidgetConstants"; +import { migrateChartDataFromArrayToObject } from "./WidgetPropsUtils"; + +describe("WidgetProps tests", () => { + it("it checks if array to object migration functions for chart widget ", () => { + const input = { + type: WidgetTypes.CANVAS_WIDGET, + widgetId: "0", + widgetName: "canvas", + parentColumnSpace: 1, + parentRowSpace: 1, + leftColumn: 0, + rightColumn: 0, + topRow: 0, + bottomRow: 0, + version: 17, + isLoading: false, + renderMode: RenderModes.CANVAS, + children: [ + { + widgetId: "some-random-id", + widgetName: "chart1", + parentColumnSpace: 1, + parentRowSpace: 1, + leftColumn: 0, + rightColumn: 0, + topRow: 0, + bottomRow: 0, + version: 17, + isLoading: false, + renderMode: RenderModes.CANVAS, + type: WidgetTypes.CHART_WIDGET, + chartData: [ + { + seriesName: "seris1", + data: [{ x: 1, y: 2 }], + }, + ], + }, + ], + }; + + // mocking implementation of our generateReactKey function + const generatorReactKeyMock = jest.spyOn(generators, "generateReactKey"); + generatorReactKeyMock.mockImplementation(() => "some-random-key"); + + const result = migrateChartDataFromArrayToObject(input); + + const output = { + type: WidgetTypes.CANVAS_WIDGET, + widgetId: "0", + widgetName: "canvas", + parentColumnSpace: 1, + parentRowSpace: 1, + leftColumn: 0, + rightColumn: 0, + topRow: 0, + bottomRow: 0, + version: 17, + isLoading: false, + renderMode: RenderModes.CANVAS, + children: [ + { + widgetId: "some-random-id", + widgetName: "chart1", + parentColumnSpace: 1, + parentRowSpace: 1, + leftColumn: 0, + rightColumn: 0, + topRow: 0, + bottomRow: 0, + version: 17, + isLoading: false, + renderMode: RenderModes.CANVAS, + type: WidgetTypes.CHART_WIDGET, + dynamicBindingPathList: [], + chartData: { + "some-random-key": { + seriesName: "seris1", + data: [{ x: 1, y: 2 }], + }, + }, + }, + ], + }; + + expect(result).toStrictEqual(output); + }); +}); diff --git a/app/client/src/utils/WidgetPropsUtils.tsx b/app/client/src/utils/WidgetPropsUtils.tsx index cd3247903a..2cc76087ed 100644 --- a/app/client/src/utils/WidgetPropsUtils.tsx +++ b/app/client/src/utils/WidgetPropsUtils.tsx @@ -21,7 +21,7 @@ import defaultTemplate from "templates/default"; import { generateReactKey } from "./generators"; import { ChartDataPoint } from "widgets/ChartWidget"; import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer"; -import { isString } from "lodash"; +import { isString, set } from "lodash"; import log from "loglevel"; import { migrateTablePrimaryColumnsBindings, @@ -354,6 +354,62 @@ function migrateOldChartData(currentDSL: ContainerWidgetProps) { return currentDSL; } +/** + * changes chartData which we were using as array. now it will be a object + * + * + * @param currentDSL + * @returns + */ +export function migrateChartDataFromArrayToObject( + currentDSL: ContainerWidgetProps, +) { + currentDSL.children = currentDSL.children?.map((children: WidgetProps) => { + if (children.type === WidgetTypes.CHART_WIDGET) { + if (Array.isArray(children.chartData)) { + const newChartData = {}; + const dynamicBindingPathList = children?.dynamicBindingPathList + ? children?.dynamicBindingPathList.slice() + : []; + + children.chartData.map((datum: any, index: number) => { + const generatedKey = generateReactKey(); + set(newChartData, `${generatedKey}`, datum); + + if ( + Array.isArray(children.dynamicBindingPathList) && + children.dynamicBindingPathList?.findIndex( + (path) => (path.key = `chartData[${index}].data`), + ) > -1 + ) { + const foundIndex = children.dynamicBindingPathList.findIndex( + (path) => (path.key = `chartData[${index}].data`), + ); + + dynamicBindingPathList[foundIndex] = { + key: `chartData.${generatedKey}.data`, + }; + } + }); + + children.dynamicBindingPathList = dynamicBindingPathList; + children.chartData = newChartData; + } + } else if ( + children.type === WidgetTypes.CONTAINER_WIDGET || + children.type === WidgetTypes.FORM_WIDGET || + children.type === WidgetTypes.CANVAS_WIDGET || + children.type === WidgetTypes.TABS_WIDGET + ) { + children = migrateChartDataFromArrayToObject(children); + } + + return children; + }); + + return currentDSL; +} + export const calculateDynamicHeight = ( canvasWidgets: { [widgetId: string]: FlattenedWidgetProps; @@ -482,6 +538,11 @@ const transformDSL = (currentDSL: ContainerWidgetProps) => { currentDSL.version = 16; } + if (currentDSL.version === 16) { + currentDSL = migrateChartDataFromArrayToObject(currentDSL); + currentDSL.version = 17; + } + return currentDSL; }; diff --git a/app/client/src/widgets/ChartWidget/index.tsx b/app/client/src/widgets/ChartWidget/index.tsx index 84761e13cb..305a8231c2 100644 --- a/app/client/src/widgets/ChartWidget/index.tsx +++ b/app/client/src/widgets/ChartWidget/index.tsx @@ -81,6 +81,9 @@ export interface ChartDataPoint { y: any; } +export interface AllChartData { + [key: string]: ChartData; +} export interface ChartData { seriesName?: string; data: ChartDataPoint[]; @@ -88,7 +91,7 @@ export interface ChartData { export interface ChartWidgetProps extends WidgetProps, WithMeta { chartType: ChartType; - chartData: ChartData[]; + chartData: AllChartData; customFusionChartConfig: { config: CustomFusionChartConfig }; xAxisName: string; yAxisName: string; diff --git a/app/client/src/widgets/ChartWidget/propertyConfig.test.ts b/app/client/src/widgets/ChartWidget/propertyConfig.test.ts index 41da9f158d..124515f6b3 100644 --- a/app/client/src/widgets/ChartWidget/propertyConfig.test.ts +++ b/app/client/src/widgets/ChartWidget/propertyConfig.test.ts @@ -68,14 +68,20 @@ describe("Validate Chart Widget's property config", () => { }); it("Validates config when chartType is CUSTOM_FUSION_CHART", () => { - const hiddenFn: (props: any) => boolean = get(config, "[1].hidden"); + const hiddenFn: (props: any) => boolean = get( + config, + "[1].children.[0].hidden", + ); let result = true; if (hiddenFn) result = hiddenFn({ chartType: "CUSTOM_FUSION_CHART" }); expect(result).toBeFalsy(); }); it("Validates that sections are hidden when chartType is CUSTOM_FUSION_CHART", () => { - const hiddenFns = [get(config, "[2].hidden"), get(config, "[3].hidden")]; + const hiddenFns = [ + get(config, "[1].children.[1].hidden"), + get(config, "[2].hidden"), + ]; hiddenFns.forEach((fn: (props: any) => boolean) => { const result = fn({ chartType: "CUSTOM_FUSION_CHART" }); expect(result).toBeTruthy(); diff --git a/app/client/src/widgets/ChartWidget/propertyConfig.ts b/app/client/src/widgets/ChartWidget/propertyConfig.ts index 6a16419166..722788521b 100644 --- a/app/client/src/widgets/ChartWidget/propertyConfig.ts +++ b/app/client/src/widgets/ChartWidget/propertyConfig.ts @@ -62,32 +62,32 @@ export default [ }, ], }, - { - helpText: - "Manually configure a FusionChart, see https://www.fusioncharts.com", - propertyName: "customFusionChartConfig", - placeholderText: `Enter {type: "bar2d","dataSource": {}}`, - label: "Custom Fusion Chart Configuration", - controlType: "CUSTOM_FUSION_CHARTS_DATA", - isBindProperty: true, - isTriggerProperty: false, - hidden: (x: any) => x.chartType !== "CUSTOM_FUSION_CHART", - validation: VALIDATION_TYPES.CUSTOM_FUSION_CHARTS_DATA, - }, { sectionName: "Chart Data", - hidden: (props: ChartWidgetProps) => - props.chartType === "CUSTOM_FUSION_CHART", children: [ + { + helpText: + "Manually configure a FusionChart, see https://www.fusioncharts.com", + placeholderText: `Enter {type: "bar2d","dataSource": {}}`, + propertyName: "customFusionChartConfig", + label: "Custom Fusion Chart Configuration", + controlType: "CUSTOM_FUSION_CHARTS_DATA", + isBindProperty: true, + isTriggerProperty: false, + validation: VALIDATION_TYPES.CUSTOM_FUSION_CHARTS_DATA, + hidden: (props: ChartWidgetProps) => + props.chartType !== "CUSTOM_FUSION_CHART", + }, { helpText: "Populates the chart with the data", propertyName: "chartData", placeholderText: 'Enter [{ "x": "val", "y": "val" }]', label: "Chart Series", controlType: "CHART_DATA", - isBindProperty: false, isTriggerProperty: false, + hidden: (props: ChartWidgetProps) => + props.chartType === "CUSTOM_FUSION_CHART", children: [ { helpText: "Series Name", @@ -105,7 +105,7 @@ export default [ controlType: "INPUT_TEXT_AREA", isBindProperty: true, isTriggerProperty: false, - validation: VALIDATION_TYPES.CHART_DATA, + validation: VALIDATION_TYPES.CHART_SERIES_DATA, }, ], }, diff --git a/app/client/src/workers/DataTreeEvaluator.ts b/app/client/src/workers/DataTreeEvaluator.ts index cbcfacabc1..389c7a7dfd 100644 --- a/app/client/src/workers/DataTreeEvaluator.ts +++ b/app/client/src/workers/DataTreeEvaluator.ts @@ -404,6 +404,8 @@ export default class DataTreeEvaluator { } else { evalPropertyValue = unEvalPropertyValue; } + + // debugger; if (isWidget(entity)) { const widgetEntity = entity; const defaultPropertyMap = this.widgetConfigMap[widgetEntity.type] diff --git a/app/client/src/workers/validations.test.ts b/app/client/src/workers/validations.test.ts index 21299bbb1e..78a03dd25f 100644 --- a/app/client/src/workers/validations.test.ts +++ b/app/client/src/workers/validations.test.ts @@ -19,109 +19,47 @@ const DUMMY_WIDGET: WidgetProps = { }; describe("Validate Validators", () => { - const validator = VALIDATORS.CHART_DATA; - it("correctly validates chart data ", () => { + it("correctly validates chart series data ", () => { const cases = [ { - input: [ - { - seriesName: "Sales", - data: [{ x: "Jan", y: 1000 }], - }, - ], + input: [{ x: "Jan", y: 1000 }], output: { isValid: true, - parsed: [ - { - seriesName: "Sales", - data: [{ x: "Jan", y: 1000 }], - }, - ], - transformed: [ - { - seriesName: "Sales", - data: [{ x: "Jan", y: 1000 }], - }, - ], + parsed: [{ x: "Jan", y: 1000 }], + transformed: [{ x: "Jan", y: 1000 }], }, }, { - input: [ - { - seriesName: "Sales", - data: [{ x: "Jan", y: 1000 }, { x: "Feb" }], - }, - ], + input: [{ x: "Jan", y: 1000 }, { x: "Feb" }], output: { isValid: false, message: - '0##This value does not evaluate to type "Array"', - parsed: [ - { - seriesName: "Sales", - data: [], - }, - ], - transformed: [ - { - seriesName: "Sales", - data: [{ x: "Jan", y: 1000 }, { x: "Feb" }], - }, - ], + 'This value does not evaluate to type: [{ "x": "val", "y": "val" }]', + parsed: [], + transformed: [{ x: "Jan", y: 1000 }, { x: "Feb" }], }, }, { - input: [ - { - seriesName: "Sales", - data: undefined, - }, - { - seriesName: "Expenses", - data: [ - { x: "Jan", y: 1000 }, - { x: "Feb", y: 2000 }, - ], - }, - ], + input: undefined, output: { isValid: false, message: - '0##This value does not evaluate to type "Array"', - parsed: [ - { - seriesName: "Sales", - data: [], - }, - { - seriesName: "Expenses", - data: [ - { x: "Jan", y: 1000 }, - { x: "Feb", y: 2000 }, - ], - }, - ], - transformed: [ - { - seriesName: "Sales", - data: undefined, - }, - { - seriesName: "Expenses", - data: [ - { x: "Jan", y: 1000 }, - { x: "Feb", y: 2000 }, - ], - }, - ], + 'This value does not evaluate to type: [{ "x": "val", "y": "val" }]', + parsed: [], + transformed: undefined, }, }, ]; for (const testCase of cases) { - const response = validator(testCase.input, DUMMY_WIDGET, {}); + const response = VALIDATORS.CHART_SERIES_DATA( + testCase.input, + DUMMY_WIDGET, + {}, + ); expect(response).toStrictEqual(testCase.output); } }); + it("Correctly validates page number", () => { const input = [0, -1, undefined, null, 2, "abcd", [], ""]; const expected = [1, 1, 1, 1, 2, 1, 1, 1]; diff --git a/app/client/src/workers/validations.ts b/app/client/src/workers/validations.ts index 1eff040443..12bea9d60c 100644 --- a/app/client/src/workers/validations.ts +++ b/app/client/src/workers/validations.ts @@ -201,6 +201,7 @@ export const VALIDATORS: Record = { if (isString(value)) { parsed = JSON.parse(parsed as string); } + if (!Array.isArray(parsed)) { return { isValid: false, @@ -209,6 +210,7 @@ export const VALIDATORS: Record = { message: `${WIDGET_TYPE_VALIDATION_ERROR} "Array"`, }; } + return { isValid: true, parsed, transformed: parsed }; } catch (e) { return { @@ -331,80 +333,56 @@ export const VALIDATORS: Record = { } return { isValid, parsed }; }, - [VALIDATION_TYPES.CHART_DATA]: ( + [VALIDATION_TYPES.CHART_SERIES_DATA]: ( value: any, props: WidgetProps, dataTree?: DataTree, ): ValidationResponse => { - const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY]( - value, - props, - dataTree, - ); + let parsed = []; + let transformed = []; + let isValid = false; + let validationMessage = ""; + + try { + const validatedResponse: ValidationResponse = VALIDATORS[ + VALIDATION_TYPES.ARRAY + ](value, props, dataTree); + + if (validatedResponse.isValid) { + isValid = every( + validatedResponse.parsed, + (chartPoint: { x: string; y: any }) => { + return ( + isObject(chartPoint) && + isString(chartPoint.x) && + !isUndefined(chartPoint.y) + ); + }, + ); + } + + if (!isValid) { + parsed = []; + transformed = validatedResponse.transformed; + validationMessage = `${WIDGET_TYPE_VALIDATION_ERROR}: [{ "x": "val", "y": "val" }]`; + } else { + parsed = validatedResponse.parsed; + transformed = validatedResponse.parsed; + } + } catch (e) { + console.error(e); + } + if (!isValid) { - return { - isValid, - parsed, - transformed: parsed, - message: `${WIDGET_TYPE_VALIDATION_ERROR} "Array"`, - }; - } - let validationMessage = ""; - let index = 0; - const parsedChartData = []; - let isValidChart = true; - - for (const seriesData of parsed) { - let isValidSeries = false; - try { - const validatedResponse: { - isValid: boolean; - parsed: Array; - message?: string; - } = VALIDATORS[VALIDATION_TYPES.ARRAY]( - seriesData.data, - props, - dataTree, - ); - if (validatedResponse.isValid) { - isValidSeries = every( - validatedResponse.parsed, - (chartPoint: { x: string; y: any }) => { - return ( - isObject(chartPoint) && - isString(chartPoint.x) && - !isUndefined(chartPoint.y) - ); - }, - ); - } - if (!isValidSeries) { - isValidChart = false; - parsedChartData.push({ - ...seriesData, - data: [], - }); - validationMessage = `${index}##${WIDGET_TYPE_VALIDATION_ERROR} "Array"`; - } else { - parsedChartData.push({ - ...seriesData, - data: validatedResponse.parsed, - }); - } - } catch (e) { - console.error(e); - } - index++; - } - if (!isValidChart) { return { isValid: false, - parsed: parsedChartData, - transformed: parsed, + parsed: [], + transformed: transformed, message: validationMessage, }; } - return { isValid, parsed: parsedChartData, transformed: parsedChartData }; + + return { isValid, parsed, transformed }; }, [VALIDATION_TYPES.CUSTOM_FUSION_CHARTS_DATA]: ( value: any, @@ -419,6 +397,7 @@ export const VALIDATORS: Record = { if (props.chartName && parsed.dataSource && parsed.dataSource.chart) { parsed.dataSource.chart.caption = props.chartName; } + if (!isValid) { return { isValid,