From 8a54bddf65ab19ca7503aa0f66fe2dfe5e1c7a09 Mon Sep 17 00:00:00 2001 From: Thomas Wang Date: Sat, 9 Feb 2019 20:14:48 -0800 Subject: [PATCH 1/7] initial commit for VIZ-58 --- .../components/controls/VizTypeControl.jsx | 40 ++++++++++++++- superset/views/core.py | 50 ++++++++++++++++++- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/superset/assets/src/explore/components/controls/VizTypeControl.jsx b/superset/assets/src/explore/components/controls/VizTypeControl.jsx index 86f2f80600d9..0f962e1eb4d1 100644 --- a/superset/assets/src/explore/components/controls/VizTypeControl.jsx +++ b/superset/assets/src/explore/components/controls/VizTypeControl.jsx @@ -45,6 +45,7 @@ export default class VizTypeControl extends React.PureComponent { this.state = { showModal: false, filter: '', + vizTypeStats: '', }; this.toggleModal = this.toggleModal.bind(this); this.changeSearch = this.changeSearch.bind(this); @@ -69,6 +70,12 @@ export default class VizTypeControl extends React.PureComponent { this.searchRef.focus(); } } + componentDidMount() { + $.get("/superset/viz_type_stats", (data) => { + this.setState({ vizTypeStats: data.this_user.concat(data.overall) }); + this.forceUpdate(); + }); + } renderItem(entry) { const { value } = this.props; const { key, value: type } = entry; @@ -89,13 +96,42 @@ export default class VizTypeControl extends React.PureComponent { ); } + getVizTypeByKey(types, key) { + for (var i = 0; i < types.length; i++) { + if (types[i].key == key) return types[i]; + } + } + sortVizTypes(types) { + var sorted = []; + var loaded_keys = new Set(); + // Sort based on existing visualization type usages statistics + for (var i = 0; i < this.state.vizTypeStats.length; i++) { + var key = this.state.vizTypeStats[i].viz_type; + if (loaded_keys.has(key)) continue; + var t = this.getVizTypeByKey(types, key); + if (typeof t !== 'undefined') { + sorted.push(t); + loaded_keys.add(key); + } + } + // For visualization types that do not have any statistics, apply the + // original order + for (var i = 0; i < types.length; i++) { + var t = types[i]; + var key = t['key']; + if (! loaded_keys.has(key)) { + sorted.push(t); + loaded_keys.add(key); + } + } + return sorted; + } render() { const { filter, showModal } = this.state; const { value } = this.props; const registry = getChartMetadataRegistry(); - - const types = registry.entries(); + const types = this.sortVizTypes(registry.entries()); const filteredTypes = filter.length > 0 ? types.filter(type => type.value.name.toLowerCase().includes(filter)) : types; diff --git a/superset/views/core.py b/superset/views/core.py index fa704989d89f..58a5e313d8b0 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -36,7 +36,7 @@ import pandas as pd import simplejson as json import sqlalchemy as sqla -from sqlalchemy import and_, create_engine, MetaData, or_, update +from sqlalchemy import and_, create_engine, MetaData, or_, update, func, desc from sqlalchemy.engine.url import make_url from sqlalchemy.exc import IntegrityError from werkzeug.routing import BaseConverter @@ -2825,6 +2825,54 @@ def sqllab(self): bootstrap_data=json.dumps(d, default=utils.json_iso_dttm_ser), ) + @has_access + @expose('/viz_type_stats', methods=['GET']) + @expose('/viz_type_stats//', methods=['GET']) + def viz_type_stats(self, user_id=None): + """ + This endpoint provides the statistics of the visualization type usage. + """ + if not user_id: + user_id = g.user.id + + key = self._get_viz_type_stats_cache_key(user_id) + payload = cache.get(key) + if payload is not None: + payload['served_from'] = 'cache' + else: + payload = { + 'served_from': 'live', + 'this_user': self._get_viz_type_stats(user_id=user_id), + 'overall': self._get_viz_type_stats(), + } + cache.set(key, payload, 7 * 24 * 60 * 60) # cache for 7 days + return json_success( + json.dumps(payload, default=utils.json_int_dttm_ser) + ) + + def _get_viz_type_stats_cache_key(self, user_id): + """ + This function returns the key for caching the result of visualization + type usage statistics of this user. + """ + return f"viz_type_stats_{user_id}" + + def _get_viz_type_stats(self, user_id=None): + """ + This function returns the statistics of the visualization type usage of + the `user_id`. If `user_id` is not provided, this function returns the + same statistics across all users. + """ + query = db.session.query( + models.Slice.viz_type, + func.count().label("cnt"), + ) + if user_id is not None: + query = query.filter(models.Slice.created_by_fk == user_id) + query = query.group_by(models.Slice.viz_type) + query = query.order_by(desc("cnt")) + return query.all() + @api @handle_api_exception @has_access_api From 4664e61d4d9ce48b6a905a22447e9a6bff2ed720 Mon Sep 17 00:00:00 2001 From: Thomas Wang Date: Thu, 14 Feb 2019 22:09:24 -0800 Subject: [PATCH 2/7] address @kristw's comments --- .../components/controls/VizTypeControl.jsx | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/superset/assets/src/explore/components/controls/VizTypeControl.jsx b/superset/assets/src/explore/components/controls/VizTypeControl.jsx index 0f962e1eb4d1..f3ad721d6079 100644 --- a/superset/assets/src/explore/components/controls/VizTypeControl.jsx +++ b/superset/assets/src/explore/components/controls/VizTypeControl.jsx @@ -23,6 +23,7 @@ import { Tooltip } from 'react-bootstrap'; import { t } from '@superset-ui/translation'; import { getChartMetadataRegistry } from '@superset-ui/chart'; +import { SupersetClient } from '@superset-ui/connection'; import ControlHeader from '../ControlHeader'; import './VizTypeControl.css'; @@ -71,9 +72,10 @@ export default class VizTypeControl extends React.PureComponent { } } componentDidMount() { - $.get("/superset/viz_type_stats", (data) => { - this.setState({ vizTypeStats: data.this_user.concat(data.overall) }); - this.forceUpdate(); + SupersetClient.get({ + endpoint: "/superset/viz_type_stats", + }).then(({ json }) => { + this.setState({ vizTypeStats: json.this_user.concat(json.overall) }); }); } renderItem(entry) { @@ -96,32 +98,35 @@ export default class VizTypeControl extends React.PureComponent { ); } - getVizTypeByKey(types, key) { + _buildVizTypeLookup(types) { + let lookup = new Map() for (var i = 0; i < types.length; i++) { - if (types[i].key == key) return types[i]; + lookup.set(types[i].key, types[i]); } + return lookup } - sortVizTypes(types) { - var sorted = []; - var loaded_keys = new Set(); + _sortVizTypes(types) { + let sorted = []; + let loadedKeys = new Set(); + let vizTypeLookup = this._buildVizTypeLookup(types); // Sort based on existing visualization type usages statistics for (var i = 0; i < this.state.vizTypeStats.length; i++) { - var key = this.state.vizTypeStats[i].viz_type; - if (loaded_keys.has(key)) continue; - var t = this.getVizTypeByKey(types, key); + let key = this.state.vizTypeStats[i].viz_type; + if (loadedKeys.has(key)) continue; + let t = vizTypeLookup.get(key); if (typeof t !== 'undefined') { sorted.push(t); - loaded_keys.add(key); + loadedKeys.add(key); } } // For visualization types that do not have any statistics, apply the // original order for (var i = 0; i < types.length; i++) { - var t = types[i]; - var key = t['key']; - if (! loaded_keys.has(key)) { + let t = types[i]; + let key = t['key']; + if (! loadedKeys.has(key)) { sorted.push(t); - loaded_keys.add(key); + loadedKeys.add(key); } } return sorted; @@ -131,7 +136,7 @@ export default class VizTypeControl extends React.PureComponent { const { value } = this.props; const registry = getChartMetadataRegistry(); - const types = this.sortVizTypes(registry.entries()); + const types = this._sortVizTypes(registry.entries()); const filteredTypes = filter.length > 0 ? types.filter(type => type.value.name.toLowerCase().includes(filter)) : types; From f08045fa08631dacc96e265944970416f5e8d11f Mon Sep 17 00:00:00 2001 From: Thomas Wang Date: Tue, 19 Feb 2019 11:04:48 -0800 Subject: [PATCH 3/7] per @mistercrunch's comment to use a static list for now --- .../components/controls/VizTypeControl.jsx | 34 +++++++------ superset/views/core.py | 50 +------------------ 2 files changed, 20 insertions(+), 64 deletions(-) diff --git a/superset/assets/src/explore/components/controls/VizTypeControl.jsx b/superset/assets/src/explore/components/controls/VizTypeControl.jsx index f3ad721d6079..a35adaa29fb1 100644 --- a/superset/assets/src/explore/components/controls/VizTypeControl.jsx +++ b/superset/assets/src/explore/components/controls/VizTypeControl.jsx @@ -23,7 +23,6 @@ import { Tooltip } from 'react-bootstrap'; import { t } from '@superset-ui/translation'; import { getChartMetadataRegistry } from '@superset-ui/chart'; -import { SupersetClient } from '@superset-ui/connection'; import ControlHeader from '../ControlHeader'; import './VizTypeControl.css'; @@ -46,7 +45,6 @@ export default class VizTypeControl extends React.PureComponent { this.state = { showModal: false, filter: '', - vizTypeStats: '', }; this.toggleModal = this.toggleModal.bind(this); this.changeSearch = this.changeSearch.bind(this); @@ -71,13 +69,6 @@ export default class VizTypeControl extends React.PureComponent { this.searchRef.focus(); } } - componentDidMount() { - SupersetClient.get({ - endpoint: "/superset/viz_type_stats", - }).then(({ json }) => { - this.setState({ vizTypeStats: json.this_user.concat(json.overall) }); - }); - } renderItem(entry) { const { value } = this.props; const { key, value: type } = entry; @@ -106,21 +97,33 @@ export default class VizTypeControl extends React.PureComponent { return lookup } _sortVizTypes(types) { + const defaultOrder = [ + 'line', 'big_number', 'table', 'filter_box', 'dist_bar', 'area', 'bar', + 'deck_polygon', 'pie', 'time_table', 'pivot_table', 'histogram', + 'big_number_total', 'deck_scatter', 'deck_hex', 'time_pivot', 'deck_arc', + 'heatmap', 'deck_grid', 'dual_line', 'deck_screengrid', 'line_multi', + 'treemap', 'box_plot', 'separator', 'sunburst', 'sankey', 'word_cloud', + 'mapbox', 'kepler', 'cal_heatmap', 'rose', 'bubble', 'deck_geojson', + 'horizon', 'markup', 'deck_multi', 'compare', 'partition', 'event_flow', + 'deck_path', 'directed_force', 'world_map', 'paired_ttest', 'para', + 'iframe', 'country_map', + ] + let sorted = []; let loadedKeys = new Set(); let vizTypeLookup = this._buildVizTypeLookup(types); - // Sort based on existing visualization type usages statistics - for (var i = 0; i < this.state.vizTypeStats.length; i++) { - let key = this.state.vizTypeStats[i].viz_type; - if (loadedKeys.has(key)) continue; + + // Sort based on the `defualtOrder` + for (var i = 0; i < defaultOrder.length; i++) { + let key = defaultOrder[i]; let t = vizTypeLookup.get(key); if (typeof t !== 'undefined') { sorted.push(t); loadedKeys.add(key); } } - // For visualization types that do not have any statistics, apply the - // original order + + // Load the rest of Viz Types not mandated by the `defualtOrder` for (var i = 0; i < types.length; i++) { let t = types[i]; let key = t['key']; @@ -129,6 +132,7 @@ export default class VizTypeControl extends React.PureComponent { loadedKeys.add(key); } } + return sorted; } render() { diff --git a/superset/views/core.py b/superset/views/core.py index 58a5e313d8b0..fa704989d89f 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -36,7 +36,7 @@ import pandas as pd import simplejson as json import sqlalchemy as sqla -from sqlalchemy import and_, create_engine, MetaData, or_, update, func, desc +from sqlalchemy import and_, create_engine, MetaData, or_, update from sqlalchemy.engine.url import make_url from sqlalchemy.exc import IntegrityError from werkzeug.routing import BaseConverter @@ -2825,54 +2825,6 @@ def sqllab(self): bootstrap_data=json.dumps(d, default=utils.json_iso_dttm_ser), ) - @has_access - @expose('/viz_type_stats', methods=['GET']) - @expose('/viz_type_stats//', methods=['GET']) - def viz_type_stats(self, user_id=None): - """ - This endpoint provides the statistics of the visualization type usage. - """ - if not user_id: - user_id = g.user.id - - key = self._get_viz_type_stats_cache_key(user_id) - payload = cache.get(key) - if payload is not None: - payload['served_from'] = 'cache' - else: - payload = { - 'served_from': 'live', - 'this_user': self._get_viz_type_stats(user_id=user_id), - 'overall': self._get_viz_type_stats(), - } - cache.set(key, payload, 7 * 24 * 60 * 60) # cache for 7 days - return json_success( - json.dumps(payload, default=utils.json_int_dttm_ser) - ) - - def _get_viz_type_stats_cache_key(self, user_id): - """ - This function returns the key for caching the result of visualization - type usage statistics of this user. - """ - return f"viz_type_stats_{user_id}" - - def _get_viz_type_stats(self, user_id=None): - """ - This function returns the statistics of the visualization type usage of - the `user_id`. If `user_id` is not provided, this function returns the - same statistics across all users. - """ - query = db.session.query( - models.Slice.viz_type, - func.count().label("cnt"), - ) - if user_id is not None: - query = query.filter(models.Slice.created_by_fk == user_id) - query = query.group_by(models.Slice.viz_type) - query = query.order_by(desc("cnt")) - return query.all() - @api @handle_api_exception @has_access_api From 66a74756830e64edf79a2616a6b3479b82c6488b Mon Sep 17 00:00:00 2001 From: Thomas Wang Date: Wed, 27 Feb 2019 10:46:40 -0800 Subject: [PATCH 4/7] fix javascript test errors --- .../components/controls/VizTypeControl.jsx | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/superset/assets/src/explore/components/controls/VizTypeControl.jsx b/superset/assets/src/explore/components/controls/VizTypeControl.jsx index a35adaa29fb1..c7961ac5951d 100644 --- a/superset/assets/src/explore/components/controls/VizTypeControl.jsx +++ b/superset/assets/src/explore/components/controls/VizTypeControl.jsx @@ -89,14 +89,14 @@ export default class VizTypeControl extends React.PureComponent { ); } - _buildVizTypeLookup(types) { - let lookup = new Map() - for (var i = 0; i < types.length; i++) { + buildVizTypeLookup(types) { + const lookup = new Map(); + for (let i = 0; i < types.length; i++) { lookup.set(types[i].key, types[i]); } - return lookup + return lookup; } - _sortVizTypes(types) { + sortVizTypes(types) { const defaultOrder = [ 'line', 'big_number', 'table', 'filter_box', 'dist_bar', 'area', 'bar', 'deck_polygon', 'pie', 'time_table', 'pivot_table', 'histogram', @@ -107,16 +107,16 @@ export default class VizTypeControl extends React.PureComponent { 'horizon', 'markup', 'deck_multi', 'compare', 'partition', 'event_flow', 'deck_path', 'directed_force', 'world_map', 'paired_ttest', 'para', 'iframe', 'country_map', - ] + ]; - let sorted = []; - let loadedKeys = new Set(); - let vizTypeLookup = this._buildVizTypeLookup(types); + const sorted = []; + const loadedKeys = new Set(); + const vizTypeLookup = this.buildVizTypeLookup(types); // Sort based on the `defualtOrder` - for (var i = 0; i < defaultOrder.length; i++) { - let key = defaultOrder[i]; - let t = vizTypeLookup.get(key); + for (let i = 0; i < defaultOrder.length; i++) { + const key = defaultOrder[i]; + const t = vizTypeLookup.get(key); if (typeof t !== 'undefined') { sorted.push(t); loadedKeys.add(key); @@ -124,10 +124,10 @@ export default class VizTypeControl extends React.PureComponent { } // Load the rest of Viz Types not mandated by the `defualtOrder` - for (var i = 0; i < types.length; i++) { - let t = types[i]; - let key = t['key']; - if (! loadedKeys.has(key)) { + for (let i = 0; i < types.length; i++) { + const t = types[i]; + const key = t.key; + if (!loadedKeys.has(key)) { sorted.push(t); loadedKeys.add(key); } @@ -140,7 +140,7 @@ export default class VizTypeControl extends React.PureComponent { const { value } = this.props; const registry = getChartMetadataRegistry(); - const types = this._sortVizTypes(registry.entries()); + const types = this.sortVizTypes(registry.entries()); const filteredTypes = filter.length > 0 ? types.filter(type => type.value.name.toLowerCase().includes(filter)) : types; From 6412e015b624da8996b71300b2e9ad6b24703924 Mon Sep 17 00:00:00 2001 From: Thomas Wang Date: Wed, 27 Feb 2019 13:13:28 -0800 Subject: [PATCH 5/7] fix lint errors --- .../components/controls/VizTypeControl.jsx | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/superset/assets/src/explore/components/controls/VizTypeControl.jsx b/superset/assets/src/explore/components/controls/VizTypeControl.jsx index c7961ac5951d..37bc39fa46e4 100644 --- a/superset/assets/src/explore/components/controls/VizTypeControl.jsx +++ b/superset/assets/src/explore/components/controls/VizTypeControl.jsx @@ -69,26 +69,6 @@ export default class VizTypeControl extends React.PureComponent { this.searchRef.focus(); } } - renderItem(entry) { - const { value } = this.props; - const { key, value: type } = entry; - const isSelected = key === value; - return ( -
- {type.name} -
- {type.name} -
-
); - } buildVizTypeLookup(types) { const lookup = new Map(); for (let i = 0; i < types.length; i++) { @@ -116,25 +96,45 @@ export default class VizTypeControl extends React.PureComponent { // Sort based on the `defualtOrder` for (let i = 0; i < defaultOrder.length; i++) { const key = defaultOrder[i]; - const t = vizTypeLookup.get(key); - if (typeof t !== 'undefined') { - sorted.push(t); + const vizType = vizTypeLookup.get(key); + if (typeof vizType !== 'undefined') { + sorted.push(vizType); loadedKeys.add(key); } } // Load the rest of Viz Types not mandated by the `defualtOrder` for (let i = 0; i < types.length; i++) { - const t = types[i]; - const key = t.key; + const vizType = types[i]; + const key = vizType.key; if (!loadedKeys.has(key)) { - sorted.push(t); + sorted.push(vizType); loadedKeys.add(key); } } return sorted; } + renderItem(entry) { + const { value } = this.props; + const { key, value: type } = entry; + const isSelected = key === value; + return ( +
+ {type.name} +
+ {type.name} +
+
); + } render() { const { filter, showModal } = this.state; const { value } = this.props; From 8a825f760af803eb890ac4dd88713e496de414c5 Mon Sep 17 00:00:00 2001 From: Thomas Wang Date: Wed, 27 Feb 2019 14:45:30 -0800 Subject: [PATCH 6/7] per @betodealmeida's comments --- .../components/controls/VizTypeControl.jsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/superset/assets/src/explore/components/controls/VizTypeControl.jsx b/superset/assets/src/explore/components/controls/VizTypeControl.jsx index 37bc39fa46e4..5f467196ec6c 100644 --- a/superset/assets/src/explore/components/controls/VizTypeControl.jsx +++ b/superset/assets/src/explore/components/controls/VizTypeControl.jsx @@ -71,6 +71,10 @@ export default class VizTypeControl extends React.PureComponent { } buildVizTypeLookup(types) { const lookup = new Map(); + types.forEach((type) => { + lookup.set(type.key, type); + }); + for (let i = 0; i < types.length; i++) { lookup.set(types[i].key, types[i]); } @@ -93,25 +97,22 @@ export default class VizTypeControl extends React.PureComponent { const loadedKeys = new Set(); const vizTypeLookup = this.buildVizTypeLookup(types); - // Sort based on the `defualtOrder` - for (let i = 0; i < defaultOrder.length; i++) { - const key = defaultOrder[i]; + // Sort based on the `defaultOrder` + defaultOrder.forEach((key) => { const vizType = vizTypeLookup.get(key); if (typeof vizType !== 'undefined') { sorted.push(vizType); loadedKeys.add(key); } - } + }); // Load the rest of Viz Types not mandated by the `defualtOrder` - for (let i = 0; i < types.length; i++) { - const vizType = types[i]; - const key = vizType.key; - if (!loadedKeys.has(key)) { + types.forEach((vizType) => { + if (!loadedKeys.has(vizType.key)) { sorted.push(vizType); - loadedKeys.add(key); + loadedKeys.add(vizType.key); } - } + }); return sorted; } From 995d50ea7d24a59db1d54fb5788d3697cbf88fca Mon Sep 17 00:00:00 2001 From: Thomas Wang Date: Wed, 27 Feb 2019 22:13:34 -0800 Subject: [PATCH 7/7] remove unnecessary loop --- .../assets/src/explore/components/controls/VizTypeControl.jsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/superset/assets/src/explore/components/controls/VizTypeControl.jsx b/superset/assets/src/explore/components/controls/VizTypeControl.jsx index 5f467196ec6c..0264b1c7a7ff 100644 --- a/superset/assets/src/explore/components/controls/VizTypeControl.jsx +++ b/superset/assets/src/explore/components/controls/VizTypeControl.jsx @@ -74,10 +74,6 @@ export default class VizTypeControl extends React.PureComponent { types.forEach((type) => { lookup.set(type.key, type); }); - - for (let i = 0; i < types.length; i++) { - lookup.set(types[i].key, types[i]); - } return lookup; } sortVizTypes(types) {