Skip to content

Commit

Permalink
multi chart plugin has multicontrol to select common columns between …
Browse files Browse the repository at this point in the history
…layers
  • Loading branch information
NurramoX committed Feb 15, 2024
1 parent cc52358 commit 4649e47
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 4 deletions.
1 change: 1 addition & 0 deletions .flaskenv
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
#
FLASK_APP="superset.app:create_app()"
FLASK_DEBUG=true
MAPBOX_API_KEY=""
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@
*/
import { t, validateNonEmpty } from '@superset-ui/core';
import { sections } from '@superset-ui/chart-controls';
import { viewport, mapboxStyle } from '../utilities/Shared_DeckGL';
import {
viewport,
mapboxStyle,
legendPosition,
legendFormat,
} from '../utilities/Shared_DeckGL';

export default {
controlPanelSections: [
Expand All @@ -32,11 +37,11 @@ export default {
{
name: 'deck_slices',
config: {
type: 'SelectAsyncControl',
type: 'MultiSelectAsyncControl',
multi: true,
label: t('deck.gl charts'),
validators: [validateNonEmpty],
default: [],
default: [{}],
description: t(
'Pick a set of deck.gl charts to layer on top of one another',
),
Expand All @@ -50,6 +55,7 @@ export default {
}
return data.result.map(o => ({
value: o.id,
datasource: o.datasource_id,
label: o.slice_name,
}));
},
Expand All @@ -59,6 +65,10 @@ export default {
],
],
},
{
label: t('Geom Color'),
controlSetRows: [[legendPosition], [legendFormat], ['color_scheme']],
},
{
label: t('Query'),
expanded: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/**
* 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 React, { useEffect, useState } from 'react';
import { SupersetClient, t } from '@superset-ui/core';
import ControlHeader from 'src/explore/components/ControlHeader';
import { Select } from 'src/components';
import { SelectOptionsType, SelectProps } from 'src/components/Select/types';
import { LabeledValue, SelectValue } from 'antd/lib/select';
import withToasts from 'src/components/MessageToasts/withToasts';
import { getClientErrorObject } from 'src/utils/getClientErrorObject';

type SelectAsyncProps = Omit<SelectProps, 'options' | 'ariaLabel' | 'onChange'>;

interface SelectAsyncControlProps extends SelectAsyncProps {
addDangerToast: (error: string) => void;
ariaLabel?: string;
dataEndpoint: string;
default?: SelectValue;
mutator?: (response: Record<string, any>) => SelectOptionsType;
multi?: boolean;
onChange: (val: SelectValue) => void;
// ControlHeader related props
description?: string;
hovered?: boolean;
label?: string;
}

function isLabeledValue(arg: any): arg is LabeledValue {
return arg.value !== undefined;
}

const SelectAsyncControl = ({
addDangerToast,
allowClear = true,
ariaLabel,
dataEndpoint,
multi = true,
mutator,
onChange,
placeholder,
value,
...props
}: SelectAsyncControlProps) => {
const [options, setOptions] = useState<SelectOptionsType>([]);
const [optionMap, setOptionMap] = useState<Map<number, number>>(new Map());
const [commonColumns, setCommonColumns] = useState<SelectOptionsType>([]);

const handleOnChange = (val: SelectValue) => {
let onChangeVal = val;
if (Array.isArray(val)) {
onChangeVal = val.map(v => (isLabeledValue(v) ? v.value : v));
}
if (isLabeledValue(val)) {
onChangeVal = val.value;
}
onChange([{ ...value[0], val: onChangeVal }]);
};

const handleOnColChange = (val: SelectValue) => {
onChange([{ val: value[0].val.slice(), col: val }]);
};

const getDeckSlices = () => {
if (value === undefined || value.length < 1) return;
const currentValue =
value[0].val ||
(props.default[0].val !== undefined ? props.default[0].val : undefined);

// safety check - the value is intended to be undefined but null was used
if (currentValue === null && !options.find(o => o.value[0].val === null)) {
return undefined;

Check failure on line 87 in superset-frontend/src/explore/components/controls/MultiSelectAsyncControl/index.tsx

View workflow job for this annotation

GitHub Actions / frontend-build

Arrow function expected no return value
}
return currentValue;

Check failure on line 89 in superset-frontend/src/explore/components/controls/MultiSelectAsyncControl/index.tsx

View workflow job for this annotation

GitHub Actions / frontend-build

Arrow function expected no return value
};

const getCol = () => {
if (value === undefined || value.length < 1) return;
const currentValue =
value[0].col ||
(props.default[0].col !== undefined ? props.default[0].col : undefined);

// safety check - the value is intended to be undefined but null was used
if (currentValue === null && !options.find(o => o.value[0].col === null)) {
return undefined;

Check failure on line 100 in superset-frontend/src/explore/components/controls/MultiSelectAsyncControl/index.tsx

View workflow job for this annotation

GitHub Actions / frontend-build

Arrow function expected no return value
}
return currentValue;

Check failure on line 102 in superset-frontend/src/explore/components/controls/MultiSelectAsyncControl/index.tsx

View workflow job for this annotation

GitHub Actions / frontend-build

Arrow function expected no return value
};

useEffect(() => {
const onError = (response: Response) =>
getClientErrorObject(response).then(e => {
const { error } = e;
addDangerToast(t('Error while fetching data: %s', error));
});
const loadOptions = () =>
SupersetClient.get({
endpoint: dataEndpoint,
})
.then(response => {
const data = mutator ? mutator(response.json) : response.json.result;
const newMap = new Map();
data.forEach(
({ value, datasource }: { value: number; datasource: number }) => {
newMap.set(value, datasource);
},
);
setOptionMap(newMap);
setOptions(data);
})
.catch(onError);
loadOptions();
}, [addDangerToast, dataEndpoint, mutator]);

function handleBlur() {
const onError = (response: Response) =>
getClientErrorObject(response).then(e => {
const { error } = e;
addDangerToast(t('Error while fetching data: %s', error));
});
const loadOptions = () => {
if (value === undefined || value.length < 1) return;
const currentValue =
value[0].val ||
(props.default[0] !== undefined ? props.default[0] : undefined);
let sanitizedValue: number[] = [];
if (Array.isArray(currentValue)) {
// @ts-ignore
sanitizedValue = currentValue.map(v =>
isLabeledValue(v) ? v.value : v,
);
}
Promise.all(
sanitizedValue.map(id =>
SupersetClient.get({
endpoint: `/api/v1/dataset/${optionMap.get(id)}`,
}),
),
)
.then(responses => {

Check failure on line 155 in superset-frontend/src/explore/components/controls/MultiSelectAsyncControl/index.tsx

View workflow job for this annotation

GitHub Actions / frontend-build

Expected to return a value at the end of arrow function
const datasets = responses.map(response =>
response.json.result.columns.map(column => column.column_name),
);
if (!datasets.length) return [];

// Find the shortest array
const [shortestArray] = datasets.sort((a, b) => a.length - b.length);

// Find intersection
const intersection = shortestArray.filter(item =>
datasets.every(array => array.includes(item)),
);
let i = 0;

Check failure on line 168 in superset-frontend/src/explore/components/controls/MultiSelectAsyncControl/index.tsx

View workflow job for this annotation

GitHub Actions / frontend-build

'i' is never reassigned. Use 'const' instead
setCommonColumns(
intersection.map(val => ({
value: val,
})),
);
// return intersection;
})
.catch(onError);
};
loadOptions();
}

return (
<>
<Select
allowClear={allowClear}
ariaLabel={ariaLabel || t('Select ...')}
value={getDeckSlices()}
header={<ControlHeader {...props} />}
onBlur={handleBlur}
mode={multi ? 'multiple' : 'single'}
onChange={handleOnChange}
options={options}
placeholder={placeholder}
/>

<Select
value={getCol()}
options={commonColumns}
onChange={handleOnColChange}
header={<ControlHeader label="Select Col" />}
/>
</>
);
};

export default withToasts(SelectAsyncControl);
2 changes: 2 additions & 0 deletions superset-frontend/src/explore/components/controls/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import DndColumnSelectControl, {
import XAxisSortControl from './XAxisSortControl';
import CurrencyControl from './CurrencyControl';
import ColumnConfigControl from './ColumnConfigControl';
import MultiSelectAsyncControl from './MultiSelectAsyncControl';

const controlMap = {
AnnotationLayerControl,
Expand All @@ -67,6 +68,7 @@ const controlMap = {
FixedOrMetricControl,
HiddenControl,
SelectAsyncControl,
MultiSelectAsyncControl,
SelectControl,
SliderControl,
SpatialControl,
Expand Down
2 changes: 1 addition & 1 deletion superset/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -1976,7 +1976,7 @@ def get_data(self, df: pd.DataFrame) -> VizData:
from superset import db
from superset.models.slice import Slice

slice_ids = self.form_data.get("deck_slices")
slice_ids = self.form_data.get("deck_slices")[0]['val']
slices = db.session.query(Slice).filter(Slice.id.in_(slice_ids)).all()
return {
"mapboxApiKey": config["MAPBOX_API_KEY"],
Expand Down

0 comments on commit 4649e47

Please sign in to comment.