diff --git a/app/client/cypress/e2e/Regression/ClientSide/Widgets/Chart/ChartDataPoint_Spec.ts b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Chart/ChartDataPoint_Spec.ts index 817605e5d5..f65a58b395 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Widgets/Chart/ChartDataPoint_Spec.ts +++ b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Chart/ChartDataPoint_Spec.ts @@ -23,6 +23,11 @@ describe("Input widget test with default value from chart datapoint", () => { }); }); + afterEach(() => { + //this is to enable re-attempt passing! + deployMode.NavigateBacktoEditor(); + }); + it("1. Chart widget - Input widget test with default value from another Input widget", () => { entityExplorer.SelectEntityByName("Input1", "Widgets"); propPane.UpdatePropertyFieldValue( @@ -55,8 +60,46 @@ describe("Input widget test with default value from chart datapoint", () => { .should("have.value", dsl.dsl.children[0].chartData[0].seriesName); }); - afterEach(() => { - //this is to enable re-attempt passing! + it("2. onDataPointClick should work and respond with x, y, seriesTitle, and rawEventData (in case of custom fusion chart).", () => { + assertHelper.AssertNetworkStatus("@updateLayout"); + entityExplorer.SelectEntityByName("Chart1"); + propPane.TogglePropertyState("Show Labels", "On"); + propPane.SelectPlatformFunction("onDataPointClick", "Show alert"); + agHelper.EnterActionValue("Message", dataSet.bindingDataPoint); + + deployMode.DeployApp(); + agHelper.Sleep(1500); //waiting for chart to load! + + agHelper.GetNClickByContains(widgetLocators.chartDataPoint, "36000"); + cy.get(locators._toastMsg).should( + "have.text", + `{"x":"Sun","y":36000,"seriesTitle":"Sales"}`, + ); deployMode.NavigateBacktoEditor(); + + agHelper.AddDsl("chartCustomSankeyDataDsl"); + cy.fixture("chartCustomSankeyDataDsl").then((val: any) => { + dsl = val; + }); + cy.fixture("testdata").then(function (data: any) { + dataSet = data; + }); + + assertHelper.AssertNetworkStatus("@updateLayout"); + entityExplorer.SelectEntityByName("Chart1"); + propPane.SelectPlatformFunction("onDataPointClick", "Show alert"); + agHelper.EnterActionValue("Message", dataSet.bindingDataPoint); + + deployMode.DeployApp(); + + agHelper.Sleep(1500); //waiting for chart to load! + agHelper.GetNClickByContains( + widgetLocators.chartDataPoint, + "European Union", + ); + cy.get(locators._toastMsg).should( + "have.text", + `{"x":-1,"y":-1,"seriesTitle":"","rawEventData":{"color":"#FFC533","alpha":100,"labelFill":"#666","labelAlpha":100,"value":4747591,"label":"European Union","sourceLinks":["France","United States","United Kingdom","Switzerland","Austria","Sweden"],"targetLinks":["Netherlands","Germany","Belgium","China","Italy","Russia","Spain"]}}`, + ); }); }); diff --git a/app/client/cypress/fixtures/chartCustomSankeyDataDsl.json b/app/client/cypress/fixtures/chartCustomSankeyDataDsl.json new file mode 100644 index 0000000000..fc829087e4 --- /dev/null +++ b/app/client/cypress/fixtures/chartCustomSankeyDataDsl.json @@ -0,0 +1,84 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 4896.0, + "snapColumns": 64.0, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0.0, + "bottomRow": 790.0, + "containerStyle": "none", + "snapRows": 124.0, + "parentRowSpace": 1.0, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 85.0, + "minHeight": 1292.0, + "dynamicTriggerPathList": [], + "parentColumnSpace": 1.0, + "dynamicBindingPathList": [], + "leftColumn": 0.0, + "children": [ + { + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "mobileBottomRow": 43.0, + "widgetName": "Chart1", + "allowScroll": false, + "dynamicPropertyPathList": [], + "displayName": "Chart", + "iconSVG": "/static/media/icon.8eea39845729f7f4bfadeecd3810a09d.svg", + "searchTags": ["graph", "visuals", "visualisations"], + "topRow": 4.0, + "bottomRow": 79.0, + "parentRowSpace": 10.0, + "type": "CHART_WIDGET", + "hideCard": false, + "mobileRightColumn": 29.0, + "chartData": { + "xne7s065jy": { + "seriesName": "2023", + "data": "[\n {\n \"x\": \"Product1\",\n \"y\": 20000\n },\n {\n \"x\": \"Product2\",\n \"y\": 22000\n },\n {\n \"x\": \"Product3\",\n \"y\": 32000\n }\n]" + } + }, + "animateLoading": true, + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "parentColumnSpace": 20.078125, + "dynamicTriggerPathList": [{ "key": "onDataPointClick" }], + "leftColumn": 0.0, + "dynamicBindingPathList": [ + { "key": "borderRadius" }, + { "key": "boxShadow" }, + { "key": "accentColor" }, + { "key": "fontFamily" } + ], + "customFusionChartConfig": "{\n \"type\": \"sankey\",\n \"dataSource\": {\n \"nodes\": [\n {\n \"label\": \"Netherlands\"\n },\n {\n \"label\": \"Canada\"\n },\n {\n \"label\": \"Belgium\"\n },\n {\n \"label\": \"Italy\"\n },\n {\n \"label\": \"Mexico\"\n },\n {\n \"label\": \"Russia\"\n },\n {\n \"label\": \"Spain\"\n },\n {\n \"label\": \"South Korea\"\n },\n {\n \"label\": \"Germany\"\n },\n {\n \"label\": \"China\"\n },\n {\n \"label\": \"European Union\"\n },\n {\n \"label\": \"Japan\"\n },\n {\n \"label\": \"United Kingdom\"\n },\n {\n \"label\": \"United States\"\n },\n {\n \"label\": \"France\"\n },\n {\n \"label\": \"Hong Kong\"\n },\n {\n \"label\": \"Switzerland\"\n },\n {\n \"label\": \"Austria\"\n },\n {\n \"label\": \"Sweden\"\n }\n ],\n \"links\": [\n {\n \"from\": \"Netherlands\",\n \"to\": \"European Union\",\n \"value\": 798744\n },\n {\n \"from\": \"Germany\",\n \"to\": \"European Union\",\n \"value\": 1468990\n },\n {\n \"from\": \"European Union\",\n \"to\": \"France\",\n \"value\": 745931\n },\n {\n \"from\": \"European Union\",\n \"to\": \"United States\",\n \"value\": 660541\n },\n {\n \"from\": \"Canada\",\n \"to\": \"United States\",\n \"value\": 594546\n },\n {\n \"from\": \"Belgium\",\n \"to\": \"European Union\",\n \"value\": 628796\n },\n {\n \"from\": \"China\",\n \"to\": \"Hong Kong\",\n \"value\": 400571\n },\n {\n \"from\": \"China\",\n \"to\": \"United States\",\n \"value\": 526454\n },\n {\n \"from\": \"European Union\",\n \"to\": \"United Kingdom\",\n \"value\": 520318\n },\n {\n \"from\": \"China\",\n \"to\": \"European Union\",\n \"value\": 560536\n },\n {\n \"from\": \"Italy\",\n \"to\": \"European Union\",\n \"value\": 539556\n },\n {\n \"from\": \"Mexico\",\n \"to\": \"United States\",\n \"value\": 492715\n },\n {\n \"from\": \"Russia\",\n \"to\": \"European Union\",\n \"value\": 385778\n },\n {\n \"from\": \"Spain\",\n \"to\": \"European Union\",\n \"value\": 365191\n },\n {\n \"from\": \"China\",\n \"to\": \"Japan\",\n \"value\": 312062\n },\n {\n \"from\": \"European Union\",\n \"to\": \"Switzerland\",\n \"value\": 328609\n },\n {\n \"from\": \"South Korea\",\n \"to\": \"China\",\n \"value\": 229073\n },\n {\n \"from\": \"European Union\",\n \"to\": \"Austria\",\n \"value\": 244913\n },\n {\n \"from\": \"Japan\",\n \"to\": \"United States\",\n \"value\": 206091\n },\n {\n \"from\": \"European Union\",\n \"to\": \"Sweden\",\n \"value\": 204849\n },\n {\n \"from\": \"Germany\",\n \"to\": \"United States\",\n \"value\": 184287\n }\n ],\n \"chart\": {\n \"caption\": \"Immigrant Flow for Last Month (In Millions)\",\n \"legendPosition\": \"bottom\",\n \"linkcolor\": \"blend\",\n \"theme\": \"fusion\"\n }\n }\n}", + "onDataPointClick": "", + "showDataPointLabel": false, + "key": "8qdt18v5ny", + "isDeprecated": false, + "rightColumn": 64.0, + "widgetId": "1jj1ihohlk", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "minWidth": 450.0, + "isVisible": true, + "version": 1.0, + "parentId": "0", + "labelOrientation": "auto", + "tags": ["Display"], + "renderMode": "CANVAS", + "isLoading": false, + "mobileTopRow": 11.0, + "responsiveBehavior": "fill", + "yAxisName": "Revenue($)", + "originalTopRow": 68.0, + "chartName": "Sales Report", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "mobileLeftColumn": 5.0, + "xAxisName": "Product Line", + "originalBottomRow": 100.0, + "chartType": "CUSTOM_FUSION_CHART" + } + ] + } +} diff --git a/app/client/cypress/locators/Widgets.json b/app/client/cypress/locators/Widgets.json index b9ee8630ca..d065a90581 100644 --- a/app/client/cypress/locators/Widgets.json +++ b/app/client/cypress/locators/Widgets.json @@ -79,7 +79,7 @@ "tableOnRowSelect": ".t--property-control-onrowselected .t--open-dropdown-Select-Action", "switchInput": ".t--draggable-switchwidget span.t--widget-name", "switchLabel": ".t--draggable-switchwidget label", - "switch":".bp3-switch", + "switch": ".bp3-switch", "multiSelectInput": ".t--draggable-multiselectwidget span.t--widget-name", "multiSelectLabel": ".t--draggable-multiselectwidget label", "addColumn": ".t--add-column-btn", @@ -202,30 +202,30 @@ "codescannerwidget": ".t--widget-codescannerwidget", "widgetNameSpan": ".t--widget-propertypane-toggle > .t--widget-name", "listWidgetv2": ".t--widget-listwidgetv2", - "progressWidget":".t--widget-progresswidget", - "circularProgressWidget":"[data-value='circular']", + "progressWidget": ".t--widget-progresswidget", + "circularProgressWidget": "[data-value='circular']", "linearProgressWidget": "[data-value='linear']", - "cameraErrorText" : ".error-text:contains('Permission denied')", + "cameraErrorText": ".error-text:contains('Permission denied')", "cameraVideo": "video", - "cameraWidgetScreen" : "div.fullscreen > div", + "cameraWidgetScreen": "div.fullscreen > div", "cameraFullscreenBtn": "//div[@class='fullscreen']/div[2]/div/div[3]/button", "cameraCaptureBtn": "//div[@class='fullscreen']/div[2]/div/div[2]/button", "cameraStopRecordingBtn": "//div[@class='fullscreen']/div[2]/div/div[2]/button", "cameraSaveBtn": "//div[@class='fullscreen']/div[2]/div/div[2]/button", "cameraRefreshBtn": "//div[@class='fullscreen']/div[2]/div/div[2]/button", - "cameraImageVideoOnOffBtn":"//div[@class='fullscreen']/div[2]/div/div[1]/button", - "cameraImageVideoDropdown":"//div[@class='fullscreen']/div[2]/div/div[1]/span/button", - "cameraImageDiscardBtn":"//div[@class='fullscreen']/div[2]/div/div[2]/button[2]", + "cameraImageVideoOnOffBtn": "//div[@class='fullscreen']/div[2]/div/div[1]/button", + "cameraImageVideoDropdown": "//div[@class='fullscreen']/div[2]/div/div[1]/span/button", + "cameraImageDiscardBtn": "//div[@class='fullscreen']/div[2]/div/div[2]/button[2]", "cameraMicrophoneBtn": "//div[@class='fullscreen']/div[2]/div/div[1]/button[1]", "cameraMicrophoneDropdown": "//div[@class='fullscreen']/div[2]/div/div[1]/span[1]/button", "cameraVideoOnOffBtn": "//div[@class='fullscreen']/div[2]/div/div[1]/button[2]", "cameraVideoDropdown": "//div[@class='fullscreen']/div[2]/div/div[1]/span[2]/button", - "cameraVideoPlayBtn":"//div[@class='fullscreen']/div[2]/div/div[2]/button[2]", - "cameraVideodiscardBtn":"//div[@class='fullscreen']/div[2]/div/div[2]/button[3]", - "codeScannerScreen" : "div.code-scanner-camera-container > div", + "cameraVideoPlayBtn": "//div[@class='fullscreen']/div[2]/div/div[2]/button[2]", + "cameraVideodiscardBtn": "//div[@class='fullscreen']/div[2]/div/div[2]/button[3]", + "codeScannerScreen": "div.code-scanner-camera-container > div", "codeScannerVideo": "video", - "codeScannerScanButton":"//*[text()='Scan a QR/Barcode']/parent::button", - "codeScannerNewScanButton":"//*[text()='Scan Code']/parent::button", - "codeScannerClose":".code-scanner-close", + "codeScannerScanButton": "//*[text()='Scan a QR/Barcode']/parent::button", + "codeScannerNewScanButton": "//*[text()='Scan Code']/parent::button", + "codeScannerClose": ".code-scanner-close", "codeScannerModal": ".code-scanner-content" } diff --git a/app/client/src/widgets/ChartWidget/component/index.test.tsx b/app/client/src/widgets/ChartWidget/component/index.test.tsx index ecb274dfa7..a8474c56f9 100644 --- a/app/client/src/widgets/ChartWidget/component/index.test.tsx +++ b/app/client/src/widgets/ChartWidget/component/index.test.tsx @@ -145,11 +145,11 @@ describe("Chart Widget", () => { }); it("3. adds a click event when user adds a click callback", async () => { - const mockCallback = jest.fn(); + const mockCallback = jest.fn((params) => params); const props = { ...defaultProps }; props.onDataPointClick = (point) => { point; - mockCallback(); + mockCallback(point); }; render(); diff --git a/app/client/src/widgets/ChartWidget/component/index.tsx b/app/client/src/widgets/ChartWidget/component/index.tsx index 4c19e023e5..ba20bb9cf7 100644 --- a/app/client/src/widgets/ChartWidget/component/index.tsx +++ b/app/client/src/widgets/ChartWidget/component/index.tsx @@ -128,6 +128,35 @@ class ChartComponent extends React.Component< }; } + parseOnDataPointClickParams = (evt: any, chartType: ChartType) => { + if (chartType === "CUSTOM_FUSION_CHART") { + const data = evt.data; + const seriesTitle = get(data, "datasetName", ""); + + return { + x: data.categoryLabel ?? -1, + y: data.dataValue ?? -1, + seriesTitle, + rawEventData: data, + } as ChartSelectedDataPoint; + } else { + const data: unknown[] = evt.data as unknown[]; + const x: unknown = data[0]; + + const index = (evt.seriesIndex ?? 0) + 1; + const y: unknown = data[index]; + + const seriesName = + evt.seriesName && evt.seriesName?.length > 0 ? evt.seriesName : "null"; + + return { + x: x ?? -1, + y: y ?? -1, + seriesTitle: seriesName, + } as ChartSelectedDataPoint; + } + }; + getEChartsOptions = () => { const options = { ...this.echartsConfigurationBuilder.prepareEChartConfig( @@ -142,22 +171,12 @@ class ChartComponent extends React.Component< }; dataClickCallback = (params: echarts.ECElementEvent) => { - const eventData: unknown[] = params.data as unknown[]; - const x: unknown = eventData[0]; + const dataPointClickParams = this.parseOnDataPointClickParams( + params, + this.state.chartType, + ); - const index = (params.seriesIndex ?? 0) + 1; - const y: unknown = eventData[index]; - - const seriesName = - params.seriesName && params.seriesName?.length > 0 - ? params.seriesName - : "null"; - - this.props.onDataPointClick({ - x: x, - y: y, - seriesTitle: seriesName, - }); + this.props.onDataPointClick(dataPointClickParams); }; initializeEchartsInstance = () => { @@ -304,13 +323,12 @@ class ChartComponent extends React.Component< height: "100%", events: { dataPlotClick: (evt: any) => { - const data = evt.data; - const seriesTitle = get(data, "datasetName", ""); - this.props.onDataPointClick({ - x: data.categoryLabel, - y: data.dataValue, - seriesTitle, - }); + const dataPointClickParams = this.parseOnDataPointClickParams( + evt, + this.state.chartType, + ); + + this.props.onDataPointClick(dataPointClickParams); }, }, ...this.getCustomFusionChartDataSource(), diff --git a/app/client/src/widgets/ChartWidget/constants.ts b/app/client/src/widgets/ChartWidget/constants.ts index 3a4256d0bf..f4236949cf 100644 --- a/app/client/src/widgets/ChartWidget/constants.ts +++ b/app/client/src/widgets/ChartWidget/constants.ts @@ -32,6 +32,7 @@ export interface ChartSelectedDataPoint { x: any; y: any; seriesTitle: string; + rawEventData?: unknown; } export const messages = {