diff --git a/app/client/cypress/fixtures/example.json b/app/client/cypress/fixtures/example.json
index 176cec376a..3dee58e627 100644
--- a/app/client/cypress/fixtures/example.json
+++ b/app/client/cypress/fixtures/example.json
@@ -196,6 +196,50 @@
"orderAmount": 9.99
}
],
+ "ChartCustomConfig": {"type": "area2d",
+ "dataSource": {
+ "chart": {
+ "caption": "Countries With Most Oil Reserves [2017-18]",
+ "subCaption": "In MMbbl = One Million barrels",
+ "xAxisName": "Country",
+ "yAxisName": "Reserves (MMbbl)",
+ "numberSuffix": "K"
+ },
+ "data": [
+ {
+ "label": "Venezuela",
+ "value": "290"
+ },
+ {
+ "label": "Saudi",
+ "value": "260"
+ },
+ {
+ "label": "Canada",
+ "value": "180"
+ },
+ {
+ "label": "Iran",
+ "value": "140"
+ },
+ {
+ "label": "Russia",
+ "value": "115"
+ },
+ {
+ "label": "UAE",
+ "value": "100"
+ },
+ {
+ "label": "US",
+ "value": "30"
+ },
+ {
+ "label": "China",
+ "value": "30"
+ }
+ ]
+ }},
"TableInputWithNull": [
{
"id": 2381224,
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Chart_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Chart_spec.js
index 5fa2fdb40b..7331a78fa1 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Chart_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Chart_spec.js
@@ -67,9 +67,11 @@ describe("Chart Widget Functionality", function() {
.click({ force: true })
.type(this.data.command)
.type(this.data.ylabel);
+
//Close edit prop
cy.get(commonlocators.editPropCrossButton).click();
});
+
it("Chart Widget Functionality To Unchecked Visible Widget", function() {
cy.togglebarDisable(commonlocators.visibleCheckbox);
cy.PublishtheApp();
@@ -96,6 +98,34 @@ describe("Chart Widget Functionality", function() {
.should("exist");
cy.get(publish.backToEditor).click();
});
+
+ it("Chart Widget Custom Config Feature", function() {
+ // Note: This only checks for crashes in custom config
+ cy.get(viewWidgetsPage.chartType)
+ .last()
+ .click({ force: true });
+
+ cy.get(commonlocators.dropdownmenu)
+ .children()
+ .contains("Custom Chart")
+ .click();
+ cy.get(viewWidgetsPage.chartType)
+ .last()
+ .should("have.text", "Custom Chart");
+
+ cy.testJsontext(
+ "customfusionchartconfiguration",
+ `{{${JSON.stringify(this.data.ChartCustomConfig)}}}`,
+ );
+ cy.get(viewWidgetsPage.chartWidget)
+ .should("be.visible")
+ .and((chart) => {
+ expect(chart.height()).to.be.greaterThan(200);
+ });
+ cy.get(viewWidgetsPage.chartWidget).should("have.css", "opacity", "1");
+ //Close edit prop
+ cy.get(commonlocators.editPropCrossButton).click();
+ });
});
afterEach(() => {
// put your clean up code if any
diff --git a/app/client/cypress/manual_TestSuite/Org_Logo_Del.js b/app/client/cypress/manual_TestSuite/Org_Logo_Del.js
new file mode 100644
index 0000000000..3ee6278b2b
--- /dev/null
+++ b/app/client/cypress/manual_TestSuite/Org_Logo_Del.js
@@ -0,0 +1,14 @@
+const homePage = require("../../../locators/HomePage.json");
+
+describe("Deletion of organisational Logo ", function() {
+ it(" org logo upload ", function() {
+ //Click on the dropdown next to organisational Name
+ // Navigate between tabs
+ // Naviagte to General Tab
+ // Add an Organisational Logo
+ // Wait until it loads
+ // Switch between Tabs
+ // Click on the remove Icon
+ //Ensure the organisational Logo is deleted
+ });
+});
diff --git a/app/client/cypress/manual_TestSuite/Org_Logo_Set.js b/app/client/cypress/manual_TestSuite/Org_Logo_Set.js
new file mode 100644
index 0000000000..fa99552481
--- /dev/null
+++ b/app/client/cypress/manual_TestSuite/Org_Logo_Set.js
@@ -0,0 +1,14 @@
+const homePage = require("../../../locators/HomePage.json");
+
+describe("insert organisational Logo ", function() {
+ it(" org logo upload ", function() {
+ //Click on the dropdown next to organisational Name
+ // Navigate between tabs
+ // Naviagte to General Tab
+ // Add an Organisational Logo
+ //Wait until it loads
+ // Switch between Tabs
+ // Navigate to General Tab and ensure the logo exsits
+ //navigate back to Homepage
+ });
+});
diff --git a/app/client/cypress/manual_TestSuite/Share_User_Icon.js b/app/client/cypress/manual_TestSuite/Share_User_Icon.js
new file mode 100644
index 0000000000..c87386fc4e
--- /dev/null
+++ b/app/client/cypress/manual_TestSuite/Share_User_Icon.js
@@ -0,0 +1,13 @@
+const homePage = require("../../../locators/HomePage.json");
+
+describe("Shared user icon ", function() {
+ it(" User Icon is disaplyed to user ", function() {
+ // Navigate to home Page
+ //Click on Share Icon
+ // Click on Field to add an Email Id
+ // Click on the Roles field
+ // Add an role from the Dropdown
+ // CLick on Invite
+ //Now observe the icon next to the Share Icon
+ });
+});
diff --git a/app/client/cypress/manual_TestSuite/Table_Filter_Test_spec.js b/app/client/cypress/manual_TestSuite/Table_Filter_Test_spec.js
new file mode 100644
index 0000000000..bd5fd602e7
--- /dev/null
+++ b/app/client/cypress/manual_TestSuite/Table_Filter_Test_spec.js
@@ -0,0 +1,11 @@
+const dsl = require("../../../fixtures/tableWidgetDsl.json");
+
+describe("Test for Table Filter ", function() {
+ it("Table Filter", function() {
+ //Add a table
+ // click on the column action item
+ // Click on Select a datatype
+ // Click on Filter option
+ // ensure to add filter
+ });
+});
diff --git a/app/client/package.json b/app/client/package.json
index c261842a74..c16508816f 100644
--- a/app/client/package.json
+++ b/app/client/package.json
@@ -14,6 +14,7 @@
"@blueprintjs/select": "^3.10.0",
"@blueprintjs/timezone": "^3.6.0",
"@craco/craco": "^5.7.0",
+ "@fusioncharts/powercharts": "^3.16.0",
"@github/g-emoji-element": "^1.1.5",
"@manaflair/redux-batch": "^1.0.0",
"@optimizely/optimizely-sdk": "^4.0.0",
diff --git a/app/client/src/actions/configsActions.tsx b/app/client/src/actions/configsActions.tsx
deleted file mode 100644
index 0a97bf03a4..0000000000
--- a/app/client/src/actions/configsActions.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { ReduxActionTypes } from "constants/ReduxActionConstants";
-
-export type EditorConfigIdsType = {
- widgetCardsPaneId?: string;
- widgetConfigsId?: string;
-};
-
-export const fetchEditorConfigs = () => {
- return {
- type: ReduxActionTypes.FETCH_CONFIGS_INIT,
- };
-};
diff --git a/app/client/src/assets/icons/control/back.svg b/app/client/src/assets/icons/control/back.svg
index b34278d7d9..3c600a22a9 100644
--- a/app/client/src/assets/icons/control/back.svg
+++ b/app/client/src/assets/icons/control/back.svg
@@ -1 +1,4 @@
-
\ No newline at end of file
+
+
diff --git a/app/client/src/components/designSystems/appsmith/ChartComponent.tsx b/app/client/src/components/designSystems/appsmith/ChartComponent.tsx
index 5b102f03e3..faccb28c53 100644
--- a/app/client/src/components/designSystems/appsmith/ChartComponent.tsx
+++ b/app/client/src/components/designSystems/appsmith/ChartComponent.tsx
@@ -4,16 +4,38 @@ import styled from "styled-components";
import { getBorderCSSShorthand, invisible } from "constants/DefaultTheme";
import { getAppsmithConfigs } from "configs";
-import { ChartType, ChartData, ChartDataPoint } from "widgets/ChartWidget";
+import { ChartData, ChartDataPoint, ChartType } from "widgets/ChartWidget";
+import log from "loglevel";
+
+export interface CustomFusionChartConfig {
+ type: string;
+ dataSource?: any;
+}
const FusionCharts = require("fusioncharts");
-const Charts = require("fusioncharts/fusioncharts.charts");
-const FusionTheme = require("fusioncharts/themes/fusioncharts.theme.fusion");
+const plugins: Record = {
+ Charts: require("fusioncharts/fusioncharts.charts"),
+ FusionTheme: require("fusioncharts/themes/fusioncharts.theme.fusion"),
+ Widgets: require("fusioncharts/fusioncharts.widgets"),
+ ZoomScatter: require("fusioncharts/fusioncharts.zoomscatter"),
+ ZoomLine: require("fusioncharts/fusioncharts.zoomline"),
+ PowerCharts: require("fusioncharts/fusioncharts.powercharts"),
+ TimeSeries: require("fusioncharts/fusioncharts.timeseries"),
+ OverlappedColumn: require("fusioncharts/fusioncharts.overlappedcolumn2d"),
+ OverlappedBar: require("fusioncharts/fusioncharts.overlappedbar2d"),
+ TreeMap: require("fusioncharts/fusioncharts.treemap"),
+ Maps: require("fusioncharts/fusioncharts.maps"),
+ Gantt: require("fusioncharts/fusioncharts.gantt"),
+ VML: require("fusioncharts/fusioncharts.vml"),
+};
+
+// Enable all plugins.
+// This is needed to support custom chart configs
+Object.keys(plugins).forEach((key: string) =>
+ (plugins[key] as any)(FusionCharts),
+);
const { fusioncharts } = getAppsmithConfigs();
-Charts(FusionCharts);
-FusionTheme(FusionCharts);
-
FusionCharts.options.license({
key: fusioncharts.licenseKey,
creditLabel: false,
@@ -22,6 +44,7 @@ FusionCharts.options.license({
export interface ChartComponentProps {
chartType: ChartType;
chartData: ChartData[];
+ customFusionChartConfig: CustomFusionChartConfig;
xAxisName: string;
yAxisName: string;
chartName: string;
@@ -47,6 +70,7 @@ const CanvasContainer = styled.div<
class ChartComponent extends React.Component {
chartInstance = new FusionCharts();
+
getChartType = () => {
const { chartType, allowHorizontalScroll, chartData } = this.props;
const isMSChart = chartData.length > 1;
@@ -216,6 +240,23 @@ class ChartComponent extends React.Component {
}
};
+ getCustomFusionChartDataSource = () => {
+ let config = this.props.customFusionChartConfig as CustomFusionChartConfig;
+ if (config && config.dataSource) {
+ config = {
+ ...config,
+ dataSource: {
+ ...config.dataSource,
+ chart: {
+ ...config.dataSource.chart,
+ caption: this.props.chartName || config.dataSource.chart.caption,
+ },
+ },
+ };
+ }
+ return config;
+ };
+
getScrollChartDataSource = () => {
const chartConfig = this.getChartConfig();
@@ -238,6 +279,16 @@ class ChartComponent extends React.Component {
};
createGraph = () => {
+ if (this.props.chartType === "CUSTOM_FUSION_CHART") {
+ const chartConfig = {
+ renderAt: this.props.widgetId + "chart-container",
+ width: "100%",
+ height: "100%",
+ ...this.getCustomFusionChartDataSource(),
+ };
+ this.chartInstance = new FusionCharts(chartConfig);
+ return;
+ }
const dataSource =
this.props.allowHorizontalScroll && this.props.chartType !== "PIE_CHART"
? this.getScrollChartDataSource()
@@ -270,7 +321,11 @@ class ChartComponent extends React.Component {
/* Component could be unmounted before FusionCharts is ready,
this check ensure we don't render on unmounted component */
if (this.chartInstance) {
- this.chartInstance.render();
+ try {
+ this.chartInstance.render();
+ } catch (e) {
+ log.error(e);
+ }
}
});
}
@@ -283,6 +338,17 @@ class ChartComponent extends React.Component {
componentDidUpdate(prevProps: ChartComponentProps) {
if (!_.isEqual(prevProps, this.props)) {
+ if (this.props.chartType === "CUSTOM_FUSION_CHART") {
+ const chartConfig = {
+ renderAt: this.props.widgetId + "chart-container",
+ width: "100%",
+ height: "100%",
+ ...this.getCustomFusionChartDataSource(),
+ };
+ this.chartInstance = new FusionCharts(chartConfig);
+ this.chartInstance.render();
+ return;
+ }
const chartType = this.getChartType();
this.chartInstance.chartType(chartType);
if (
diff --git a/app/client/src/components/propertyControls/CustomFusionChartControl.tsx b/app/client/src/components/propertyControls/CustomFusionChartControl.tsx
new file mode 100644
index 0000000000..20d22cf008
--- /dev/null
+++ b/app/client/src/components/propertyControls/CustomFusionChartControl.tsx
@@ -0,0 +1,33 @@
+import React from "react";
+import InputTextControl, { InputText } from "./InputTextControl";
+
+class CustomFusionChartControl extends InputTextControl {
+ render() {
+ const expected = "{\n type: string,\n dataSource: Object\n}";
+ const {
+ propertyValue,
+ isValid,
+ label,
+ placeholderText,
+ dataTreePath,
+ validationMessage,
+ } = this.props;
+ return (
+
+ );
+ }
+ static getControlType() {
+ return "CUSTOM_FUSION_CHARTS_DATA";
+ }
+}
+
+export default CustomFusionChartControl;
diff --git a/app/client/src/components/propertyControls/index.ts b/app/client/src/components/propertyControls/index.ts
index a92d2ee16e..8b89e875cd 100644
--- a/app/client/src/components/propertyControls/index.ts
+++ b/app/client/src/components/propertyControls/index.ts
@@ -42,6 +42,7 @@ import ButtonTabControl, {
import MultiSwitchControl, {
MultiSwitchControlProps,
} from "components/propertyControls/MultiSwitchControl";
+import CustomFusionChartControl from "./CustomFusionChartControl";
export const PropertyControls = {
InputTextControl,
@@ -55,6 +56,7 @@ export const PropertyControls = {
ColumnActionSelectorControl,
MultiSwitchControl,
ChartDataControl,
+ CustomFusionChartControl,
LocationSearchControl,
StepControl,
TabControl,
diff --git a/app/client/src/constants/FieldExpectedValue.ts b/app/client/src/constants/FieldExpectedValue.ts
index 936069c0e0..530b8dcc14 100644
--- a/app/client/src/constants/FieldExpectedValue.ts
+++ b/app/client/src/constants/FieldExpectedValue.ts
@@ -68,7 +68,8 @@ const FIELD_VALUES: Record<
},
CHART_WIDGET: {
chartName: "string",
- chartType: "LINE_CHART | BAR_CHART | PIE_CHART | COLUMN_CHART | AREA_CHART",
+ chartType:
+ "LINE_CHART | BAR_CHART | PIE_CHART | COLUMN_CHART | AREA_CHART | CUSTOM_FUSION_CHART",
xAxisName: "string",
yAxisName: "string",
isVisible: "boolean",
diff --git a/app/client/src/constants/WidgetValidation.ts b/app/client/src/constants/WidgetValidation.ts
index e49629bd48..23eb649159 100644
--- a/app/client/src/constants/WidgetValidation.ts
+++ b/app/client/src/constants/WidgetValidation.ts
@@ -18,6 +18,7 @@ export const VALIDATION_TYPES = {
MAX_DATE: "MAX_DATE",
TABS_DATA: "TABS_DATA",
CHART_DATA: "CHART_DATA",
+ CUSTOM_FUSION_CHARTS_DATA: "CUSTOM_FUSION_CHARTS_DATA",
MARKERS: "MARKERS",
ACTION_SELECTOR: "ACTION_SELECTOR",
ARRAY_ACTION_SELECTOR: "ARRAY_ACTION_SELECTOR",
diff --git a/app/client/src/pages/Editor/PropertyPane/PropertySection.tsx b/app/client/src/pages/Editor/PropertyPane/PropertySection.tsx
index 5808825c1c..f0ccd8d313 100644
--- a/app/client/src/pages/Editor/PropertyPane/PropertySection.tsx
+++ b/app/client/src/pages/Editor/PropertyPane/PropertySection.tsx
@@ -55,8 +55,8 @@ const areEqual = (prev: PropertySectionProps, next: PropertySectionProps) => {
export const PropertySection = memo((props: PropertySectionProps) => {
const [isOpen, open] = useState(!!props.isDefaultOpen);
const widgetProps: any = useSelector(getWidgetPropsForPropertyPane);
- if (props.hidden && props.propertyPath) {
- if (props.propertyPath && props.hidden(widgetProps, props.propertyPath)) {
+ if (props.hidden) {
+ if (props.hidden(widgetProps, props.propertyPath || "")) {
return null;
}
}
diff --git a/app/client/src/sagas/InitSagas.ts b/app/client/src/sagas/InitSagas.ts
index b9e42ad991..4e8c29572a 100644
--- a/app/client/src/sagas/InitSagas.ts
+++ b/app/client/src/sagas/InitSagas.ts
@@ -16,7 +16,6 @@ import {
} from "constants/ReduxActionConstants";
import { ERROR_CODES } from "constants/ApiConstants";
-import { fetchEditorConfigs } from "actions/configsActions";
import {
fetchPage,
fetchPageList,
@@ -52,7 +51,6 @@ function* initializeEditorSaga(
yield put({ type: ReduxActionTypes.START_EVALUATION });
yield all([
put(fetchPageList(applicationId, APP_MODE.EDIT)),
- put(fetchEditorConfigs()),
put(fetchActions(applicationId)),
put(fetchPage(pageId)),
put(fetchApplication(applicationId, APP_MODE.EDIT)),
diff --git a/app/client/src/selectors/propertyPaneSelectors.tsx b/app/client/src/selectors/propertyPaneSelectors.tsx
index d74d1b30a5..c9a3cf955a 100644
--- a/app/client/src/selectors/propertyPaneSelectors.tsx
+++ b/app/client/src/selectors/propertyPaneSelectors.tsx
@@ -1,7 +1,6 @@
import { createSelector } from "reselect";
import { AppState } from "reducers";
import { PropertyPaneReduxState } from "reducers/uiReducers/propertyPaneReducer";
-
import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
import { WidgetProps } from "widgets/BaseWidget";
import { DataTree, DataTreeWidget } from "entities/DataTree/dataTreeFactory";
diff --git a/app/client/src/utils/PropertyControlFactory.tsx b/app/client/src/utils/PropertyControlFactory.tsx
index f997c8e5eb..e94ef1c0cc 100644
--- a/app/client/src/utils/PropertyControlFactory.tsx
+++ b/app/client/src/utils/PropertyControlFactory.tsx
@@ -28,7 +28,6 @@ class PropertyControlFactory {
if (customEditor) controlBuilder = this.controlMap.get(customEditor);
else controlBuilder = this.controlMap.get("CODE_EDITOR");
}
-
if (controlBuilder) {
const controlProps: ControlProps = {
...controlData,
diff --git a/app/client/src/widgets/BaseWidget.tsx b/app/client/src/widgets/BaseWidget.tsx
index 3c4dba6207..f917f5a5a1 100644
--- a/app/client/src/widgets/BaseWidget.tsx
+++ b/app/client/src/widgets/BaseWidget.tsx
@@ -33,6 +33,7 @@ import {
WidgetDynamicPathListProps,
WidgetEvaluatedProps,
} from "../utils/DynamicBindingUtils";
+import { PropertyPaneConfig } from "constants/PropertyControlConstants";
import { BatchPropertyUpdatePayload } from "actions/controlActions";
/***
@@ -53,6 +54,9 @@ abstract class BaseWidget<
> extends Component {
static contextType = EditorContext;
+ static getPropertyPaneConfig(): PropertyPaneConfig[] {
+ return [];
+ }
// Needed to send a default no validation option. In case a widget needs
// validation implement this in the widget class again
static getPropertyValidationMap(): WidgetPropertyValidationType {
diff --git a/app/client/src/widgets/ChartWidget/index.tsx b/app/client/src/widgets/ChartWidget/index.tsx
index e60f8f357f..156b15f4cb 100644
--- a/app/client/src/widgets/ChartWidget/index.tsx
+++ b/app/client/src/widgets/ChartWidget/index.tsx
@@ -9,6 +9,7 @@ import { retryPromise } from "utils/AppsmithUtils";
import { EventType } from "constants/ActionConstants";
import withMeta, { WithMeta } from "widgets/MetaHOC";
import propertyConfig from "widgets/ChartWidget/propertyConfig";
+import { CustomFusionChartConfig } from "components/designSystems/appsmith/ChartComponent";
const ChartComponent = lazy(() =>
retryPromise(() =>
@@ -26,6 +27,7 @@ class ChartWidget extends BaseWidget {
chartName: VALIDATION_TYPES.TEXT,
isVisible: VALIDATION_TYPES.BOOLEAN,
chartData: VALIDATION_TYPES.CHART_DATA,
+ customFusionChartConfig: VALIDATION_TYPES.CUSTOM_FUSION_CHARTS_DATA,
};
}
@@ -63,6 +65,7 @@ class ChartWidget extends BaseWidget {
yAxisName={this.props.yAxisName}
chartName={this.props.chartName}
chartData={this.props.chartData}
+ customFusionChartConfig={this.props.customFusionChartConfig}
widgetId={this.props.widgetId}
onDataPointClick={this.onDataPointClick}
allowHorizontalScroll={this.props.allowHorizontalScroll}
@@ -82,7 +85,8 @@ export type ChartType =
| "PIE_CHART"
| "COLUMN_CHART"
| "AREA_CHART"
- | "SCATTER_CHART";
+ | "SCATTER_CHART"
+ | "CUSTOM_FUSION_CHART";
export interface ChartDataPoint {
x: any;
@@ -97,6 +101,7 @@ export interface ChartData {
export interface ChartWidgetProps extends WidgetProps, WithMeta {
chartType: ChartType;
chartData: ChartData[];
+ customFusionChartConfig: { config: CustomFusionChartConfig };
xAxisName: string;
yAxisName: string;
chartName: string;
diff --git a/app/client/src/widgets/ChartWidget/propertyConfig.test.ts b/app/client/src/widgets/ChartWidget/propertyConfig.test.ts
new file mode 100644
index 0000000000..41da9f158d
--- /dev/null
+++ b/app/client/src/widgets/ChartWidget/propertyConfig.test.ts
@@ -0,0 +1,84 @@
+/* eslint-disable @typescript-eslint/no-namespace */
+import { isString, get } from "lodash";
+import config from "./propertyConfig";
+
+declare global {
+ namespace jest {
+ interface Matchers {
+ toBePropertyPaneConfig(): R;
+ }
+ }
+}
+const validateControl = (control: Record) => {
+ if (typeof control !== "object") return false;
+ const properties = [
+ "propertyName",
+ "controlType",
+ "isBindProperty",
+ "isTriggerProperty",
+ ];
+ properties.forEach((prop: string) => {
+ if (!control.hasOwnProperty(prop)) {
+ return false;
+ }
+ const value = control[prop];
+ if (isString(value) && value.length === 0) return false;
+ });
+ return true;
+};
+
+const validateSection = (section: Record) => {
+ if (typeof section !== "object") return false;
+ if (!section.hasOwnProperty("sectionName")) return false;
+ const name = section.sectionName;
+ if ((name as string).length === 0) return false;
+ if (section.children) {
+ return (section.children as Array>).forEach(
+ (child) => {
+ if (!validateControl(child)) return false;
+ },
+ );
+ }
+ return true;
+};
+
+expect.extend({
+ toBePropertyPaneConfig(received) {
+ if (Array.isArray(received)) {
+ let pass = true;
+ received.forEach((section) => {
+ if (!validateSection(section) && !validateControl(section))
+ pass = false;
+ });
+ return {
+ pass,
+ message: () => "Expected value to be a property pane config internal",
+ };
+ }
+ return {
+ pass: false,
+ message: () => "Expected value to be a property pane config external",
+ };
+ },
+});
+
+describe("Validate Chart Widget's property config", () => {
+ it("Validates Chart Widget's property config", () => {
+ expect(config).toBePropertyPaneConfig();
+ });
+
+ it("Validates config when chartType is CUSTOM_FUSION_CHART", () => {
+ const hiddenFn: (props: any) => boolean = get(config, "[1].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")];
+ 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 2964491ea3..e2e47d5dbc 100644
--- a/app/client/src/widgets/ChartWidget/propertyConfig.ts
+++ b/app/client/src/widgets/ChartWidget/propertyConfig.ts
@@ -1,3 +1,5 @@
+import { ChartWidgetProps } from "widgets/ChartWidget";
+
export default [
{
sectionName: "General",
@@ -37,15 +39,41 @@ export default [
label: "Area Chart",
value: "AREA_CHART",
},
+ {
+ label: "Custom Chart",
+ value: "CUSTOM_FUSION_CHART",
+ },
],
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
},
+ {
+ propertyName: "isVisible",
+ label: "Visible",
+ helpText: "Controls the visibility of the widget",
+ controlType: "SWITCH",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ },
],
},
+ {
+ 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",
+ },
{
sectionName: "Chart Data",
+ hidden: (props: ChartWidgetProps) =>
+ props.chartType === "CUSTOM_FUSION_CHART",
children: [
{
helpText: "Populates the chart with the data",
@@ -53,6 +81,7 @@ export default [
placeholderText: 'Enter [{ "x": "val", "y": "val" }]',
label: "Chart Series",
controlType: "CHART_DATA",
+
isBindProperty: false,
isTriggerProperty: false,
children: [
@@ -78,6 +107,8 @@ export default [
},
{
sectionName: "Axis",
+ hidden: (props: ChartWidgetProps) =>
+ props.chartType === "CUSTOM_FUSION_CHART",
children: [
{
helpText: "Specifies the label of the x-axis",
@@ -104,15 +135,7 @@ export default [
controlType: "SWITCH",
isBindProperty: false,
isTriggerProperty: false,
- },
- {
- propertyName: "isVisible",
- label: "Visible",
- helpText: "Controls the visibility of the widget",
- controlType: "SWITCH",
- isJSConvertible: true,
- isBindProperty: true,
- isTriggerProperty: false,
+ hidden: (x: any) => x.chartType === "CUSTOM_FUSION_CHART",
},
],
},
diff --git a/app/client/src/widgets/FormWidget.tsx b/app/client/src/widgets/FormWidget.tsx
index 7a9574f14e..80f3889d1b 100644
--- a/app/client/src/widgets/FormWidget.tsx
+++ b/app/client/src/widgets/FormWidget.tsx
@@ -8,6 +8,40 @@ import * as Sentry from "@sentry/react";
import withMeta from "./MetaHOC";
class FormWidget extends ContainerWidget {
+ static getPropertyPaneConfig() {
+ return [
+ {
+ sectionName: "General",
+ children: [
+ {
+ propertyName: "backgroundColor",
+ label: "Background Color",
+ helpText: "Use a html color name, HEX, RGB or RGBA value",
+ placeholderText: "#FFFFFF / Gray / rgb(255, 99, 71)",
+ controlType: "INPUT_TEXT",
+ isBindProperty: true,
+ isTriggerProperty: false,
+ },
+ {
+ helpText: "Controls the visibility of the widget",
+ propertyName: "isVisible",
+ label: "Visible",
+ controlType: "SWITCH",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ },
+ {
+ propertyName: "shouldScrollContents",
+ label: "Scroll Contents",
+ controlType: "SWITCH",
+ isBindProperty: false,
+ isTriggerProperty: false,
+ },
+ ],
+ },
+ ];
+ }
checkInvalidChildren = (children: WidgetProps[]): boolean => {
return _.some(children, (child) => {
if ("children" in child) {
diff --git a/app/client/src/workers/validations.test.ts b/app/client/src/workers/validations.test.ts
index 2d75c87e69..4f06c62768 100644
--- a/app/client/src/workers/validations.test.ts
+++ b/app/client/src/workers/validations.test.ts
@@ -164,6 +164,303 @@ describe("Validate Validators", () => {
});
});
+describe("Chart Custom Config validator", () => {
+ const validator = VALIDATORS.CUSTOM_FUSION_CHARTS_DATA;
+ it("correctly validates ", () => {
+ const cases = [
+ {
+ input: {
+ type: "area2d",
+ dataSource: {
+ chart: {
+ caption: "Countries With Most Oil Reserves [2017-18]",
+ subCaption: "In MMbbl = One Million barrels",
+ xAxisName: "Country",
+ yAxisName: "Reserves (MMbbl)",
+ numberSuffix: "K",
+ },
+ data: [
+ {
+ label: "Venezuela",
+ value: "290",
+ },
+ {
+ label: "Saudi",
+ value: "260",
+ },
+ {
+ label: "Canada",
+ value: "180",
+ },
+ {
+ label: "Iran",
+ value: "140",
+ },
+ {
+ label: "Russia",
+ value: "115",
+ },
+ {
+ label: "UAE",
+ value: "100",
+ },
+ {
+ label: "US",
+ value: "30",
+ },
+ {
+ label: "China",
+ value: "30",
+ },
+ ],
+ },
+ },
+
+ output: {
+ isValid: true,
+ parsed: {
+ type: "area2d",
+ dataSource: {
+ chart: {
+ caption: "Countries With Most Oil Reserves [2017-18]",
+ subCaption: "In MMbbl = One Million barrels",
+ xAxisName: "Country",
+ yAxisName: "Reserves (MMbbl)",
+ numberSuffix: "K",
+ },
+ data: [
+ {
+ label: "Venezuela",
+ value: "290",
+ },
+ {
+ label: "Saudi",
+ value: "260",
+ },
+ {
+ label: "Canada",
+ value: "180",
+ },
+ {
+ label: "Iran",
+ value: "140",
+ },
+ {
+ label: "Russia",
+ value: "115",
+ },
+ {
+ label: "UAE",
+ value: "100",
+ },
+ {
+ label: "US",
+ value: "30",
+ },
+ {
+ label: "China",
+ value: "30",
+ },
+ ],
+ },
+ },
+
+ transformed: {
+ type: "area2d",
+ dataSource: {
+ chart: {
+ caption: "Countries With Most Oil Reserves [2017-18]",
+ subCaption: "In MMbbl = One Million barrels",
+ xAxisName: "Country",
+ yAxisName: "Reserves (MMbbl)",
+ numberSuffix: "K",
+ },
+ data: [
+ {
+ label: "Venezuela",
+ value: "290",
+ },
+ {
+ label: "Saudi",
+ value: "260",
+ },
+ {
+ label: "Canada",
+ value: "180",
+ },
+ {
+ label: "Iran",
+ value: "140",
+ },
+ {
+ label: "Russia",
+ value: "115",
+ },
+ {
+ label: "UAE",
+ value: "100",
+ },
+ {
+ label: "US",
+ value: "30",
+ },
+ {
+ label: "China",
+ value: "30",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ input: {
+ type: "area2d",
+ dataSource: {
+ data: [
+ {
+ label: "Venezuela",
+ value: "290",
+ },
+ {
+ label: "Saudi",
+ value: "260",
+ },
+ {
+ label: "Canada",
+ value: "180",
+ },
+ {
+ label: "Iran",
+ value: "140",
+ },
+ {
+ label: "Russia",
+ value: "115",
+ },
+ {
+ label: "UAE",
+ value: "100",
+ },
+ {
+ label: "US",
+ value: "30",
+ },
+ {
+ label: "China",
+ value: "30",
+ },
+ ],
+ },
+ },
+ output: {
+ isValid: true,
+ parsed: {
+ type: "area2d",
+ dataSource: {
+ data: [
+ {
+ label: "Venezuela",
+ value: "290",
+ },
+ {
+ label: "Saudi",
+ value: "260",
+ },
+ {
+ label: "Canada",
+ value: "180",
+ },
+ {
+ label: "Iran",
+ value: "140",
+ },
+ {
+ label: "Russia",
+ value: "115",
+ },
+ {
+ label: "UAE",
+ value: "100",
+ },
+ {
+ label: "US",
+ value: "30",
+ },
+ {
+ label: "China",
+ value: "30",
+ },
+ ],
+ },
+ },
+ transformed: {
+ type: "area2d",
+ dataSource: {
+ data: [
+ {
+ label: "Venezuela",
+ value: "290",
+ },
+ {
+ label: "Saudi",
+ value: "260",
+ },
+ {
+ label: "Canada",
+ value: "180",
+ },
+ {
+ label: "Iran",
+ value: "140",
+ },
+ {
+ label: "Russia",
+ value: "115",
+ },
+ {
+ label: "UAE",
+ value: "100",
+ },
+ {
+ label: "US",
+ value: "30",
+ },
+ {
+ label: "China",
+ value: "30",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ input: {
+ type: undefined,
+ dataSource: undefined,
+ },
+ output: {
+ isValid: false,
+ message:
+ "Value does not match type: {type: string, dataSource: { chart: object, data: Array<{label: string, value: number}>}}",
+ parsed: {
+ type: undefined,
+ dataSource: undefined,
+ },
+ transformed: {
+ type: undefined,
+ dataSource: undefined,
+ },
+ },
+ },
+ ];
+ for (const testCase of cases) {
+ const response = validator(testCase.input, DUMMY_WIDGET, {});
+ expect(response).toStrictEqual(testCase.output);
+ }
+ });
+});
describe("validateDateString test", () => {
it("Check whether the valid date strings are recognized as valid", () => {
const validDateStrings = [
diff --git a/app/client/src/workers/validations.ts b/app/client/src/workers/validations.ts
index 83e7357a9d..b305216f66 100644
--- a/app/client/src/workers/validations.ts
+++ b/app/client/src/workers/validations.ts
@@ -365,6 +365,39 @@ export const VALIDATORS: Record = {
}
return { isValid, parsed: parsedChartData, transformed: parsedChartData };
},
+ [VALIDATION_TYPES.CUSTOM_FUSION_CHARTS_DATA]: (
+ value: any,
+ props: WidgetProps,
+ dataTree?: DataTree,
+ ): ValidationResponse => {
+ const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.OBJECT](
+ value,
+ props,
+ dataTree,
+ );
+ if (props.chartName && parsed.dataSource && parsed.dataSource.chart) {
+ parsed.dataSource.chart.caption = props.chartName;
+ }
+ if (!isValid) {
+ return {
+ isValid,
+ parsed,
+ message: `${WIDGET_TYPE_VALIDATION_ERROR}: {type: string, dataSource: { chart: object, data: Array<{label: string, value: number}>}}`,
+ };
+ }
+ if (parsed.renderAt) {
+ delete parsed.renderAt;
+ }
+ if (!parsed.dataSource || !parsed.type) {
+ return {
+ isValid: false,
+ parsed: parsed,
+ transformed: parsed,
+ message: `${WIDGET_TYPE_VALIDATION_ERROR}: {type: string, dataSource: { chart: object, data: Array<{label: string, value: number}>}}`,
+ };
+ }
+ return { isValid, parsed, transformed: parsed };
+ },
[VALIDATION_TYPES.MARKERS]: (
value: any,
props: WidgetProps,