Skip to content

Commit

Permalink
Merge pull request #1 from askstylo/drilldown
Browse files Browse the repository at this point in the history
Drilldown
  • Loading branch information
EvilDrW committed Sep 25, 2021
2 parents 484af3a + c0fb334 commit e768cd3
Show file tree
Hide file tree
Showing 15 changed files with 250 additions and 42 deletions.
5 changes: 3 additions & 2 deletions packages/superset-ui-chart-controls/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* under the License.
*/
import React, { ReactNode, ReactText, ReactElement } from 'react';
import { QueryFormData, DatasourceType, Metric, JsonValue, Column } from '@superset-ui/core';
import { QueryFormData, DatasourceType, Metric, JsonValue, Column, JsonObject } from '@superset-ui/core';
import sharedControls from './shared-controls';
import sharedControlComponents from './shared-controls/components';

Expand Down Expand Up @@ -62,6 +62,7 @@ export interface ControlPanelState {
form_data: QueryFormData;
datasource: DatasourceMeta | null;
controls: ControlStateMapping;
dataMask: JsonObject;
}

/**
Expand Down Expand Up @@ -167,7 +168,7 @@ export type TabOverride = 'data' | 'customize' | boolean;
* show a warning based on the value of another component. It's also possible to bind
* arbitrary data from the redux store to the component this way.
* - tabOverride: set to 'data' if you want to force a renderTrigger to show up on the `Data`
tab, or 'customize' if you want it to show up on that tam. Otherwise sections with ALL
tab, or 'customize' if you want it to show up on that tam. Otherwise sections with ALL
`renderTrigger: true` components will show up on the `Customize` tab.
* - visibility: a function that uses control panel props to check whether a control should
* be visibile.
Expand Down
92 changes: 92 additions & 0 deletions packages/superset-ui-core/src/query/DrillDown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* 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.
*/
import { QueryObjectFilterClause, DrillDownType } from "./types";
import { ensureIsArray } from "../utils";

export default class DrillDown {
static fromHierarchy(
hierarchy: string[]
): DrillDownType {
const _hierarchy = ensureIsArray(hierarchy)
return {
hierarchy: _hierarchy,
currentIdx: _hierarchy.length > 0 ? 0 : -1,
filters: [],
};
}

static drillDown(
value: DrillDownType,
selectValue: string,
): DrillDownType {
const idx = value.currentIdx;
const len = value.hierarchy.length;

if (idx + 1 >= len) {
return {
hierarchy: value.hierarchy,
currentIdx: 0,
filters: [],
}
}
return {
hierarchy: value.hierarchy,
currentIdx: idx + 1,
filters: value.filters.concat({
col: value.hierarchy[idx],
op: 'IN',
val: [selectValue],
})
}
}

static rollUp(
value: DrillDownType,
): DrillDownType {
const idx = value.currentIdx;
const len = value.hierarchy.length;
return {
hierarchy: value.hierarchy,
currentIdx: idx - 1 < 0 ? len - 1 : idx - 1,
filters: value.filters.slice(0, -1),
}
}

static getColumn(
value: DrillDownType | undefined | null,
hierarchy: string[],
): string {
if (value) {
return value.hierarchy[value.currentIdx];
}
const val = DrillDown.fromHierarchy(hierarchy);
return val.hierarchy[val.currentIdx];
}

static getFilters(
value: DrillDownType | undefined | null,
hierarchy: string[],
): QueryObjectFilterClause[] {
if (value) {
return value.filters;
}
const val = DrillDown.fromHierarchy(hierarchy);
return val.filters;
}
}
1 change: 1 addition & 0 deletions packages/superset-ui-core/src/query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export { default as convertFilter } from './convertFilter';
export { default as extractTimegrain } from './extractTimegrain';
export { default as getMetricLabel } from './getMetricLabel';
export { default as DatasourceKey } from './DatasourceKey';
export { default as DrillDown } from './DrillDown';
export { default as normalizeOrderBy } from './normalizeOrderBy';

export * from './types/AnnotationLayer';
Expand Down
6 changes: 6 additions & 0 deletions packages/superset-ui-core/src/query/types/QueryFormData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ export type ExtraFormDataOverride = ExtraFormDataOverrideRegular & ExtraFormData

export type ExtraFormData = ExtraFormDataAppend & ExtraFormDataOverride;

export type DrillDownType = {
hierarchy: string[];
currentIdx: number;
filters: QueryObjectFilterClause[];
}

// Type signature for formData shared by all viz types
// It will be gradually filled out as we build out the query object

Expand Down
1 change: 1 addition & 0 deletions packages/superset-ui-core/src/utils/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export enum FeatureFlag {
ESCAPE_MARKDOWN_HTML = 'ESCAPE_MARKDOWN_HTML',
DASHBOARD_NATIVE_FILTERS = 'DASHBOARD_NATIVE_FILTERS',
DASHBOARD_CROSS_FILTERS = 'DASHBOARD_CROSS_FILTERS',
DASHBOARD_DRILL_DOWN = 'DASHBOARD_DRILL_DOWN',
DASHBOARD_NATIVE_FILTERS_SET = 'DASHBOARD_NATIVE_FILTERS_SET',
DASHBOARD_FILTERS_EXPERIMENTAL = 'DASHBOARD_FILTERS_EXPERIMENTAL',
VERSIONED_EXPORT = 'VERSIONED_EXPORT',
Expand Down
17 changes: 16 additions & 1 deletion plugins/legacy-preset-chart-nvd3/src/DistBar/controlPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { ensureIsArray, t, validateNonEmpty } from '@superset-ui/core';
import { ensureIsArray, t, validateNonEmpty, FeatureFlag, isFeatureEnabled } from '@superset-ui/core';
import {
ColumnMeta,
ControlPanelConfig,
Expand Down Expand Up @@ -72,6 +72,21 @@ const config: ControlPanelConfig = {
},
},
],
isFeatureEnabled(FeatureFlag.DASHBOARD_DRILL_DOWN) ? [
{
name: 'drillDown',
config: {
type: 'DrillDownControl',
default: false,
label: t('Enable drill down'),
description: t('Columns as hierarchy.'),
mapStateToProps: ({ form_data }) => ({
chartId: form_data?.slice_id || 0,
columns: form_data.groupby,
})
}
}
] : []
],
},
{
Expand Down
11 changes: 9 additions & 2 deletions plugins/legacy-preset-chart-nvd3/src/DistBar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
import { t, buildQueryContext, ChartMetadata, ChartPlugin, DrillDown } from '@superset-ui/core';
import transformProps from '../transformProps';
import thumbnail from './images/thumbnail.png';
import example1 from './images/Bar_Chart.jpg';
Expand Down Expand Up @@ -57,7 +57,14 @@ export default class DistBarChartPlugin extends ChartPlugin {
super({
loadChart: () => import('../ReactNVD3'),
metadata,
transformProps,
transformProps: (chartProps) => {
const { formData, ownState } = chartProps;
const { drillDown } = formData;
if (drillDown && ownState?.drilldown) {
chartProps.formData.groupby = [DrillDown.getColumn(ownState.drilldown, [])];
}
return transformProps(chartProps);
},
controlPanel,
});
}
Expand Down
23 changes: 23 additions & 0 deletions plugins/legacy-preset-chart-nvd3/src/NVD3Vis.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
getNumberFormatter,
NumberFormats,
CategoricalColorNamespace,
DrillDown
} from '@superset-ui/core';

import 'nvd3-fork/build/nv.d3.css';
Expand Down Expand Up @@ -262,6 +263,7 @@ function nvd3Vis(element, props) {
colorScheme,
comparisonType,
contribution,
drillDown,
entity,
isBarStacked,
isDonut,
Expand All @@ -276,10 +278,12 @@ function nvd3Vis(element, props) {
onBrushEnd = NOOP,
onError = NOOP,
orderBars,
ownState,
pieLabelType,
rangeLabels,
ranges,
reduceXTicks = false,
setDataMask,
showBarValue,
showBrush,
showControls,
Expand Down Expand Up @@ -424,6 +428,25 @@ function nvd3Vis(element, props) {
width = computeBarChartWidth(data, isBarStacked, maxWidth);
}
chart.width(width);

// dispatch the drilldown event
chart.multibar.dispatch.on('elementClick', (e) => {
if (drillDown && ownState?.drilldown) { // need the formdata stuff here
const value = e.data.x;
const drilldown = DrillDown.drillDown(ownState?.drilldown, value)
setDataMask({
extraFormData: {
filters: drilldown.filters,
},
filterState: {
value: value && drilldown.filters.length > 0 ? [value] : null,
},
ownState: {
drilldown: drilldown,
}
});
}
});
break;

case 'pie':
Expand Down
8 changes: 6 additions & 2 deletions plugins/legacy-preset-chart-nvd3/src/transformProps.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ const grabD3Format = (datasource, targetMetric) => {
};

export default function transformProps(chartProps) {
const { width, height, annotationData, datasource, formData, hooks, queriesData } = chartProps;
const { width, height, annotationData, datasource, formData, hooks, ownState, queriesData } = chartProps;

const { onAddFilter = NOOP, onError = NOOP } = hooks;
const { onAddFilter = NOOP, onError = NOOP, setDataMask } = hooks;

const {
annotationLayers,
Expand All @@ -48,6 +48,7 @@ export default function transformProps(chartProps) {
comparisonType,
contribution,
donut,
drillDown,
entity,
labelsOutside,
leftMargin,
Expand Down Expand Up @@ -134,6 +135,7 @@ export default function transformProps(chartProps) {
colorScheme,
comparisonType,
contribution,
drillDown,
entity,
isBarStacked: barStacked,
isDonut: donut,
Expand All @@ -152,11 +154,13 @@ export default function transformProps(chartProps) {
}
: undefined,
onError,
ownState,
orderBars,
pieLabelType,
rangeLabels,
ranges,
reduceXTicks,
setDataMask,
showBarValue,
showBrush,
showControls,
Expand Down
63 changes: 40 additions & 23 deletions plugins/plugin-chart-echarts/src/Pie/EchartsPie.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,49 +20,66 @@ import React, { useCallback } from 'react';
import { PieChartTransformedProps } from './types';
import Echart from '../components/Echart';
import { EventHandlers } from '../types';
import { DataMask, DrillDown } from "@superset-ui/core";

export default function EchartsPie({
formData,
height,
width,
echartOptions,
setDataMask,
labelMap,
groupby,
selectedValues,
formData,
ownState,
}: PieChartTransformedProps) {
const handleChange = useCallback(
(values: string[]) => {
if (!formData.emitFilter) {
if (!formData.emitFilter && !formData.drillDown) {
return;
}

const groupbyValues = values.map(value => labelMap[value]);

setDataMask({
extraFormData: {
filters:
values.length === 0
? []
: groupby.map((col, idx) => {
const val = groupbyValues.map(v => v[idx]);
if (val === null || val === undefined)
let dataMask: DataMask = {};
if (formData.emitFilter) {
dataMask = {
extraFormData: {
filters:
values.length === 0
? []
: groupby.map((col, idx) => {
const val = groupbyValues.map(v => v[idx]);
return {
col,
op: 'IS NULL',
op: 'IN',
val: val as (string | number | boolean)[],
};
return {
col,
op: 'IN',
val: val as (string | number | boolean)[],
};
}),
},
filterState: {
value: groupbyValues.length ? groupbyValues : null,
selectedValues: values.length ? values : null,
},
});
}),
},
filterState: {
value: groupbyValues.length ? groupbyValues : null,
selectedValues: values.length ? values : null,
},
}
}

if (formData.drillDown && ownState?.drilldown) {
const drilldown = DrillDown.drillDown(ownState?.drilldown, values[0])
dataMask = {
extraFormData: {
filters: drilldown.filters,
},
filterState: {
value: groupbyValues.length && drilldown.filters.length > 0 ? groupbyValues : null,
},
ownState: {
drilldown: drilldown,
}
}
}

setDataMask(dataMask);
},
[groupby, labelMap, setDataMask, selectedValues],
);
Expand Down
Loading

0 comments on commit e768cd3

Please sign in to comment.