Fix: Update chart from array to object (#3907)

* update structure of chart data

* update chart data

* update chart data structure

* remove array like validator

* remove log

* remove log

* widget utils + update tests for chart data validations

* update utils test

* add migrations

* remove unnecessary helper function

* remove unnecessary helper function

* update validation test cases

* WIP

* WIP 2

* Remove validationConfigMap from widget and add to properties

* Update data tree validator to get validation from the correct place

* Minor reference fixes

* Fix test mocks

* fix for bad setting of nested property path

* add test for migration for chart widget function

* fix test for widget utils

* remove unused import;

* remove unsued import

* update migration version

* remove console and add check if data is array

* fix custom fusion chart + validation not working issue

* move custom fusion chart config to chart data section

* remove console and unused import

* fix test

* fix property config test

* fix dynamicbinding path list in migration

* remove old chart validation

* remove old validation test

* fix widget prop utils test

* remove array codepath for widget utils

* fix utils test

* fix utils test

* fix prettier issue

Co-authored-by: Pawan Kumar <pawankumar@Pawans-MacBook-Pro.local>
Co-authored-by: root <root@DESKTOP-9GENCK0.localdomain>
Co-authored-by: hetunandu <hetu@appsmith.com>
Co-authored-by: Hetu Nandu <hetunandu@gmail.com>
This commit is contained in:
Pawan Kumar 2021-04-26 16:05:59 +05:30 committed by GitHub
parent 46b67577dd
commit d814e780ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 378 additions and 310 deletions

View File

@ -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<ChartComponentProps> {
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<ChartComponentProps> {
};
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<ChartComponentProps> {
];
}
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<ChartComponentProps> {
},
];
}
return data.map((item) => {
return {
label: item.x,
@ -142,22 +145,30 @@ class ChartComponent extends React.Component<ChartComponentProps> {
});
};
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<ChartComponentProps> {
});
};
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<Record<
string,
unknown
@ -204,6 +224,8 @@ class ChartComponent extends React.Component<ChartComponentProps> {
data: seriesChartData,
};
});
return dataset;
};
getChartConfig = () => {
@ -219,10 +241,9 @@ class ChartComponent extends React.Component<ChartComponentProps> {
};
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(),

View File

@ -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 (
<StyledOptionControlWrapper orientation={"VERTICAL"}>
<ActionHolder>
@ -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<ControlProps> {
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 (
<DataControlComponent
index={0}
index={firstKey}
item={data}
length={1}
deleteOption={this.deleteOption}
updateOption={this.updateOption}
isValid={validations[0].isValid}
validationMessage={validations[0].validationMessage}
evaluated={evaluatedValue[0]}
validationMessage={get(validationMessage, `${firstKey}`)}
evaluated={get(evaluatedValue, `${firstKey}`)}
theme={this.props.theme}
/>
);
}
return (
<React.Fragment>
<Wrapper>
{chartData.map((data, index) => {
{Object.keys(chartData).map((key: string) => {
const data = get(chartData, `${key}`);
return (
<DataControlComponent
key={index}
index={index}
key={key}
index={key}
item={data}
length={dataLength}
deleteOption={this.deleteOption}
updateOption={this.updateOption}
isValid={validations[index].isValid}
validationMessage={validations[index].validationMessage}
evaluated={evaluatedValue[index]}
validationMessage={get(validationMessage, `${key}`)}
evaluated={get(evaluatedValue, `${key}`)}
theme={this.props.theme}
/>
);
@ -284,27 +248,28 @@ class ChartDataControl extends BaseControl<ControlProps> {
);
}
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 }]),
});

View File

@ -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",

View File

@ -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",

View File

@ -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;
}
});
});
}
}
});

View File

@ -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 $",
},

View File

@ -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);
});
});

View File

@ -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<WidgetProps>) {
return currentDSL;
}
/**
* changes chartData which we were using as array. now it will be a object
*
*
* @param currentDSL
* @returns
*/
export function migrateChartDataFromArrayToObject(
currentDSL: ContainerWidgetProps<WidgetProps>,
) {
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<WidgetProps>) => {
currentDSL.version = 16;
}
if (currentDSL.version === 16) {
currentDSL = migrateChartDataFromArrayToObject(currentDSL);
currentDSL.version = 17;
}
return currentDSL;
};

View File

@ -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;

View File

@ -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();

View File

@ -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,
},
],
},

View File

@ -404,6 +404,8 @@ export default class DataTreeEvaluator {
} else {
evalPropertyValue = unEvalPropertyValue;
}
// debugger;
if (isWidget(entity)) {
const widgetEntity = entity;
const defaultPropertyMap = this.widgetConfigMap[widgetEntity.type]

View File

@ -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<x:string, y:number>"',
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<x:string, y:number>"',
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];

View File

@ -201,6 +201,7 @@ export const VALIDATORS: Record<VALIDATION_TYPES, Validator> = {
if (isString(value)) {
parsed = JSON.parse(parsed as string);
}
if (!Array.isArray(parsed)) {
return {
isValid: false,
@ -209,6 +210,7 @@ export const VALIDATORS: Record<VALIDATION_TYPES, Validator> = {
message: `${WIDGET_TYPE_VALIDATION_ERROR} "Array"`,
};
}
return { isValid: true, parsed, transformed: parsed };
} catch (e) {
return {
@ -331,80 +333,56 @@ export const VALIDATORS: Record<VALIDATION_TYPES, Validator> = {
}
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<x:string, y:number>"`,
};
}
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<unknown>;
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<x:string, y:number>"`;
} 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<VALIDATION_TYPES, Validator> = {
if (props.chartName && parsed.dataSource && parsed.dataSource.chart) {
parsed.dataSource.chart.caption = props.chartName;
}
if (!isValid) {
return {
isValid,