feat: add Echarts Graph chart (#13111)

* update preset file

* migration added

* bug fix

* lint

* Update superset/migrations/versions/1412ec1e5a7b_legacy_force_directed_to_echart.py

Co-authored-by: Jesse Yang <jesse.yang@airbnb.com>

* version bump

* bummp dependency

* add package.lock

* merge migrations

* add integration test

* lint

* remove unnecessary params from test

* check key existance in migrations

* test to typescript

* Update graph.test.ts

* fix migration downstream head

* Upgrade to lockfileversion 2

Co-authored-by: Jesse Yang <jesse.yang@airbnb.com>
This commit is contained in:
Mayur
2021-02-24 06:17:20 +05:30
committed by GitHub
parent 8ef52babfe
commit 6954114f84
4 changed files with 200 additions and 38 deletions

View File

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

View File

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

View File

@@ -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' }),

View File

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