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:
parent
46b67577dd
commit
d814e780ed
|
|
@ -1,10 +1,10 @@
|
||||||
import _, { isString } from "lodash";
|
import _, { get } from "lodash";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
import { getBorderCSSShorthand, invisible } from "constants/DefaultTheme";
|
import { getBorderCSSShorthand, invisible } from "constants/DefaultTheme";
|
||||||
import { getAppsmithConfigs } from "configs";
|
import { getAppsmithConfigs } from "configs";
|
||||||
import { ChartData, ChartDataPoint, ChartType } from "widgets/ChartWidget";
|
import { AllChartData, ChartDataPoint, ChartType } from "widgets/ChartWidget";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
|
|
||||||
export interface CustomFusionChartConfig {
|
export interface CustomFusionChartConfig {
|
||||||
|
|
@ -43,7 +43,7 @@ FusionCharts.options.license({
|
||||||
|
|
||||||
export interface ChartComponentProps {
|
export interface ChartComponentProps {
|
||||||
chartType: ChartType;
|
chartType: ChartType;
|
||||||
chartData: ChartData[];
|
chartData: AllChartData;
|
||||||
customFusionChartConfig: CustomFusionChartConfig;
|
customFusionChartConfig: CustomFusionChartConfig;
|
||||||
xAxisName: string;
|
xAxisName: string;
|
||||||
yAxisName: string;
|
yAxisName: string;
|
||||||
|
|
@ -73,7 +73,8 @@ class ChartComponent extends React.Component<ChartComponentProps> {
|
||||||
|
|
||||||
getChartType = () => {
|
getChartType = () => {
|
||||||
const { chartType, allowHorizontalScroll, chartData } = this.props;
|
const { chartType, allowHorizontalScroll, chartData } = this.props;
|
||||||
const isMSChart = chartData.length > 1;
|
const dataLength = Object.keys(chartData).length;
|
||||||
|
const isMSChart = dataLength > 1;
|
||||||
switch (chartType) {
|
switch (chartType) {
|
||||||
case "PIE_CHART":
|
case "PIE_CHART":
|
||||||
return "pie2d";
|
return "pie2d";
|
||||||
|
|
@ -107,9 +108,11 @@ class ChartComponent extends React.Component<ChartComponentProps> {
|
||||||
};
|
};
|
||||||
|
|
||||||
getChartData = () => {
|
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 [
|
return [
|
||||||
{
|
{
|
||||||
label: "",
|
label: "",
|
||||||
|
|
@ -118,14 +121,13 @@ class ChartComponent extends React.Component<ChartComponentProps> {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
let data: ChartDataPoint[] = chartData[0].data;
|
const firstKey = Object.keys(chartData)[0] as string;
|
||||||
if (isString(chartData[0].data)) {
|
let data = get(chartData, `${firstKey}.data`, []) as ChartDataPoint[];
|
||||||
try {
|
|
||||||
data = JSON.parse(chartData[0].data);
|
if (!Array.isArray(data)) {
|
||||||
} catch (e) {
|
data = [];
|
||||||
data = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
@ -134,6 +136,7 @@ class ChartComponent extends React.Component<ChartComponentProps> {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.map((item) => {
|
return data.map((item) => {
|
||||||
return {
|
return {
|
||||||
label: item.x,
|
label: item.x,
|
||||||
|
|
@ -142,22 +145,30 @@ class ChartComponent extends React.Component<ChartComponentProps> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
getChartCategoriesMutliSeries = (chartData: ChartData[]) => {
|
getChartCategoriesMutliSeries = (chartData: AllChartData) => {
|
||||||
const categories: string[] = [];
|
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++) {
|
for (let dataIndex = 0; dataIndex < data.length; dataIndex++) {
|
||||||
const category = data[dataIndex].x;
|
const category = data[dataIndex].x;
|
||||||
if (!categories.includes(category)) {
|
if (!categories.includes(category)) {
|
||||||
categories.push(category);
|
categories.push(category);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
return categories;
|
return categories;
|
||||||
};
|
};
|
||||||
|
|
||||||
getChartCategories = (chartData: ChartData[]) => {
|
getChartCategories = (chartData: AllChartData) => {
|
||||||
const categories: string[] = this.getChartCategoriesMutliSeries(chartData);
|
const categories: string[] = this.getChartCategoriesMutliSeries(chartData);
|
||||||
|
|
||||||
if (categories.length === 0) {
|
if (categories.length === 0) {
|
||||||
return [
|
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);
|
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<
|
const seriesChartData: Array<Record<
|
||||||
string,
|
string,
|
||||||
unknown
|
unknown
|
||||||
|
|
@ -204,6 +224,8 @@ class ChartComponent extends React.Component<ChartComponentProps> {
|
||||||
data: seriesChartData,
|
data: seriesChartData,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return dataset;
|
||||||
};
|
};
|
||||||
|
|
||||||
getChartConfig = () => {
|
getChartConfig = () => {
|
||||||
|
|
@ -219,10 +241,9 @@ class ChartComponent extends React.Component<ChartComponentProps> {
|
||||||
};
|
};
|
||||||
|
|
||||||
getChartDataSource = () => {
|
getChartDataSource = () => {
|
||||||
if (
|
const dataLength = Object.keys(this.props.chartData).length;
|
||||||
this.props.chartData.length <= 1 ||
|
|
||||||
this.props.chartType === "PIE_CHART"
|
if (dataLength <= 1 || this.props.chartType === "PIE_CHART") {
|
||||||
) {
|
|
||||||
return {
|
return {
|
||||||
chart: this.getChartConfig(),
|
chart: this.getChartConfig(),
|
||||||
data: this.getChartData(),
|
data: this.getChartData(),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import _ from "lodash";
|
import { get, has, isString } from "lodash";
|
||||||
import BaseControl, { ControlProps } from "./BaseControl";
|
import BaseControl, { ControlProps } from "./BaseControl";
|
||||||
import { ControlWrapper, StyledPropertyPaneButton } from "./StyledControls";
|
import { ControlWrapper, StyledPropertyPaneButton } from "./StyledControls";
|
||||||
import styled from "constants/DefaultTheme";
|
import styled from "constants/DefaultTheme";
|
||||||
|
|
@ -13,6 +13,8 @@ import {
|
||||||
TabBehaviour,
|
TabBehaviour,
|
||||||
} from "components/editorComponents/CodeEditor/EditorConfig";
|
} from "components/editorComponents/CodeEditor/EditorConfig";
|
||||||
import { Size, Category } from "components/ads/Button";
|
import { Size, Category } from "components/ads/Button";
|
||||||
|
import { AllChartData, ChartData } from "widgets/ChartWidget";
|
||||||
|
import { generateReactKey } from "utils/generators";
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
background-color: ${(props) =>
|
background-color: ${(props) =>
|
||||||
|
|
@ -76,16 +78,15 @@ const Box = styled.div`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type RenderComponentProps = {
|
type RenderComponentProps = {
|
||||||
index: number;
|
index: string;
|
||||||
item: {
|
item: ChartData;
|
||||||
seriesName: string;
|
|
||||||
data: Array<{ x: string; y: string }> | string;
|
|
||||||
};
|
|
||||||
length: number;
|
length: number;
|
||||||
isValid: boolean;
|
validationMessage: {
|
||||||
validationMessage: string;
|
data: string;
|
||||||
deleteOption: (index: number) => void;
|
seriesName: string;
|
||||||
updateOption: (index: number, key: string, value: string) => void;
|
};
|
||||||
|
deleteOption: (index: string) => void;
|
||||||
|
updateOption: (index: string, key: string, value: string) => void;
|
||||||
evaluated: {
|
evaluated: {
|
||||||
seriesName: string;
|
seriesName: string;
|
||||||
data: Array<{ x: string; y: string }> | any;
|
data: Array<{ x: string; y: string }> | any;
|
||||||
|
|
@ -100,9 +101,10 @@ function DataControlComponent(props: RenderComponentProps) {
|
||||||
item,
|
item,
|
||||||
index,
|
index,
|
||||||
length,
|
length,
|
||||||
isValid,
|
|
||||||
evaluated,
|
evaluated,
|
||||||
|
validationMessage,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledOptionControlWrapper orientation={"VERTICAL"}>
|
<StyledOptionControlWrapper orientation={"VERTICAL"}>
|
||||||
<ActionHolder>
|
<ActionHolder>
|
||||||
|
|
@ -160,7 +162,9 @@ function DataControlComponent(props: RenderComponentProps) {
|
||||||
}}
|
}}
|
||||||
evaluatedValue={evaluated?.data}
|
evaluatedValue={evaluated?.data}
|
||||||
meta={{
|
meta={{
|
||||||
error: isValid ? "" : "There is an error",
|
error: has(validationMessage, "data")
|
||||||
|
? get(validationMessage, "data")
|
||||||
|
: "",
|
||||||
touched: true,
|
touched: true,
|
||||||
}}
|
}}
|
||||||
theme={props.theme}
|
theme={props.theme}
|
||||||
|
|
@ -176,95 +180,55 @@ function DataControlComponent(props: RenderComponentProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChartDataControl extends BaseControl<ControlProps> {
|
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() {
|
render() {
|
||||||
const chartData: Array<{ seriesName: string; data: string }> = _.isString(
|
const chartData: AllChartData = isString(this.props.propertyValue)
|
||||||
this.props.propertyValue,
|
? {}
|
||||||
)
|
|
||||||
? []
|
|
||||||
: 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") {
|
if (this.props.widgetProperties.chartType === "PIE_CHART") {
|
||||||
const data = chartData.length
|
const data = dataLength
|
||||||
? chartData[0]
|
? get(chartData, `${firstKey}`)
|
||||||
: {
|
: {
|
||||||
seriesName: "",
|
seriesName: "",
|
||||||
data: "",
|
data: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataControlComponent
|
<DataControlComponent
|
||||||
index={0}
|
index={firstKey}
|
||||||
item={data}
|
item={data}
|
||||||
length={1}
|
length={1}
|
||||||
deleteOption={this.deleteOption}
|
deleteOption={this.deleteOption}
|
||||||
updateOption={this.updateOption}
|
updateOption={this.updateOption}
|
||||||
isValid={validations[0].isValid}
|
validationMessage={get(validationMessage, `${firstKey}`)}
|
||||||
validationMessage={validations[0].validationMessage}
|
evaluated={get(evaluatedValue, `${firstKey}`)}
|
||||||
evaluated={evaluatedValue[0]}
|
|
||||||
theme={this.props.theme}
|
theme={this.props.theme}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
{chartData.map((data, index) => {
|
{Object.keys(chartData).map((key: string) => {
|
||||||
|
const data = get(chartData, `${key}`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataControlComponent
|
<DataControlComponent
|
||||||
key={index}
|
key={key}
|
||||||
index={index}
|
index={key}
|
||||||
item={data}
|
item={data}
|
||||||
length={dataLength}
|
length={dataLength}
|
||||||
deleteOption={this.deleteOption}
|
deleteOption={this.deleteOption}
|
||||||
updateOption={this.updateOption}
|
updateOption={this.updateOption}
|
||||||
isValid={validations[index].isValid}
|
validationMessage={get(validationMessage, `${key}`)}
|
||||||
validationMessage={validations[index].validationMessage}
|
evaluated={get(evaluatedValue, `${key}`)}
|
||||||
evaluated={evaluatedValue[index]}
|
|
||||||
theme={this.props.theme}
|
theme={this.props.theme}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
@ -284,27 +248,28 @@ class ChartDataControl extends BaseControl<ControlProps> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteOption = (index: number) => {
|
deleteOption = (index: string) => {
|
||||||
this.deleteProperties([`${this.props.propertyName}[${index}]`]);
|
this.deleteProperties([`${this.props.propertyName}.${index}`]);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateOption = (
|
updateOption = (
|
||||||
index: number,
|
index: string,
|
||||||
propertyName: string,
|
propertyName: string,
|
||||||
updatedValue: string,
|
updatedValue: string,
|
||||||
) => {
|
) => {
|
||||||
this.updateProperty(
|
this.updateProperty(
|
||||||
`${this.props.propertyName}[${index}].${propertyName}`,
|
`${this.props.propertyName}.${index}.${propertyName}`,
|
||||||
updatedValue,
|
updatedValue,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* it adds new series data object in the chartData
|
||||||
|
*/
|
||||||
addOption = () => {
|
addOption = () => {
|
||||||
const chartData: Array<{
|
const randomString = generateReactKey();
|
||||||
seriesName: string;
|
|
||||||
data: string;
|
this.updateProperty(`${this.props.propertyName}.${randomString}`, {
|
||||||
}> = this.props.propertyValue;
|
|
||||||
this.updateProperty(`${this.props.propertyName}[${chartData.length}]`, {
|
|
||||||
seriesName: "",
|
seriesName: "",
|
||||||
data: JSON.stringify([{ x: "label", y: 50 }]),
|
data: JSON.stringify([{ x: "label", y: 50 }]),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ export enum VALIDATION_TYPES {
|
||||||
MIN_DATE = "MIN_DATE",
|
MIN_DATE = "MIN_DATE",
|
||||||
MAX_DATE = "MAX_DATE",
|
MAX_DATE = "MAX_DATE",
|
||||||
TABS_DATA = "TABS_DATA",
|
TABS_DATA = "TABS_DATA",
|
||||||
CHART_DATA = "CHART_DATA",
|
|
||||||
LIST_DATA = "LIST_DATA",
|
LIST_DATA = "LIST_DATA",
|
||||||
|
CHART_SERIES_DATA = "CHART_SERIES_DATA",
|
||||||
CUSTOM_FUSION_CHARTS_DATA = "CUSTOM_FUSION_CHARTS_DATA",
|
CUSTOM_FUSION_CHARTS_DATA = "CUSTOM_FUSION_CHARTS_DATA",
|
||||||
MARKERS = "MARKERS",
|
MARKERS = "MARKERS",
|
||||||
ACTION_SELECTOR = "ACTION_SELECTOR",
|
ACTION_SELECTOR = "ACTION_SELECTOR",
|
||||||
|
|
|
||||||
|
|
@ -186,12 +186,12 @@ describe("getAllPathsFromPropertyConfig", () => {
|
||||||
chartName: "Sales on working days",
|
chartName: "Sales on working days",
|
||||||
allowHorizontalScroll: false,
|
allowHorizontalScroll: false,
|
||||||
version: 1,
|
version: 1,
|
||||||
chartData: [
|
chartData: {
|
||||||
{
|
"random-id": {
|
||||||
seriesName: "",
|
seriesName: "",
|
||||||
data: "{{Api1.data}}",
|
data: "{{Api1.data}}",
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
xAxisName: "Last Week",
|
xAxisName: "Last Week",
|
||||||
yAxisName: "Total Order Revenue $",
|
yAxisName: "Total Order Revenue $",
|
||||||
type: WidgetTypes.CHART_WIDGET,
|
type: WidgetTypes.CHART_WIDGET,
|
||||||
|
|
@ -206,7 +206,7 @@ describe("getAllPathsFromPropertyConfig", () => {
|
||||||
widgetId: "x1naz9is2b",
|
widgetId: "x1naz9is2b",
|
||||||
dynamicBindingPathList: [
|
dynamicBindingPathList: [
|
||||||
{
|
{
|
||||||
key: "chartData[0].data",
|
key: "chartData.random-id.data",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
@ -216,8 +216,8 @@ describe("getAllPathsFromPropertyConfig", () => {
|
||||||
bindingPaths: {
|
bindingPaths: {
|
||||||
chartType: EvaluationSubstitutionType.TEMPLATE,
|
chartType: EvaluationSubstitutionType.TEMPLATE,
|
||||||
chartName: EvaluationSubstitutionType.TEMPLATE,
|
chartName: EvaluationSubstitutionType.TEMPLATE,
|
||||||
"chartData[0].seriesName": EvaluationSubstitutionType.TEMPLATE,
|
"chartData.random-id.seriesName": EvaluationSubstitutionType.TEMPLATE,
|
||||||
"chartData[0].data": EvaluationSubstitutionType.TEMPLATE,
|
"chartData.random-id.data": EvaluationSubstitutionType.TEMPLATE,
|
||||||
xAxisName: EvaluationSubstitutionType.TEMPLATE,
|
xAxisName: EvaluationSubstitutionType.TEMPLATE,
|
||||||
yAxisName: EvaluationSubstitutionType.TEMPLATE,
|
yAxisName: EvaluationSubstitutionType.TEMPLATE,
|
||||||
isVisible: EvaluationSubstitutionType.TEMPLATE,
|
isVisible: EvaluationSubstitutionType.TEMPLATE,
|
||||||
|
|
@ -226,8 +226,8 @@ describe("getAllPathsFromPropertyConfig", () => {
|
||||||
onDataPointClick: true,
|
onDataPointClick: true,
|
||||||
},
|
},
|
||||||
validationPaths: {
|
validationPaths: {
|
||||||
"chartData[0].data": "CHART_DATA",
|
"chartData.random-id.data": "CHART_SERIES_DATA",
|
||||||
"chartData[0].seriesName": "TEXT",
|
"chartData.random-id.seriesName": "TEXT",
|
||||||
chartName: "TEXT",
|
chartName: "TEXT",
|
||||||
isVisible: "BOOLEAN",
|
isVisible: "BOOLEAN",
|
||||||
xAxisName: "TEXT",
|
xAxisName: "TEXT",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { WidgetProps } from "widgets/BaseWidget";
|
import { WidgetProps } from "widgets/BaseWidget";
|
||||||
import { PropertyPaneConfig } from "constants/PropertyControlConstants";
|
import { PropertyPaneConfig } from "constants/PropertyControlConstants";
|
||||||
import { get } from "lodash";
|
import { get, isObject, isUndefined } from "lodash";
|
||||||
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
|
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||||
import { VALIDATION_TYPES } from "constants/WidgetValidation";
|
import { VALIDATION_TYPES } from "constants/WidgetValidation";
|
||||||
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||||
|
|
@ -29,6 +29,7 @@ export const getAllPathsFromPropertyConfig = (
|
||||||
if ("hidden" in controlConfig) {
|
if ("hidden" in controlConfig) {
|
||||||
isHidden = controlConfig.hidden(widget, basePath);
|
isHidden = controlConfig.hidden(widget, basePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isHidden) {
|
if (!isHidden) {
|
||||||
if (
|
if (
|
||||||
controlConfig.isBindProperty &&
|
controlConfig.isBindProperty &&
|
||||||
|
|
@ -101,34 +102,36 @@ export const getAllPathsFromPropertyConfig = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (controlConfig.children) {
|
if (controlConfig.children) {
|
||||||
// Property in array structure
|
|
||||||
const basePropertyPath = controlConfig.propertyName;
|
const basePropertyPath = controlConfig.propertyName;
|
||||||
const widgetPropertyValue = get(widget, basePropertyPath, []);
|
const widgetPropertyValue = get(widget, basePropertyPath, []);
|
||||||
if (Array.isArray(widgetPropertyValue)) {
|
// Property in object structure
|
||||||
widgetPropertyValue.forEach(
|
if (
|
||||||
(arrayPropertyValue: any, index: number) => {
|
!isUndefined(widgetPropertyValue) &&
|
||||||
const arrayIndexPropertyPath = `${basePropertyPath}[${index}]`;
|
isObject(widgetPropertyValue)
|
||||||
controlConfig.children.forEach((childPropertyConfig: any) => {
|
) {
|
||||||
const childArrayPropertyPath = `${arrayIndexPropertyPath}.${childPropertyConfig.propertyName}`;
|
Object.keys(widgetPropertyValue).map((key: string) => {
|
||||||
if (
|
const objectIndexPropertyPath = `${basePropertyPath}.${key}`;
|
||||||
childPropertyConfig.isBindProperty &&
|
controlConfig.children.forEach((childPropertyConfig: any) => {
|
||||||
!childPropertyConfig.isTriggerProperty
|
const childArrayPropertyPath = `${objectIndexPropertyPath}.${childPropertyConfig.propertyName}`;
|
||||||
) {
|
|
||||||
bindingPaths[childArrayPropertyPath] =
|
if (
|
||||||
EvaluationSubstitutionType.TEMPLATE;
|
childPropertyConfig.isBindProperty &&
|
||||||
if (childPropertyConfig.validation) {
|
!childPropertyConfig.isTriggerProperty
|
||||||
validationPaths[childArrayPropertyPath] =
|
) {
|
||||||
childPropertyConfig.validation;
|
bindingPaths[childArrayPropertyPath] =
|
||||||
}
|
EvaluationSubstitutionType.TEMPLATE;
|
||||||
} else if (
|
if (childPropertyConfig.validation) {
|
||||||
childPropertyConfig.isBindProperty &&
|
validationPaths[childArrayPropertyPath] =
|
||||||
childPropertyConfig.isTriggerProperty
|
childPropertyConfig.validation;
|
||||||
) {
|
|
||||||
triggerPaths[childArrayPropertyPath] = true;
|
|
||||||
}
|
}
|
||||||
});
|
} else if (
|
||||||
},
|
childPropertyConfig.isBindProperty &&
|
||||||
);
|
childPropertyConfig.isTriggerProperty
|
||||||
|
) {
|
||||||
|
triggerPaths[childArrayPropertyPath] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -384,8 +384,8 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
||||||
chartName: "Sales on working days",
|
chartName: "Sales on working days",
|
||||||
allowHorizontalScroll: false,
|
allowHorizontalScroll: false,
|
||||||
version: 1,
|
version: 1,
|
||||||
chartData: [
|
chartData: {
|
||||||
{
|
[generateReactKey()]: {
|
||||||
seriesName: "Sales",
|
seriesName: "Sales",
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
|
|
@ -418,7 +418,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
xAxisName: "Last Week",
|
xAxisName: "Last Week",
|
||||||
yAxisName: "Total Order Revenue $",
|
yAxisName: "Total Order Revenue $",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
90
app/client/src/utils/WidgetPropsUtils.test.tsx
Normal file
90
app/client/src/utils/WidgetPropsUtils.test.tsx
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -21,7 +21,7 @@ import defaultTemplate from "templates/default";
|
||||||
import { generateReactKey } from "./generators";
|
import { generateReactKey } from "./generators";
|
||||||
import { ChartDataPoint } from "widgets/ChartWidget";
|
import { ChartDataPoint } from "widgets/ChartWidget";
|
||||||
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
|
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||||
import { isString } from "lodash";
|
import { isString, set } from "lodash";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import {
|
import {
|
||||||
migrateTablePrimaryColumnsBindings,
|
migrateTablePrimaryColumnsBindings,
|
||||||
|
|
@ -354,6 +354,62 @@ function migrateOldChartData(currentDSL: ContainerWidgetProps<WidgetProps>) {
|
||||||
return currentDSL;
|
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 = (
|
export const calculateDynamicHeight = (
|
||||||
canvasWidgets: {
|
canvasWidgets: {
|
||||||
[widgetId: string]: FlattenedWidgetProps;
|
[widgetId: string]: FlattenedWidgetProps;
|
||||||
|
|
@ -482,6 +538,11 @@ const transformDSL = (currentDSL: ContainerWidgetProps<WidgetProps>) => {
|
||||||
currentDSL.version = 16;
|
currentDSL.version = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentDSL.version === 16) {
|
||||||
|
currentDSL = migrateChartDataFromArrayToObject(currentDSL);
|
||||||
|
currentDSL.version = 17;
|
||||||
|
}
|
||||||
|
|
||||||
return currentDSL;
|
return currentDSL;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,9 @@ export interface ChartDataPoint {
|
||||||
y: any;
|
y: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AllChartData {
|
||||||
|
[key: string]: ChartData;
|
||||||
|
}
|
||||||
export interface ChartData {
|
export interface ChartData {
|
||||||
seriesName?: string;
|
seriesName?: string;
|
||||||
data: ChartDataPoint[];
|
data: ChartDataPoint[];
|
||||||
|
|
@ -88,7 +91,7 @@ export interface ChartData {
|
||||||
|
|
||||||
export interface ChartWidgetProps extends WidgetProps, WithMeta {
|
export interface ChartWidgetProps extends WidgetProps, WithMeta {
|
||||||
chartType: ChartType;
|
chartType: ChartType;
|
||||||
chartData: ChartData[];
|
chartData: AllChartData;
|
||||||
customFusionChartConfig: { config: CustomFusionChartConfig };
|
customFusionChartConfig: { config: CustomFusionChartConfig };
|
||||||
xAxisName: string;
|
xAxisName: string;
|
||||||
yAxisName: string;
|
yAxisName: string;
|
||||||
|
|
|
||||||
|
|
@ -68,14 +68,20 @@ describe("Validate Chart Widget's property config", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Validates config when chartType is CUSTOM_FUSION_CHART", () => {
|
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;
|
let result = true;
|
||||||
if (hiddenFn) result = hiddenFn({ chartType: "CUSTOM_FUSION_CHART" });
|
if (hiddenFn) result = hiddenFn({ chartType: "CUSTOM_FUSION_CHART" });
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Validates that sections are hidden when chartType is CUSTOM_FUSION_CHART", () => {
|
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) => {
|
hiddenFns.forEach((fn: (props: any) => boolean) => {
|
||||||
const result = fn({ chartType: "CUSTOM_FUSION_CHART" });
|
const result = fn({ chartType: "CUSTOM_FUSION_CHART" });
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTruthy();
|
||||||
|
|
|
||||||
|
|
@ -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",
|
sectionName: "Chart Data",
|
||||||
hidden: (props: ChartWidgetProps) =>
|
|
||||||
props.chartType === "CUSTOM_FUSION_CHART",
|
|
||||||
children: [
|
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",
|
helpText: "Populates the chart with the data",
|
||||||
propertyName: "chartData",
|
propertyName: "chartData",
|
||||||
placeholderText: 'Enter [{ "x": "val", "y": "val" }]',
|
placeholderText: 'Enter [{ "x": "val", "y": "val" }]',
|
||||||
label: "Chart Series",
|
label: "Chart Series",
|
||||||
controlType: "CHART_DATA",
|
controlType: "CHART_DATA",
|
||||||
|
|
||||||
isBindProperty: false,
|
isBindProperty: false,
|
||||||
isTriggerProperty: false,
|
isTriggerProperty: false,
|
||||||
|
hidden: (props: ChartWidgetProps) =>
|
||||||
|
props.chartType === "CUSTOM_FUSION_CHART",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
helpText: "Series Name",
|
helpText: "Series Name",
|
||||||
|
|
@ -105,7 +105,7 @@ export default [
|
||||||
controlType: "INPUT_TEXT_AREA",
|
controlType: "INPUT_TEXT_AREA",
|
||||||
isBindProperty: true,
|
isBindProperty: true,
|
||||||
isTriggerProperty: false,
|
isTriggerProperty: false,
|
||||||
validation: VALIDATION_TYPES.CHART_DATA,
|
validation: VALIDATION_TYPES.CHART_SERIES_DATA,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -404,6 +404,8 @@ export default class DataTreeEvaluator {
|
||||||
} else {
|
} else {
|
||||||
evalPropertyValue = unEvalPropertyValue;
|
evalPropertyValue = unEvalPropertyValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// debugger;
|
||||||
if (isWidget(entity)) {
|
if (isWidget(entity)) {
|
||||||
const widgetEntity = entity;
|
const widgetEntity = entity;
|
||||||
const defaultPropertyMap = this.widgetConfigMap[widgetEntity.type]
|
const defaultPropertyMap = this.widgetConfigMap[widgetEntity.type]
|
||||||
|
|
|
||||||
|
|
@ -19,109 +19,47 @@ const DUMMY_WIDGET: WidgetProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("Validate Validators", () => {
|
describe("Validate Validators", () => {
|
||||||
const validator = VALIDATORS.CHART_DATA;
|
it("correctly validates chart series data ", () => {
|
||||||
it("correctly validates chart data ", () => {
|
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
input: [
|
input: [{ x: "Jan", y: 1000 }],
|
||||||
{
|
|
||||||
seriesName: "Sales",
|
|
||||||
data: [{ x: "Jan", y: 1000 }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output: {
|
output: {
|
||||||
isValid: true,
|
isValid: true,
|
||||||
parsed: [
|
parsed: [{ x: "Jan", y: 1000 }],
|
||||||
{
|
transformed: [{ x: "Jan", y: 1000 }],
|
||||||
seriesName: "Sales",
|
|
||||||
data: [{ x: "Jan", y: 1000 }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
transformed: [
|
|
||||||
{
|
|
||||||
seriesName: "Sales",
|
|
||||||
data: [{ x: "Jan", y: 1000 }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: [
|
input: [{ x: "Jan", y: 1000 }, { x: "Feb" }],
|
||||||
{
|
|
||||||
seriesName: "Sales",
|
|
||||||
data: [{ x: "Jan", y: 1000 }, { x: "Feb" }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output: {
|
output: {
|
||||||
isValid: false,
|
isValid: false,
|
||||||
message:
|
message:
|
||||||
'0##This value does not evaluate to type "Array<x:string, y:number>"',
|
'This value does not evaluate to type: [{ "x": "val", "y": "val" }]',
|
||||||
parsed: [
|
parsed: [],
|
||||||
{
|
transformed: [{ x: "Jan", y: 1000 }, { x: "Feb" }],
|
||||||
seriesName: "Sales",
|
|
||||||
data: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
transformed: [
|
|
||||||
{
|
|
||||||
seriesName: "Sales",
|
|
||||||
data: [{ x: "Jan", y: 1000 }, { x: "Feb" }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: [
|
input: undefined,
|
||||||
{
|
|
||||||
seriesName: "Sales",
|
|
||||||
data: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
seriesName: "Expenses",
|
|
||||||
data: [
|
|
||||||
{ x: "Jan", y: 1000 },
|
|
||||||
{ x: "Feb", y: 2000 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output: {
|
output: {
|
||||||
isValid: false,
|
isValid: false,
|
||||||
message:
|
message:
|
||||||
'0##This value does not evaluate to type "Array<x:string, y:number>"',
|
'This value does not evaluate to type: [{ "x": "val", "y": "val" }]',
|
||||||
parsed: [
|
parsed: [],
|
||||||
{
|
transformed: undefined,
|
||||||
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 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
for (const testCase of cases) {
|
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);
|
expect(response).toStrictEqual(testCase.output);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Correctly validates page number", () => {
|
it("Correctly validates page number", () => {
|
||||||
const input = [0, -1, undefined, null, 2, "abcd", [], ""];
|
const input = [0, -1, undefined, null, 2, "abcd", [], ""];
|
||||||
const expected = [1, 1, 1, 1, 2, 1, 1, 1];
|
const expected = [1, 1, 1, 1, 2, 1, 1, 1];
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,7 @@ export const VALIDATORS: Record<VALIDATION_TYPES, Validator> = {
|
||||||
if (isString(value)) {
|
if (isString(value)) {
|
||||||
parsed = JSON.parse(parsed as string);
|
parsed = JSON.parse(parsed as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.isArray(parsed)) {
|
if (!Array.isArray(parsed)) {
|
||||||
return {
|
return {
|
||||||
isValid: false,
|
isValid: false,
|
||||||
|
|
@ -209,6 +210,7 @@ export const VALIDATORS: Record<VALIDATION_TYPES, Validator> = {
|
||||||
message: `${WIDGET_TYPE_VALIDATION_ERROR} "Array"`,
|
message: `${WIDGET_TYPE_VALIDATION_ERROR} "Array"`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return { isValid: true, parsed, transformed: parsed };
|
return { isValid: true, parsed, transformed: parsed };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -331,80 +333,56 @@ export const VALIDATORS: Record<VALIDATION_TYPES, Validator> = {
|
||||||
}
|
}
|
||||||
return { isValid, parsed };
|
return { isValid, parsed };
|
||||||
},
|
},
|
||||||
[VALIDATION_TYPES.CHART_DATA]: (
|
[VALIDATION_TYPES.CHART_SERIES_DATA]: (
|
||||||
value: any,
|
value: any,
|
||||||
props: WidgetProps,
|
props: WidgetProps,
|
||||||
dataTree?: DataTree,
|
dataTree?: DataTree,
|
||||||
): ValidationResponse => {
|
): ValidationResponse => {
|
||||||
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
|
let parsed = [];
|
||||||
value,
|
let transformed = [];
|
||||||
props,
|
let isValid = false;
|
||||||
dataTree,
|
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) {
|
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 {
|
return {
|
||||||
isValid: false,
|
isValid: false,
|
||||||
parsed: parsedChartData,
|
parsed: [],
|
||||||
transformed: parsed,
|
transformed: transformed,
|
||||||
message: validationMessage,
|
message: validationMessage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { isValid, parsed: parsedChartData, transformed: parsedChartData };
|
|
||||||
|
return { isValid, parsed, transformed };
|
||||||
},
|
},
|
||||||
[VALIDATION_TYPES.CUSTOM_FUSION_CHARTS_DATA]: (
|
[VALIDATION_TYPES.CUSTOM_FUSION_CHARTS_DATA]: (
|
||||||
value: any,
|
value: any,
|
||||||
|
|
@ -419,6 +397,7 @@ export const VALIDATORS: Record<VALIDATION_TYPES, Validator> = {
|
||||||
if (props.chartName && parsed.dataSource && parsed.dataSource.chart) {
|
if (props.chartName && parsed.dataSource && parsed.dataSource.chart) {
|
||||||
parsed.dataSource.chart.caption = props.chartName;
|
parsed.dataSource.chart.caption = props.chartName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
return {
|
return {
|
||||||
isValid,
|
isValid,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user