diff --git a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/graph.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/graph.test.ts new file mode 100644 index 0000000000000..47adb075bdd91 --- /dev/null +++ b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/graph.test.ts @@ -0,0 +1,80 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +type adhocFilter = { + expressionType: string; + subject: string; + operator: string; + comparator: string; + clause: string; + sqlExpression: string | null; + filterOptionName: string; +}; + +describe('Visualization > Graph', () => { + const GRAPH_FORM_DATA = { + datasource: '1__table', + viz_type: 'graph_chart', + slice_id: 55, + granularity_sqla: 'ds', + time_grain_sqla: 'P1D', + time_range: '100 years ago : now', + metric: 'sum__value', + adhoc_filters: [], + source: 'source', + target: 'target', + row_limit: 50000, + show_legend: true, + color_scheme: 'bnbColors', + }; + + function verify(formData: { + [name: string]: string | boolean | number | Array; + }): void { + cy.visitChartByParams(JSON.stringify(formData)); + cy.verifySliceSuccess({ waitAlias: '@getJson' }); + } + + beforeEach(() => { + cy.login(); + cy.intercept('POST', '/api/v1/chart/data*').as('getJson'); + }); + + it('should work with ad-hoc metric', () => { + verify(GRAPH_FORM_DATA); + cy.get('.chart-container .graph_chart canvas').should('have.length', 1); + }); + + it('should work with simple filter', () => { + verify({ + ...GRAPH_FORM_DATA, + adhoc_filters: [ + { + expressionType: 'SIMPLE', + subject: 'source', + operator: '==', + comparator: 'Agriculture', + clause: 'WHERE', + sqlExpression: null, + filterOptionName: 'filter_tqx1en70hh_7nksse7nqic', + }, + ], + }); + cy.get('.chart-container .graph_chart canvas').should('have.length', 1); + }); +}); diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 203e2144ed8c2..2e2f420eaae20 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -16472,8 +16472,7 @@ "node_modules/@types/anymatch": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", - "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", - "dev": true + "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==" }, "node_modules/@types/aria-query": { "version": "4.2.0", @@ -16857,8 +16856,7 @@ "node_modules/@types/node": { "version": "10.12.15", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.15.tgz", - "integrity": "sha512-9kROxduaN98QghwwHmxXO2Xz3MaWf+I1sLVAA6KJDF5xix+IyXVhds0MAfdNwtcpSrzhaTsNB0/jnL86fgUhqA==", - "dev": true + "integrity": "sha512-9kROxduaN98QghwwHmxXO2Xz3MaWf+I1sLVAA6KJDF5xix+IyXVhds0MAfdNwtcpSrzhaTsNB0/jnL86fgUhqA==" }, "node_modules/@types/node-fetch": { "version": "2.5.8", @@ -16971,7 +16969,6 @@ "version": "0.32.22", "resolved": "https://registry.npmjs.org/@types/react-bootstrap/-/react-bootstrap-0.32.22.tgz", "integrity": "sha512-pjUVcJzogMxns3lbvMqnnU+I8EOYxl3aI13tS2vvRm0RdAe1rs7Ds/VZA29GI6p8p3Un6NqKUpW3+dgwAjyzxg==", - "dev": true, "dependencies": { "@types/react": "*" } @@ -17017,7 +17014,6 @@ "version": "5.5.4", "resolved": "https://registry.npmjs.org/@types/react-loadable/-/react-loadable-5.5.4.tgz", "integrity": "sha512-otKcjNCfVUzdBMdwOqFITTmBruIXw6GeoZitTBvJ6BMrif8Utu2JLy42GWukNnYI7ewJdncUCooz5Y/1dBz4+w==", - "dev": true, "dependencies": { "@types/react": "*", "@types/webpack": "*" @@ -17237,8 +17233,7 @@ "node_modules/@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", - "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", - "dev": true + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==" }, "node_modules/@types/stack-utils": { "version": "1.0.1", @@ -17249,8 +17244,7 @@ "node_modules/@types/tapable": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.4.tgz", - "integrity": "sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==", - "dev": true + "integrity": "sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==" }, "node_modules/@types/testing-library__jest-dom": { "version": "5.9.5", @@ -17265,7 +17259,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz", "integrity": "sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ==", - "dev": true, "dependencies": { "source-map": "^0.6.1" } @@ -17274,7 +17267,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -17288,7 +17280,6 @@ "version": "4.39.1", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.39.1.tgz", "integrity": "sha512-rgO9ihNu/l72Sjx3shqwc9r6gi+tOMsqxhMEZhOEVIZt82GFOeUyEdpTk1BO2HqEHLS/XJW8ldUTIIfIMMyYFQ==", - "dev": true, "dependencies": { "@types/anymatch": "*", "@types/node": "*", @@ -17308,7 +17299,6 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.5.tgz", "integrity": "sha512-zfvjpp7jiafSmrzJ2/i3LqOyTYTuJ7u1KOXlKgDlvsj9Rr0x7ZiYu5lZbXwobL7lmsRNtPXlBfmaUD8eU2Hu8w==", - "dev": true, "dependencies": { "@types/node": "*", "@types/source-list-map": "*", @@ -17319,7 +17309,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -17328,7 +17317,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -70643,8 +70631,7 @@ "@types/anymatch": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", - "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", - "dev": true + "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==" }, "@types/aria-query": { "version": "4.2.0", @@ -71030,8 +71017,7 @@ "@types/node": { "version": "10.12.15", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.15.tgz", - "integrity": "sha512-9kROxduaN98QghwwHmxXO2Xz3MaWf+I1sLVAA6KJDF5xix+IyXVhds0MAfdNwtcpSrzhaTsNB0/jnL86fgUhqA==", - "dev": true + "integrity": "sha512-9kROxduaN98QghwwHmxXO2Xz3MaWf+I1sLVAA6KJDF5xix+IyXVhds0MAfdNwtcpSrzhaTsNB0/jnL86fgUhqA==" }, "@types/node-fetch": { "version": "2.5.8", @@ -71140,7 +71126,6 @@ "version": "0.32.22", "resolved": "https://registry.npmjs.org/@types/react-bootstrap/-/react-bootstrap-0.32.22.tgz", "integrity": "sha512-pjUVcJzogMxns3lbvMqnnU+I8EOYxl3aI13tS2vvRm0RdAe1rs7Ds/VZA29GI6p8p3Un6NqKUpW3+dgwAjyzxg==", - "dev": true, "requires": { "@types/react": "*" } @@ -71186,7 +71171,6 @@ "version": "5.5.4", "resolved": "https://registry.npmjs.org/@types/react-loadable/-/react-loadable-5.5.4.tgz", "integrity": "sha512-otKcjNCfVUzdBMdwOqFITTmBruIXw6GeoZitTBvJ6BMrif8Utu2JLy42GWukNnYI7ewJdncUCooz5Y/1dBz4+w==", - "dev": true, "requires": { "@types/react": "*", "@types/webpack": "*" @@ -71412,8 +71396,7 @@ "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", - "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", - "dev": true + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==" }, "@types/stack-utils": { "version": "1.0.1", @@ -71424,8 +71407,7 @@ "@types/tapable": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.4.tgz", - "integrity": "sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==", - "dev": true + "integrity": "sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==" }, "@types/testing-library__jest-dom": { "version": "5.9.5", @@ -71440,7 +71422,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz", "integrity": "sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ==", - "dev": true, "requires": { "source-map": "^0.6.1" }, @@ -71448,8 +71429,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -71462,7 +71442,6 @@ "version": "4.39.1", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.39.1.tgz", "integrity": "sha512-rgO9ihNu/l72Sjx3shqwc9r6gi+tOMsqxhMEZhOEVIZt82GFOeUyEdpTk1BO2HqEHLS/XJW8ldUTIIfIMMyYFQ==", - "dev": true, "requires": { "@types/anymatch": "*", "@types/node": "*", @@ -71475,8 +71454,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -71490,7 +71468,6 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.5.tgz", "integrity": "sha512-zfvjpp7jiafSmrzJ2/i3LqOyTYTuJ7u1KOXlKgDlvsj9Rr0x7ZiYu5lZbXwobL7lmsRNtPXlBfmaUD8eU2Hu8w==", - "dev": true, "requires": { "@types/node": "*", "@types/source-list-map": "*", @@ -71500,8 +71477,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -96536,7 +96512,8 @@ "reactable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reactable/-/reactable-1.1.0.tgz", - "integrity": "sha512-SnvZ3CXyFFxGotw9cqNiVUGb2oW16UlIypGQZRJGgPiJuFqW22jO7A+Y/Tvv8no8F/bZoLdZ+QJP7eZfcc9kCw==" + "integrity": "sha512-SnvZ3CXyFFxGotw9cqNiVUGb2oW16UlIypGQZRJGgPiJuFqW22jO7A+Y/Tvv8no8F/bZoLdZ+QJP7eZfcc9kCw==", + "requires": {} }, "reactcss": { "version": "1.2.3", diff --git a/superset-frontend/src/visualizations/presets/MainPreset.js b/superset-frontend/src/visualizations/presets/MainPreset.js index 6ad96b61d7051..901d9bb32a558 100644 --- a/superset-frontend/src/visualizations/presets/MainPreset.js +++ b/superset-frontend/src/visualizations/presets/MainPreset.js @@ -25,7 +25,6 @@ import CalendarChartPlugin from '@superset-ui/legacy-plugin-chart-calendar'; import ChordChartPlugin from '@superset-ui/legacy-plugin-chart-chord'; import CountryMapChartPlugin from '@superset-ui/legacy-plugin-chart-country-map'; import EventFlowChartPlugin from '@superset-ui/legacy-plugin-chart-event-flow'; -import ForceDirectedChartPlugin from '@superset-ui/legacy-plugin-chart-force-directed'; import HeatmapChartPlugin from '@superset-ui/legacy-plugin-chart-heatmap'; import HistogramChartPlugin from '@superset-ui/legacy-plugin-chart-histogram'; import HorizonChartPlugin from '@superset-ui/legacy-plugin-chart-horizon'; @@ -58,6 +57,7 @@ import { EchartsPieChartPlugin, EchartsBoxPlotChartPlugin, EchartsTimeseriesChartPlugin, + EchartsGraphChartPlugin, } from '@superset-ui/plugin-chart-echarts'; import { SelectFilterPlugin, @@ -88,7 +88,7 @@ export default class MainPreset extends Preset { new DualLineChartPlugin().configure({ key: 'dual_line' }), new EventFlowChartPlugin().configure({ key: 'event_flow' }), new FilterBoxChartPlugin().configure({ key: 'filter_box' }), - new ForceDirectedChartPlugin().configure({ key: 'directed_force' }), + new EchartsGraphChartPlugin().configure({ key: 'graph_chart' }), new HeatmapChartPlugin().configure({ key: 'heatmap' }), new HistogramChartPlugin().configure({ key: 'histogram' }), new HorizonChartPlugin().configure({ key: 'horizon' }), diff --git a/superset/migrations/versions/1412ec1e5a7b_legacy_force_directed_to_echart.py b/superset/migrations/versions/1412ec1e5a7b_legacy_force_directed_to_echart.py new file mode 100644 index 0000000000000..4407c1f8b7d41 --- /dev/null +++ b/superset/migrations/versions/1412ec1e5a7b_legacy_force_directed_to_echart.py @@ -0,0 +1,105 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +"""legacy force directed to echart + +Revision ID: 1412ec1e5a7b +Revises: c501b7c653a3 +Create Date: 2021-02-14 11:46:02.379832 + +""" +import json + +import sqlalchemy as sa +from alembic import op +from sqlalchemy import Column, Integer, or_, String, Text +from sqlalchemy.ext.declarative import declarative_base + +from superset import db + +# revision identifiers, used by Alembic. +revision = "1412ec1e5a7b" +down_revision = "c501b7c653a3" + + +Base = declarative_base() + + +class Slice(Base): + __tablename__ = "slices" + id = Column(Integer, primary_key=True) + viz_type = Column(String(250)) + params = Column(Text) + + +def upgrade(): + bind = op.get_bind() + session = db.Session(bind=bind) + + for slc in session.query(Slice).filter(Slice.viz_type.like("directed_force")): + params = json.loads(slc.params) + groupby = params.get("groupby", []) + if groupby: + params["source"] = groupby[0] + params["target"] = groupby[1] if len(groupby) > 1 else None + del params["groupby"] + + params["edgeLength"] = 400 + params["repulsion"] = 1000 + params["layout"] = "force" + + if "charge" in params: + del params["charge"] + if "collapsed_fieldset" in params: + del params["collapsed_fieldsets"] + if "link_length" in params: + del params["link_length"] + + slc.params = json.dumps(params) + slc.viz_type = "graph_chart" + session.merge(slc) + session.commit() + session.close() + + +def downgrade(): + bind = op.get_bind() + session = db.Session(bind=bind) + + for slc in session.query(Slice).filter(Slice.viz_type.like("graph_chart")): + params = json.loads(slc.params) + source = params.get("source", None) + target = params.get("target", None) + if source and target: + params["groupby"] = [source, target] + del params["source"] + del params["target"] + + params["charge"] = "-500" + params["collapsed_fieldsets"] = "" + params["link_length"] = "200" + if "edgeLength" in params: + del params["edgeLength"] + if "repulsion" in params: + del params["repulsion"] + if "layout" in params: + del params["layout"] + + slc.params = json.dumps(params) + slc.viz_type = "directed_force" + session.merge(slc) + session.commit() + session.close()