From 30ecdf3e9fdf66bda26c4ef942f0f61e0fd54ea6 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Fri, 23 Sep 2022 14:28:00 -0400 Subject: [PATCH 01/24] added adhoc filter plugin files --- .../FiltersConfigForm/FiltersConfigForm.tsx | 1 + .../FiltersConfigForm/utils.ts | 6 + .../util/filterboxMigrationHelper.ts | 1 + .../Adhoc/AdhocFilterPlugin.stories.tsx | 62 ++++ .../Adhoc/AdhocFilterPlugin.test.tsx | 252 +++++++++++++ .../components/Adhoc/AdhocFilterPlugin.tsx | 335 ++++++++++++++++++ .../components/Adhoc/buildQuery.test.ts | 125 +++++++ .../filters/components/Adhoc/buildQuery.ts | 79 +++++ .../filters/components/Adhoc/controlPanel.ts | 157 ++++++++ .../components/Adhoc/images/thumbnail.png | Bin 0 -> 5658 bytes .../src/filters/components/Adhoc/index.ts | 44 +++ .../components/Adhoc/transformProps.ts | 68 ++++ .../src/filters/components/Adhoc/types.ts | 87 +++++ .../src/filters/components/index.ts | 1 + superset-frontend/src/filters/utils.ts | 38 ++ .../src/visualizations/presets/MainPreset.js | 2 + 16 files changed, 1258 insertions(+) create mode 100644 superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.stories.tsx create mode 100644 superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.test.tsx create mode 100644 superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx create mode 100644 superset-frontend/src/filters/components/Adhoc/buildQuery.test.ts create mode 100644 superset-frontend/src/filters/components/Adhoc/buildQuery.ts create mode 100644 superset-frontend/src/filters/components/Adhoc/controlPanel.ts create mode 100644 superset-frontend/src/filters/components/Adhoc/images/thumbnail.png create mode 100644 superset-frontend/src/filters/components/Adhoc/index.ts create mode 100644 superset-frontend/src/filters/components/Adhoc/transformProps.ts create mode 100644 superset-frontend/src/filters/components/Adhoc/types.ts diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx index 816bb112dd333..f1b3a5705b19f 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx @@ -303,6 +303,7 @@ const FILTERS_WITH_ADHOC_FILTERS = ['filter_select', 'filter_range']; // TODO: Rename the filter plugins and remove this mapping const FILTER_TYPE_NAME_MAPPING = { + [t('Adhoc filter')]: t('Adhoc'), [t('Select filter')]: t('Value'), [t('Range filter')]: t('Numerical range'), [t('Time filter')]: t('Time range'), diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/utils.ts b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/utils.ts index 8fdb3b0325997..538f5ce88bfb3 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/utils.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/utils.ts @@ -35,6 +35,12 @@ export const FILTER_SUPPORTED_TYPES = { GenericDataType.NUMERIC, GenericDataType.TEMPORAL, ], + filter_adhoc: [ + GenericDataType.BOOLEAN, + GenericDataType.STRING, + GenericDataType.NUMERIC, + GenericDataType.TEMPORAL, + ], filter_range: [GenericDataType.NUMERIC], }; diff --git a/superset-frontend/src/dashboard/util/filterboxMigrationHelper.ts b/superset-frontend/src/dashboard/util/filterboxMigrationHelper.ts index 018d67f29f17f..44356637682f7 100644 --- a/superset-frontend/src/dashboard/util/filterboxMigrationHelper.ts +++ b/superset-frontend/src/dashboard/util/filterboxMigrationHelper.ts @@ -96,6 +96,7 @@ enum FILTER_COMPONENT_FILTER_TYPES { FILTER_TIMEGRAIN = 'filter_timegrain', FILTER_TIMECOLUMN = 'filter_timecolumn', FILTER_SELECT = 'filter_select', + FILTER_ADHOC = 'filter_adhoc', FILTER_RANGE = 'filter_range', } diff --git a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.stories.tsx b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.stories.tsx new file mode 100644 index 0000000000000..20ef4e2801a51 --- /dev/null +++ b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.stories.tsx @@ -0,0 +1,62 @@ +/** + * 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 from 'react'; +import { action } from '@storybook/addon-actions'; +import { boolean, withKnobs } from '@storybook/addon-knobs'; +import { SuperChart, getChartTransformPropsRegistry } from '@superset-ui/core'; +import { mockQueryDataForCountries } from 'spec/fixtures/mockNativeFilters'; +import AdhocFilterPlugin from './index'; +import transformProps from './transformProps'; + +new AdhocFilterPlugin().configure({ key: 'filter_adhoc' }).register(); + +getChartTransformPropsRegistry().registerValue('filter_adhoc', transformProps); + +export default { + title: 'Filter Plugins', + decorators: [withKnobs], +}; + +export const Select = ({ + width, + height, +}: { + width: number; + height: number; +}) => ( + +); diff --git a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.test.tsx b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.test.tsx new file mode 100644 index 0000000000000..bf86782a76acf --- /dev/null +++ b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.test.tsx @@ -0,0 +1,252 @@ +/** + * 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 userEvent from '@testing-library/user-event'; +import { AppSection } from '@superset-ui/core'; +import React from 'react'; +import { render, screen } from 'spec/helpers/testing-library'; +import { NULL_STRING } from 'src/utils/common'; +import AdhocFilterPlugin from './AdhocFilterPlugin'; +import transformProps from './transformProps'; + +const selectMultipleProps = { + formData: { + sortAscending: true, + multiSelect: true, + enableEmptyFilter: true, + defaultToFirstItem: false, + inverseSelection: false, + searchAllOptions: false, + datasource: '3__table', + groupby: ['gender'], + adhocFilters: [], + extraFilters: [], + extraFormData: {}, + granularitySqla: 'ds', + metrics: ['count'], + rowLimit: 1000, + showSearch: true, + defaultValue: ['boy'], + timeRangeEndpoints: ['inclusive', 'exclusive'], + urlParams: {}, + vizType: 'filter_select', + inputRef: { current: null }, + }, + height: 20, + hooks: {}, + ownState: {}, + filterState: { value: ['boy'] }, + queriesData: [ + { + rowcount: 2, + colnames: ['gender'], + coltypes: [1], + data: [{ gender: 'boy' }, { gender: 'girl' }, { gender: null }], + applied_filters: [{ column: 'gender' }], + rejected_filters: [], + }, + ], + width: 220, + behaviors: ['NATIVE_FILTER'], + isRefreshing: false, + appSection: AppSection.DASHBOARD, +}; + +describe('AdhocFilterPlugin', () => { + const setDataMask = jest.fn(); + const getWrapper = (props = {}) => + render( + // @ts-ignore + , + ); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('Add multiple values with first render', () => { + getWrapper(); + expect(setDataMask).toHaveBeenCalledWith({ + extraFormData: {}, + filterState: { + value: ['boy'], + }, + }); + expect(setDataMask).toHaveBeenCalledWith({ + __cache: { + value: ['boy'], + }, + extraFormData: { + filters: [ + { + col: 'gender', + op: 'IN', + val: ['boy'], + }, + ], + }, + filterState: { + label: 'boy', + value: ['boy'], + }, + }); + userEvent.click(screen.getByRole('combobox')); + userEvent.click(screen.getByTitle('girl')); + expect(setDataMask).toHaveBeenCalledWith({ + __cache: { + value: ['boy'], + }, + extraFormData: { + filters: [ + { + col: 'gender', + op: 'IN', + val: ['boy', 'girl'], + }, + ], + }, + filterState: { + label: 'boy, girl', + value: ['boy', 'girl'], + }, + }); + }); + + it('Remove multiple values when required', () => { + getWrapper(); + userEvent.click(document.querySelector('[data-icon="close"]')!); + expect(setDataMask).toHaveBeenCalledWith({ + __cache: { + value: ['boy'], + }, + extraFormData: { + adhoc_filters: [ + { + clause: 'WHERE', + expressionType: 'SQL', + sqlExpression: '1 = 0', + }, + ], + }, + filterState: { + label: undefined, + value: null, + }, + }); + }); + + it('Remove multiple values when not required', () => { + getWrapper({ enableEmptyFilter: false }); + userEvent.click(document.querySelector('[data-icon="close"]')!); + expect(setDataMask).toHaveBeenCalledWith({ + __cache: { + value: ['boy'], + }, + extraFormData: {}, + filterState: { + label: undefined, + value: null, + }, + }); + }); + + it('Select single values with inverse', () => { + getWrapper({ multiSelect: false, inverseSelection: true }); + userEvent.click(screen.getByRole('combobox')); + userEvent.click(screen.getByTitle('girl')); + expect(setDataMask).toHaveBeenCalledWith({ + __cache: { + value: ['boy'], + }, + extraFormData: { + filters: [ + { + col: 'gender', + op: 'NOT IN', + val: ['girl'], + }, + ], + }, + filterState: { + label: 'girl (excluded)', + value: ['girl'], + }, + }); + }); + + it('Select single null (empty) value', () => { + getWrapper(); + userEvent.click(screen.getByRole('combobox')); + userEvent.click(screen.getByTitle(NULL_STRING)); + expect(setDataMask).toHaveBeenLastCalledWith({ + __cache: { + value: ['boy'], + }, + extraFormData: { + filters: [ + { + col: 'gender', + op: 'IN', + val: ['boy', null], + }, + ], + }, + filterState: { + label: `boy, ${NULL_STRING}`, + value: ['boy', null], + }, + }); + }); + + it('Add ownState with column types when search all options', () => { + getWrapper({ searchAllOptions: true, multiSelect: false }); + userEvent.click(screen.getByRole('combobox')); + userEvent.click(screen.getByTitle('girl')); + expect(setDataMask).toHaveBeenCalledWith({ + __cache: { + value: ['boy'], + }, + extraFormData: { + filters: [ + { + col: 'gender', + op: 'IN', + val: ['girl'], + }, + ], + }, + filterState: { + label: 'girl', + value: ['girl'], + }, + ownState: { + coltypeMap: { + gender: 1, + }, + search: null, + }, + }); + }); +}); diff --git a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx new file mode 100644 index 0000000000000..8ae9404f0c563 --- /dev/null +++ b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx @@ -0,0 +1,335 @@ +/** + * 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. + */ +/* eslint-disable no-param-reassign */ +import { + AppSection, + DataMask, + DataRecordValue, + ensureIsArray, + ExtraFormData, + GenericDataType, + getColumnLabel, + JsonObject, + smartDateDetailedFormatter, + t, + tn, +} from '@superset-ui/core'; +import { LabeledValue as AntdLabeledValue } from 'antd/lib/select'; +import React, { useCallback, useEffect, useState, useMemo } from 'react'; +import { Select } from 'src/components'; +import debounce from 'lodash/debounce'; +import { SLOW_DEBOUNCE } from 'src/constants'; +import { useImmerReducer } from 'use-immer'; +import { propertyComparator } from 'src/components/Select/Select'; +import { PluginFilterAdhocProps, SelectValue } from './types'; +import { StyledFormItem, FilterPluginStyle, StatusMessage } from '../common'; +import { getDataRecordFormatter, getAdhocExtraFormData } from '../../utils'; + +type DataMaskAction = + | { type: 'ownState'; ownState: JsonObject } + | { + type: 'filterState'; + __cache: JsonObject; + extraFormData: ExtraFormData; + filterState: { value: SelectValue; label?: string }; + }; + +function reducer( + draft: DataMask & { __cache?: JsonObject }, + action: DataMaskAction, +) { + switch (action.type) { + case 'ownState': + draft.ownState = { + ...draft.ownState, + ...action.ownState, + }; + return draft; + case 'filterState': + draft.extraFormData = action.extraFormData; + // eslint-disable-next-line no-underscore-dangle + draft.__cache = action.__cache; + draft.filterState = { ...draft.filterState, ...action.filterState }; + return draft; + default: + return draft; + } +} + +export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) { + const { + coltypeMap, + data, + filterState, + formData, + height, + isRefreshing, + width, + setDataMask, + setFocusedFilter, + unsetFocusedFilter, + setFilterActive, + appSection, + showOverflow, + parentRef, + inputRef, + } = props; + const { + enableEmptyFilter, + multiSelect, + showSearch, + inverseSelection, + defaultToFirstItem, + searchAllOptions, + } = formData; + const groupby = useMemo( + () => ensureIsArray(formData.groupby).map(getColumnLabel), + [formData.groupby], + ); + const [col] = groupby; + const [initialColtypeMap] = useState(coltypeMap); + const [dataMask, dispatchDataMask] = useImmerReducer(reducer, { + extraFormData: {}, + filterState, + }); + const datatype: GenericDataType = coltypeMap[col]; + const labelFormatter = useMemo( + () => + getDataRecordFormatter({ + timeFormatter: smartDateDetailedFormatter, + }), + [], + ); + + const updateDataMask = useCallback( + (values: SelectValue) => { + const emptyFilter = + enableEmptyFilter && !inverseSelection && !values?.length; + + const suffix = inverseSelection && values?.length ? t(' (excluded)') : ''; + + dispatchDataMask({ + type: 'filterState', + __cache: filterState, + extraFormData: getAdhocExtraFormData( + col, + values, + emptyFilter, + inverseSelection, + ), + filterState: { + ...filterState, + label: values?.length + ? `${(values || []) + .map(value => labelFormatter(value, datatype)) + .join(', ')}${suffix}` + : undefined, + value: + appSection === AppSection.FILTER_CONFIG_MODAL && defaultToFirstItem + ? undefined + : values, + }, + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [ + appSection, + col, + datatype, + defaultToFirstItem, + dispatchDataMask, + enableEmptyFilter, + inverseSelection, + JSON.stringify(filterState), + labelFormatter, + ], + ); + + useEffect(() => { + updateDataMask(filterState.value); + }, [JSON.stringify(filterState.value)]); + + const isDisabled = + appSection === AppSection.FILTER_CONFIG_MODAL && defaultToFirstItem; + + const debouncedOwnStateFunc = useCallback( + debounce((val: string) => { + dispatchDataMask({ + type: 'ownState', + ownState: { + coltypeMap: initialColtypeMap, + search: val, + }, + }); + }, SLOW_DEBOUNCE), + [], + ); + + const searchWrapper = useCallback( + (val: string) => { + if (searchAllOptions) { + debouncedOwnStateFunc(val); + } + }, + [debouncedOwnStateFunc, searchAllOptions], + ); + + const clearSuggestionSearch = useCallback(() => { + if (searchAllOptions) { + dispatchDataMask({ + type: 'ownState', + ownState: { + coltypeMap: initialColtypeMap, + search: null, + }, + }); + } + }, [dispatchDataMask, initialColtypeMap, searchAllOptions]); + + const handleBlur = useCallback(() => { + clearSuggestionSearch(); + unsetFocusedFilter(); + }, [clearSuggestionSearch, unsetFocusedFilter]); + + const handleChange = useCallback( + (value?: SelectValue | number | string) => { + const values = value === null ? [null] : ensureIsArray(value); + + if (values.length === 0) { + updateDataMask(null); + } else { + updateDataMask(values); + } + }, + [updateDataMask], + ); + + useEffect(() => { + if (defaultToFirstItem && filterState.value === undefined) { + // initialize to first value if set to default to first item + const firstItem: SelectValue = data[0] + ? (groupby.map(col => data[0][col]) as string[]) + : null; + // firstItem[0] !== undefined for a case when groupby changed but new data still not fetched + // TODO: still need repopulate default value in config modal when column changed + if (firstItem && firstItem[0] !== undefined) { + updateDataMask(firstItem); + } + } else if (isDisabled) { + // empty selection if filter is disabled + updateDataMask(null); + } else { + // reset data mask based on filter state + updateDataMask(filterState.value); + } + }, [ + col, + isDisabled, + defaultToFirstItem, + enableEmptyFilter, + inverseSelection, + updateDataMask, + data, + groupby, + JSON.stringify(filterState), + ]); + + useEffect(() => { + setDataMask(dataMask); + }, [JSON.stringify(dataMask)]); + + const placeholderText = + data.length === 0 + ? t('No data') + : tn('%s option', '%s options', data.length, data.length); + + const formItemExtra = useMemo(() => { + if (filterState.validateMessage) { + return ( + + {filterState.validateMessage} + + ); + } + return undefined; + }, [filterState.validateMessage, filterState.validateStatus]); + + const options = useMemo(() => { + const options: { label: string; value: DataRecordValue }[] = []; + data.forEach(row => { + const [value] = groupby.map(col => row[col]); + options.push({ + label: labelFormatter(value, datatype), + value, + }); + }); + return options; + }, [data, datatype, groupby, labelFormatter]); + + const sortComparator = useCallback( + (a: AntdLabeledValue, b: AntdLabeledValue) => { + const labelComparator = propertyComparator('label'); + if (formData.sortAscending) { + return labelComparator(a, b); + } + return labelComparator(b, a); + }, + [formData.sortAscending], + ); + + return ( + + + parentRef?.current - : (trigger: HTMLElement) => trigger?.parentNode - } - showSearch={showSearch} - mode={multiSelect ? 'multiple' : 'single'} - placeholder={placeholderText} - onSearch={searchWrapper} - onSelect={clearSuggestionSearch} - onBlur={handleBlur} - onMouseEnter={setFocusedFilter} - onMouseLeave={unsetFocusedFilter} - // @ts-ignore - onChange={handleChange} - ref={inputRef} - loading={isRefreshing} - maxTagCount={5} - invertSelection={inverseSelection} - // @ts-ignore - options={options} - sortComparator={sortComparator} - onDropdownVisibleChange={setFilterActive} + { + // New Adhoc Filters Selected + updateDataMask(filters); + }} + label={' '} + value={filterState.filters || []} /> diff --git a/superset-frontend/src/filters/utils.ts b/superset-frontend/src/filters/utils.ts index b5056df6616a2..eac83e929ba04 100644 --- a/superset-frontend/src/filters/utils.ts +++ b/superset-frontend/src/filters/utils.ts @@ -24,11 +24,11 @@ import { TimeFormatter, ExtraFormData, } from '@superset-ui/core'; +import AdhocFilter from 'src/explore/components/controls/FilterControl/AdhocFilter'; import { FALSE_STRING, NULL_STRING, TRUE_STRING } from 'src/utils/common'; export const getAdhocExtraFormData = ( - col: string, - value?: null | (string | number | boolean | null)[], + adhoc_filters: AdhocFilter[] = [], emptyFilter = false, inverseSelection = false, ): ExtraFormData => { @@ -41,16 +41,8 @@ export const getAdhocExtraFormData = ( sqlExpression: '1 = 0', }, ]; - } else if (value !== undefined && value !== null && value.length !== 0) { - extra.adhoc_filters = [ - { - expressionType: 'SIMPLE', - subject: col, - operator: 'IN', - comparator: value, - clause: 'WHERE', - }, - ]; + } else { + extra.adhoc_filters = adhoc_filters; } return extra; }; diff --git a/superset-frontend/src/visualizations/presets/MainPreset.js b/superset-frontend/src/visualizations/presets/MainPreset.js index d2e77bd1efac8..92d316d78707e 100644 --- a/superset-frontend/src/visualizations/presets/MainPreset.js +++ b/superset-frontend/src/visualizations/presets/MainPreset.js @@ -184,7 +184,7 @@ export default class MainPreset extends Preset { new EchartsTimeseriesStepChartPlugin().configure({ key: 'echarts_timeseries_step', }), - new AdhocFilterPlugin().configure({ key: 'adhoc_filter '}), + new AdhocFilterPlugin().configure({ key: 'filter_adhoc' }), new SelectFilterPlugin().configure({ key: 'filter_select' }), new RangeFilterPlugin().configure({ key: 'filter_range' }), new TimeFilterPlugin().configure({ key: 'filter_time' }), From 080d27996eb299ba62af72665bc26b85776993ca Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 27 Sep 2022 09:21:55 -0400 Subject: [PATCH 04/24] fixed hook that made too many requests --- .../src/filters/components/Adhoc/AdhocFilterPlugin.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx index 1ca5ab29a84cf..976a0afc54278 100644 --- a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx @@ -49,6 +49,8 @@ import { addDangerToast } from 'src/components/MessageToasts/actions'; import { cacheWrapper } from 'src/utils/cacheWrapper'; // eslint-disable-next-line import/no-unresolved import { getClientErrorObject } from 'src/utils/getClientErrorObject'; +// eslint-disable-next-line import/no-unresolved +import { useChangeEffect } from 'src/hooks/useChangeEffect'; import { PluginFilterAdhocProps, SelectValue } from './types'; import { StyledFormItem, FilterPluginStyle, StatusMessage } from '../common'; import { getDataRecordFormatter, getAdhocExtraFormData } from '../../utils'; @@ -148,7 +150,7 @@ export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) { ({ endpoint }) => endpoint || '', ); - useEffect(() => { + useChangeEffect(datasetId, () => { if (datasetId) { cachedSupersetGet({ endpoint: `/api/v1/dataset/${datasetId}`, @@ -164,9 +166,9 @@ export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) { addDangerToast(response.message); }); } - }, [datasetId]); + }); - useEffect(() => { + useChangeEffect(datasetId, () => { if (datasetId != null) { cachedSupersetGet({ endpoint: `/api/v1/dataset/${datasetId}`, From 99d9f5a3e2c5e97bfb9603eb673b13e574deb241 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 27 Sep 2022 11:52:18 -0400 Subject: [PATCH 05/24] fixed applied filter label --- .../src/filters/components/Adhoc/AdhocFilterPlugin.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx index 976a0afc54278..0d066002cad4b 100644 --- a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx @@ -203,7 +203,13 @@ export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) { ), filterState: { ...filterState, - label: (adhoc_filters || []).map(f => String(f.subject)).join(', '), + label: (adhoc_filters || []) + .map(f => + f.sqlExpression + ? String(f.sqlExpression) + : String(f.subject) + String(f.operator) + String(f.comparator), + ) + .join(', '), value: adhoc_filters, filters: adhoc_filters, }, From 80151b6895ca11bd5f2d2fab8450b4af10e7f475 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Wed, 28 Sep 2022 08:41:38 -0400 Subject: [PATCH 06/24] removed duplicate files --- .../Adhoc/AdhocFilterPlugin.stories.tsx | 62 ----- .../Adhoc/AdhocFilterPlugin.test.tsx | 252 ------------------ .../components/Adhoc/buildQuery.test.ts | 125 --------- 3 files changed, 439 deletions(-) delete mode 100644 superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.stories.tsx delete mode 100644 superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.test.tsx delete mode 100644 superset-frontend/src/filters/components/Adhoc/buildQuery.test.ts diff --git a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.stories.tsx b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.stories.tsx deleted file mode 100644 index 20ef4e2801a51..0000000000000 --- a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.stories.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/** - * 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 from 'react'; -import { action } from '@storybook/addon-actions'; -import { boolean, withKnobs } from '@storybook/addon-knobs'; -import { SuperChart, getChartTransformPropsRegistry } from '@superset-ui/core'; -import { mockQueryDataForCountries } from 'spec/fixtures/mockNativeFilters'; -import AdhocFilterPlugin from './index'; -import transformProps from './transformProps'; - -new AdhocFilterPlugin().configure({ key: 'filter_adhoc' }).register(); - -getChartTransformPropsRegistry().registerValue('filter_adhoc', transformProps); - -export default { - title: 'Filter Plugins', - decorators: [withKnobs], -}; - -export const Select = ({ - width, - height, -}: { - width: number; - height: number; -}) => ( - -); diff --git a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.test.tsx b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.test.tsx deleted file mode 100644 index bf86782a76acf..0000000000000 --- a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.test.tsx +++ /dev/null @@ -1,252 +0,0 @@ -/** - * 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 userEvent from '@testing-library/user-event'; -import { AppSection } from '@superset-ui/core'; -import React from 'react'; -import { render, screen } from 'spec/helpers/testing-library'; -import { NULL_STRING } from 'src/utils/common'; -import AdhocFilterPlugin from './AdhocFilterPlugin'; -import transformProps from './transformProps'; - -const selectMultipleProps = { - formData: { - sortAscending: true, - multiSelect: true, - enableEmptyFilter: true, - defaultToFirstItem: false, - inverseSelection: false, - searchAllOptions: false, - datasource: '3__table', - groupby: ['gender'], - adhocFilters: [], - extraFilters: [], - extraFormData: {}, - granularitySqla: 'ds', - metrics: ['count'], - rowLimit: 1000, - showSearch: true, - defaultValue: ['boy'], - timeRangeEndpoints: ['inclusive', 'exclusive'], - urlParams: {}, - vizType: 'filter_select', - inputRef: { current: null }, - }, - height: 20, - hooks: {}, - ownState: {}, - filterState: { value: ['boy'] }, - queriesData: [ - { - rowcount: 2, - colnames: ['gender'], - coltypes: [1], - data: [{ gender: 'boy' }, { gender: 'girl' }, { gender: null }], - applied_filters: [{ column: 'gender' }], - rejected_filters: [], - }, - ], - width: 220, - behaviors: ['NATIVE_FILTER'], - isRefreshing: false, - appSection: AppSection.DASHBOARD, -}; - -describe('AdhocFilterPlugin', () => { - const setDataMask = jest.fn(); - const getWrapper = (props = {}) => - render( - // @ts-ignore - , - ); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('Add multiple values with first render', () => { - getWrapper(); - expect(setDataMask).toHaveBeenCalledWith({ - extraFormData: {}, - filterState: { - value: ['boy'], - }, - }); - expect(setDataMask).toHaveBeenCalledWith({ - __cache: { - value: ['boy'], - }, - extraFormData: { - filters: [ - { - col: 'gender', - op: 'IN', - val: ['boy'], - }, - ], - }, - filterState: { - label: 'boy', - value: ['boy'], - }, - }); - userEvent.click(screen.getByRole('combobox')); - userEvent.click(screen.getByTitle('girl')); - expect(setDataMask).toHaveBeenCalledWith({ - __cache: { - value: ['boy'], - }, - extraFormData: { - filters: [ - { - col: 'gender', - op: 'IN', - val: ['boy', 'girl'], - }, - ], - }, - filterState: { - label: 'boy, girl', - value: ['boy', 'girl'], - }, - }); - }); - - it('Remove multiple values when required', () => { - getWrapper(); - userEvent.click(document.querySelector('[data-icon="close"]')!); - expect(setDataMask).toHaveBeenCalledWith({ - __cache: { - value: ['boy'], - }, - extraFormData: { - adhoc_filters: [ - { - clause: 'WHERE', - expressionType: 'SQL', - sqlExpression: '1 = 0', - }, - ], - }, - filterState: { - label: undefined, - value: null, - }, - }); - }); - - it('Remove multiple values when not required', () => { - getWrapper({ enableEmptyFilter: false }); - userEvent.click(document.querySelector('[data-icon="close"]')!); - expect(setDataMask).toHaveBeenCalledWith({ - __cache: { - value: ['boy'], - }, - extraFormData: {}, - filterState: { - label: undefined, - value: null, - }, - }); - }); - - it('Select single values with inverse', () => { - getWrapper({ multiSelect: false, inverseSelection: true }); - userEvent.click(screen.getByRole('combobox')); - userEvent.click(screen.getByTitle('girl')); - expect(setDataMask).toHaveBeenCalledWith({ - __cache: { - value: ['boy'], - }, - extraFormData: { - filters: [ - { - col: 'gender', - op: 'NOT IN', - val: ['girl'], - }, - ], - }, - filterState: { - label: 'girl (excluded)', - value: ['girl'], - }, - }); - }); - - it('Select single null (empty) value', () => { - getWrapper(); - userEvent.click(screen.getByRole('combobox')); - userEvent.click(screen.getByTitle(NULL_STRING)); - expect(setDataMask).toHaveBeenLastCalledWith({ - __cache: { - value: ['boy'], - }, - extraFormData: { - filters: [ - { - col: 'gender', - op: 'IN', - val: ['boy', null], - }, - ], - }, - filterState: { - label: `boy, ${NULL_STRING}`, - value: ['boy', null], - }, - }); - }); - - it('Add ownState with column types when search all options', () => { - getWrapper({ searchAllOptions: true, multiSelect: false }); - userEvent.click(screen.getByRole('combobox')); - userEvent.click(screen.getByTitle('girl')); - expect(setDataMask).toHaveBeenCalledWith({ - __cache: { - value: ['boy'], - }, - extraFormData: { - filters: [ - { - col: 'gender', - op: 'IN', - val: ['girl'], - }, - ], - }, - filterState: { - label: 'girl', - value: ['girl'], - }, - ownState: { - coltypeMap: { - gender: 1, - }, - search: null, - }, - }); - }); -}); diff --git a/superset-frontend/src/filters/components/Adhoc/buildQuery.test.ts b/superset-frontend/src/filters/components/Adhoc/buildQuery.test.ts deleted file mode 100644 index 08b7037f9cef6..0000000000000 --- a/superset-frontend/src/filters/components/Adhoc/buildQuery.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -/** - * 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 { GenericDataType } from '@superset-ui/core'; -import buildQuery from './buildQuery'; -import { PluginFilterSelectQueryFormData } from './types'; - -describe('Select buildQuery', () => { - const formData: PluginFilterSelectQueryFormData = { - datasource: '5__table', - groupby: ['my_col'], - viz_type: 'filter_select', - sortAscending: undefined, - sortMetric: undefined, - filters: undefined, - enableEmptyFilter: false, - inverseSelection: false, - multiSelect: false, - defaultToFirstItem: false, - searchAllOptions: false, - height: 100, - width: 100, - }; - - it('should build a default query', () => { - const queryContext = buildQuery(formData); - expect(queryContext.queries.length).toEqual(1); - const [query] = queryContext.queries; - expect(query.groupby).toEqual(['my_col']); - expect(query.filters).toEqual([]); - expect(query.metrics).toEqual([]); - expect(query.orderby).toEqual([]); - }); - - it('should sort descending by metric', () => { - const queryContext = buildQuery({ - ...formData, - sortMetric: 'my_metric', - sortAscending: false, - }); - expect(queryContext.queries.length).toEqual(1); - const [query] = queryContext.queries; - expect(query.groupby).toEqual(['my_col']); - expect(query.metrics).toEqual(['my_metric']); - expect(query.orderby).toEqual([['my_metric', false]]); - }); - - it('should sort ascending by metric', () => { - const queryContext = buildQuery({ - ...formData, - sortMetric: 'my_metric', - sortAscending: true, - }); - expect(queryContext.queries.length).toEqual(1); - const [query] = queryContext.queries; - expect(query.groupby).toEqual(['my_col']); - expect(query.metrics).toEqual(['my_metric']); - expect(query.orderby).toEqual([['my_metric', true]]); - }); - - it('should sort ascending by column', () => { - const queryContext = buildQuery({ - ...formData, - sortAscending: true, - }); - expect(queryContext.queries.length).toEqual(1); - const [query] = queryContext.queries; - expect(query.groupby).toEqual(['my_col']); - expect(query.metrics).toEqual([]); - expect(query.orderby).toEqual([['my_col', true]]); - }); - - it('should sort descending by column', () => { - const queryContext = buildQuery({ - ...formData, - sortAscending: false, - }); - expect(queryContext.queries.length).toEqual(1); - const [query] = queryContext.queries; - expect(query.groupby).toEqual(['my_col']); - expect(query.metrics).toEqual([]); - expect(query.orderby).toEqual([['my_col', false]]); - }); - - it('should add text search parameter to query filter', () => { - const queryContext = buildQuery(formData, { - ownState: { - search: 'abc', - coltypeMap: { my_col: GenericDataType.STRING }, - }, - }); - expect(queryContext.queries.length).toEqual(1); - const [query] = queryContext.queries; - expect(query.filters).toEqual([ - { col: 'my_col', op: 'ILIKE', val: '%abc%' }, - ]); - }); - - it('should add numeric search parameter to query filter', () => { - const queryContext = buildQuery(formData, { - ownState: { - search: '123', - coltypeMap: { my_col: GenericDataType.NUMERIC }, - }, - }); - expect(queryContext.queries.length).toEqual(1); - const [query] = queryContext.queries; - expect(query.filters).toEqual([{ col: 'my_col', op: '>=', val: 123 }]); - }); -}); From 3a66d9365fb1385b2cab52c12fee91fcd06f23ee Mon Sep 17 00:00:00 2001 From: cccs-Dustin <96579982+cccs-Dustin@users.noreply.github.com> Date: Wed, 28 Sep 2022 08:44:25 -0400 Subject: [PATCH 07/24] Removed uneeded functions --- .../components/Adhoc/AdhocFilterPlugin.tsx | 97 +------------------ 1 file changed, 2 insertions(+), 95 deletions(-) diff --git a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx index 0d066002cad4b..01afd4ac09cca 100644 --- a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx @@ -18,29 +18,18 @@ */ /* eslint-disable no-param-reassign */ import { - AppSection, DataMask, - DataRecordValue, - ensureIsArray, ExtraFormData, GenericDataType, - getColumnLabel, JsonObject, JsonResponse, - QueryFormColumn, smartDateDetailedFormatter, SupersetApiError, SupersetClient, t, - tn, } from '@superset-ui/core'; -import { LabeledValue as AntdLabeledValue } from 'antd/lib/select'; import React, { useCallback, useEffect, useState, useMemo } from 'react'; -import debounce from 'lodash/debounce'; -// eslint-disable-next-line import/no-unresolved -import { SLOW_DEBOUNCE } from 'src/constants'; import { useImmerReducer } from 'use-immer'; -import { propertyComparator } from 'src/components/Select/Select'; import AdhocFilterControl from 'src/explore/components/controls/FilterControl/AdhocFilterControl'; import AdhocFilter from 'src/explore/components/controls/FilterControl/AdhocFilter'; // eslint-disable-next-line import/no-unresolved @@ -51,7 +40,7 @@ import { cacheWrapper } from 'src/utils/cacheWrapper'; import { getClientErrorObject } from 'src/utils/getClientErrorObject'; // eslint-disable-next-line import/no-unresolved import { useChangeEffect } from 'src/hooks/useChangeEffect'; -import { PluginFilterAdhocProps, SelectValue } from './types'; +import { PluginFilterAdhocProps } from './types'; import { StyledFormItem, FilterPluginStyle, StatusMessage } from '../common'; import { getDataRecordFormatter, getAdhocExtraFormData } from '../../utils'; @@ -108,18 +97,7 @@ export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) { parentRef, inputRef, } = props; - const { - enableEmptyFilter, - multiSelect, - showSearch, - inverseSelection, - defaultToFirstItem, - searchAllOptions, - } = formData; - const groupby = useMemo( - () => ensureIsArray(formData.groupby).map(getColumnLabel), - [formData.groupby], - ); + const { enableEmptyFilter, inverseSelection, defaultToFirstItem } = formData; const datasetId = useMemo( () => formData.datasource.split('_')[0], [formData.datasource], @@ -128,7 +106,6 @@ export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) { const [col, setCol] = useState(''); console.log(formData.columns); const [columns, setColumns] = useState(); - const [initialColtypeMap] = useState(coltypeMap); const [dataMask, dispatchDataMask] = useImmerReducer(reducer, { extraFormData: {}, filterState, @@ -233,57 +210,10 @@ export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) { updateDataMask(filterState.value); }, [JSON.stringify(filterState.value)]); - const isDisabled = - appSection === AppSection.FILTER_CONFIG_MODAL && defaultToFirstItem; - - const debouncedOwnStateFunc = useCallback( - debounce((val: string) => { - dispatchDataMask({ - type: 'ownState', - ownState: { - coltypeMap: initialColtypeMap, - search: val, - }, - }); - }, SLOW_DEBOUNCE), - [], - ); - - const searchWrapper = useCallback( - (val: string) => { - if (searchAllOptions) { - debouncedOwnStateFunc(val); - } - }, - [debouncedOwnStateFunc, searchAllOptions], - ); - - const clearSuggestionSearch = useCallback(() => { - if (searchAllOptions) { - dispatchDataMask({ - type: 'ownState', - ownState: { - coltypeMap: initialColtypeMap, - search: null, - }, - }); - } - }, [dispatchDataMask, initialColtypeMap, searchAllOptions]); - - const handleBlur = useCallback(() => { - clearSuggestionSearch(); - unsetFocusedFilter(); - }, [clearSuggestionSearch, unsetFocusedFilter]); - useEffect(() => { setDataMask(dataMask); }, [JSON.stringify(dataMask)]); - const placeholderText = - data.length === 0 - ? t('No data') - : tn('%s option', '%s options', data.length, data.length); - const formItemExtra = useMemo(() => { if (filterState.validateMessage) { return ( @@ -295,29 +225,6 @@ export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) { return undefined; }, [filterState.validateMessage, filterState.validateStatus]); - const options = useMemo(() => { - const options: { label: string; value: DataRecordValue }[] = []; - data.forEach(row => { - const [value] = groupby.map(col => row[col]); - options.push({ - label: labelFormatter(value, datatype), - value, - }); - }); - return options; - }, [data, datatype, groupby, labelFormatter]); - - const sortComparator = useCallback( - (a: AntdLabeledValue, b: AntdLabeledValue) => { - const labelComparator = propertyComparator('label'); - if (formData.sortAscending) { - return labelComparator(a, b); - } - return labelComparator(b, a); - }, - [formData.sortAscending], - ); - return ( Date: Wed, 28 Sep 2022 08:48:20 -0400 Subject: [PATCH 08/24] Removed uneeded functions and variables --- .../src/filters/components/Adhoc/AdhocFilterPlugin.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx index 01afd4ac09cca..614732bd737fc 100644 --- a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx @@ -20,7 +20,6 @@ import { DataMask, ExtraFormData, - GenericDataType, JsonObject, JsonResponse, smartDateDetailedFormatter, @@ -103,14 +102,11 @@ export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) { [formData.datasource], ); const [datasetDetails, setDatasetDetails] = useState>(); - const [col, setCol] = useState(''); - console.log(formData.columns); const [columns, setColumns] = useState(); const [dataMask, dispatchDataMask] = useImmerReducer(reducer, { extraFormData: {}, filterState, }); - const datatype: GenericDataType = coltypeMap[col]; const labelFormatter = useMemo( () => getDataRecordFormatter({ @@ -195,8 +191,6 @@ export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) { // eslint-disable-next-line react-hooks/exhaustive-deps [ appSection, - col, - datatype, defaultToFirstItem, dispatchDataMask, enableEmptyFilter, From ac5e2036219865e7d45438b618b3b5e5a1c935b8 Mon Sep 17 00:00:00 2001 From: cccs-Dustin <96579982+cccs-Dustin@users.noreply.github.com> Date: Wed, 28 Sep 2022 09:15:44 -0400 Subject: [PATCH 09/24] Removed unused props variables --- .../components/Adhoc/AdhocFilterPlugin.tsx | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx index 614732bd737fc..8240846d4c558 100644 --- a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx @@ -79,23 +79,8 @@ function reducer( } export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) { - const { - coltypeMap, - data, - filterState, - formData, - height, - isRefreshing, - width, - setDataMask, - setFocusedFilter, - unsetFocusedFilter, - setFilterActive, - appSection, - showOverflow, - parentRef, - inputRef, - } = props; + const { filterState, formData, height, width, setDataMask, appSection } = + props; const { enableEmptyFilter, inverseSelection, defaultToFirstItem } = formData; const datasetId = useMemo( () => formData.datasource.split('_')[0], From be85fab2c9d764458e6d7eb67704ce0b4a1d95c3 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Wed, 28 Sep 2022 09:49:18 -0400 Subject: [PATCH 10/24] modifying base image tag --- cccs-build/superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cccs-build/superset/Dockerfile b/cccs-build/superset/Dockerfile index 94eb220a3e921..31c35f03d3d7f 100644 --- a/cccs-build/superset/Dockerfile +++ b/cccs-build/superset/Dockerfile @@ -1,7 +1,7 @@ # Vault CA container import ARG VAULT_CA_CONTAINER=uchimera.azurecr.io/cccs/hogwarts/vault-ca:master_2921_22315d60 FROM $VAULT_CA_CONTAINER AS vault_ca -FROM uchimera.azurecr.io/cccs/superset-base:cccs-2.0_20220923113053_b4951 +FROM uchimera.azurecr.io/cccs/superset-base:Adding-Dashboard-Adhoc-Filters_20220928133009_b4980 USER root From 673ffac0748f53ff1aee664edd27ccaaa452f7f7 Mon Sep 17 00:00:00 2001 From: cccs-Dustin <96579982+cccs-Dustin@users.noreply.github.com> Date: Wed, 28 Sep 2022 10:28:03 -0400 Subject: [PATCH 11/24] Removed unused config settings --- .../FiltersConfigForm/FiltersConfigForm.tsx | 447 +++++++++--------- .../filters/components/Adhoc/controlPanel.ts | 81 +--- 2 files changed, 226 insertions(+), 302 deletions(-) diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx index f1b3a5705b19f..fc7092b0176ed 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx @@ -851,251 +851,254 @@ const FiltersConfigForm = ( expandIconPosition="right" key={`native-filter-config-${filterId}`} > - {formFilter?.filterType !== 'filter_time' && ( - - {canDependOnOtherFilters && hasAvailableFilters && ( - - { - setNativeFilterFieldValues(form, filterId, { - dependencies, - }); - forceUpdate(); - validateDependencies(); - formChanged(); - }} - getDependencySuggestion={() => - getDependencySuggestion(filterId) - } - /> - - )} - {hasDataset && hasAdditionalFilters && ( - - { - formChanged(); - if (checked) { - validatePreFilter(); - } - }} + {formFilter?.filterType !== 'filter_time' && + formFilter?.filterType !== 'filter_adhoc' && ( + + {canDependOnOtherFilters && hasAvailableFilters && ( + - - c.filterable, - ) || [] - } - savedMetrics={datasetDetails?.metrics || []} - datasource={datasetDetails} - onChange={(filters: AdhocFilter[]) => { - setNativeFilterFieldValues(form, filterId, { - adhoc_filters: filters, - }); - forceUpdate(); + { + setNativeFilterFieldValues(form, filterId, { + dependencies, + }); + forceUpdate(); + validateDependencies(); + formChanged(); + }} + getDependencySuggestion={() => + getDependencySuggestion(filterId) + } + /> + + )} + {hasDataset && hasAdditionalFilters && ( + + { + formChanged(); + if (checked) { validatePreFilter(); - }} - label={ - - {t('Pre-filter')} - {!hasTimeRange && } - } - /> - - {showTimeRangePicker && ( - {t('Time range')}} - initialValue={filterToEdit?.time_range || 'No filter'} - required={!hasAdhoc} + }} + > + - { + c.filterable, + ) || [] + } + savedMetrics={datasetDetails?.metrics || []} + datasource={datasetDetails} + onChange={(filters: AdhocFilter[]) => { setNativeFilterFieldValues(form, filterId, { - time_range: timeRange, + adhoc_filters: filters, }); forceUpdate(); validatePreFilter(); }} + label={ + + {t('Pre-filter')} + {!hasTimeRange && } + + } /> - - )} - {hasTimeRange && ( + + {showTimeRangePicker && ( + {t('Time range')}} + initialValue={filterToEdit?.time_range || 'No filter'} + required={!hasAdhoc} + rules={[ + { + validator: preFilterValidator, + }, + ]} + > + { + setNativeFilterFieldValues(form, filterId, { + time_range: timeRange, + }); + forceUpdate(); + validatePreFilter(); + }} + /> + + )} + {hasTimeRange && ( + + {t('Time column')} +   + + + } + initialValue={filterToEdit?.granularity_sqla} + > + !!column.is_dttm} + datasetId={datasetId} + onChange={column => { + // We need reset default value when when column changed + setNativeFilterFieldValues(form, filterId, { + granularity_sqla: column, + }); + forceUpdate(); + }} + /> + + )} + + + )} + {formFilter?.filterType !== 'filter_range' ? ( + + { + onSortChanged(checked || undefined); + formChanged(); + }} + > - {t('Time column')}  - - - } - initialValue={filterToEdit?.granularity_sqla} + name={[ + 'filters', + filterId, + 'controlValues', + 'sortAscending', + ]} + initialValue={sort} + label={{t('Sort type')}} > - !!column.is_dttm} - datasetId={datasetId} - onChange={column => { - // We need reset default value when when column changed - setNativeFilterFieldValues(form, filterId, { - granularity_sqla: column, - }); - forceUpdate(); + { + onSortChanged(value.target.value); }} - /> + > + {t('Sort ascending')} + {t('Sort descending')} + - )} - - - )} - {formFilter?.filterType !== 'filter_range' ? ( - - { - onSortChanged(checked || undefined); - formChanged(); - }} - > - {t('Sort type')}} + {hasMetrics && ( + + {t('Sort Metric')} +   + + + } + data-test="field-input" + > + ({ - value: metric.metric_name, - label: metric.verbose_name ?? metric.metric_name, - }))} - onChange={value => { - if (value !== undefined) { - setNativeFilterFieldValues(form, filterId, { - sortMetric: value, - }); - forceUpdate(); - } - }} - /> - - )} - - - ) : ( - - { - onEnableSingleValueChanged( - checked ? SingleValueType.Exact : undefined, - ); - formChanged(); - }} - > - {t('Single value type')} - } - > - - onEnableSingleValueChanged(value.target.value) - } - > - - {t('Minimum')} - - - {t('Exact')} - - - {t('Maximum')} - - - - - - )} - - )} + + onEnableSingleValueChanged(value.target.value) + } + > + + {t('Minimum')} + + + {t('Exact')} + + + {t('Maximum')} + + + + + + )} + + )} Date: Thu, 29 Sep 2022 10:25:45 -0400 Subject: [PATCH 12/24] removed column for filter config form --- .../filters/components/Adhoc/buildQuery.ts | 46 ++----------------- .../filters/components/Adhoc/controlPanel.ts | 30 +----------- 2 files changed, 7 insertions(+), 69 deletions(-) diff --git a/superset-frontend/src/filters/components/Adhoc/buildQuery.ts b/superset-frontend/src/filters/components/Adhoc/buildQuery.ts index abcd84f9d238d..61494fc1e587d 100644 --- a/superset-frontend/src/filters/components/Adhoc/buildQuery.ts +++ b/superset-frontend/src/filters/components/Adhoc/buildQuery.ts @@ -18,62 +18,26 @@ */ import { buildQueryContext, - GenericDataType, - getColumnLabel, - isPhysicalColumn, QueryObject, QueryObjectFilterClause, BuildQuery, } from '@superset-ui/core'; -import { DEFAULT_FORM_DATA, PluginFilterSelectQueryFormData } from './types'; +import { PluginFilterSelectQueryFormData } from './types'; const buildQuery: BuildQuery = ( formData: PluginFilterSelectQueryFormData, - options, -) => { - const { search, coltypeMap } = options?.ownState || {}; - const { sortAscending, sortMetric } = { ...DEFAULT_FORM_DATA, ...formData }; - return buildQueryContext(formData, baseQueryObject => { - const { columns = [], filters = [] } = baseQueryObject; +) => + buildQueryContext(formData, baseQueryObject => { + const { filters = [] } = baseQueryObject; const extraFilters: QueryObjectFilterClause[] = []; - if (search) { - columns.filter(isPhysicalColumn).forEach(column => { - const label = getColumnLabel(column); - if (coltypeMap[label] === GenericDataType.STRING) { - extraFilters.push({ - col: column, - op: 'ILIKE', - val: `%${search}%`, - }); - } else if ( - coltypeMap[label] === GenericDataType.NUMERIC && - !Number.isNaN(Number(search)) - ) { - // for numeric columns we apply a >= where clause - extraFilters.push({ - col: column, - op: '>=', - val: Number(search), - }); - } - }); - } - - const sortColumns = sortMetric ? [sortMetric] : columns; const query: QueryObject[] = [ { ...baseQueryObject, - groupby: columns, - metrics: sortMetric ? [sortMetric] : [], + result_type: 'columns', filters: filters.concat(extraFilters), - orderby: - sortMetric || sortAscending !== undefined - ? sortColumns.map(column => [column, !!sortAscending]) - : [], }, ]; return query; }); -}; export default buildQuery; diff --git a/superset-frontend/src/filters/components/Adhoc/controlPanel.ts b/superset-frontend/src/filters/components/Adhoc/controlPanel.ts index f9aa17ddf8127..9d05179b2314b 100644 --- a/superset-frontend/src/filters/components/Adhoc/controlPanel.ts +++ b/superset-frontend/src/filters/components/Adhoc/controlPanel.ts @@ -16,12 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import { t, validateNonEmpty } from '@superset-ui/core'; -import { - ControlPanelConfig, - sections, - sharedControls, -} from '@superset-ui/chart-controls'; +import { t } from '@superset-ui/core'; +import { ControlPanelConfig, sections } from '@superset-ui/chart-controls'; import { DEFAULT_FORM_DATA } from './types'; const { enableEmptyFilter } = DEFAULT_FORM_DATA; @@ -30,22 +26,6 @@ const config: ControlPanelConfig = { controlPanelSections: [ // @ts-ignore sections.legacyRegularTime, - { - label: t('Query'), - expanded: true, - controlSetRows: [ - [ - { - name: 'groupby', - config: { - ...sharedControls.groupby, - label: 'Column', - required: true, - }, - }, - ], - ], - }, { label: t('UI Configuration'), expanded: true, @@ -67,12 +47,6 @@ const config: ControlPanelConfig = { ], }, ], - controlOverrides: { - groupby: { - multi: false, - validators: [validateNonEmpty], - }, - }, }; export default config; From 157aaae5f6f04c1507192b178ef12894856f816d Mon Sep 17 00:00:00 2001 From: cccs-Dustin <96579982+cccs-Dustin@users.noreply.github.com> Date: Thu, 29 Sep 2022 13:41:47 -0400 Subject: [PATCH 13/24] Improved the applied filter(s) modal --- .../filters/components/Adhoc/AdhocFilterPlugin.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx index 8240846d4c558..084ad9f8fc489 100644 --- a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx @@ -146,6 +146,15 @@ export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) { } }); + const labelString = (props: AdhocFilter) => { + if (props.comparator.length >= 1) { + return `${props.subject} ${props.operator} (${props.comparator.join( + ', ', + )})`; + } + return `${props.subject} ${props.operator} ${props.comparator}`; + }; + const updateDataMask = useCallback( (adhoc_filters: AdhocFilter[]) => { const emptyFilter = @@ -163,9 +172,7 @@ export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) { ...filterState, label: (adhoc_filters || []) .map(f => - f.sqlExpression - ? String(f.sqlExpression) - : String(f.subject) + String(f.operator) + String(f.comparator), + f.sqlExpression ? String(f.sqlExpression) : labelString(f), ) .join(', '), value: adhoc_filters, From 257eec8a49dcd69b4ada85db98166af2f484bddc Mon Sep 17 00:00:00 2001 From: cccs-Dustin <96579982+cccs-Dustin@users.noreply.github.com> Date: Thu, 29 Sep 2022 13:58:45 -0400 Subject: [PATCH 14/24] Temp update to build image --- cccs-build/superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cccs-build/superset/Dockerfile b/cccs-build/superset/Dockerfile index 31c35f03d3d7f..3b98aaeb5d55b 100644 --- a/cccs-build/superset/Dockerfile +++ b/cccs-build/superset/Dockerfile @@ -1,7 +1,7 @@ # Vault CA container import ARG VAULT_CA_CONTAINER=uchimera.azurecr.io/cccs/hogwarts/vault-ca:master_2921_22315d60 FROM $VAULT_CA_CONTAINER AS vault_ca -FROM uchimera.azurecr.io/cccs/superset-base:Adding-Dashboard-Adhoc-Filters_20220928133009_b4980 +FROM uchimera.azurecr.io/cccs/superset-base:Adding-Dashboard-Adhoc-Filters_20220929174338_b5009 USER root From 14c936a69a539f7e9cb8f7e758fc7f4996a4e802 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Thu, 29 Sep 2022 15:48:31 -0400 Subject: [PATCH 15/24] fixed string formatting issue: --- .../src/filters/components/Adhoc/AdhocFilterPlugin.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx index 084ad9f8fc489..67924b212a3d7 100644 --- a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx @@ -19,6 +19,7 @@ /* eslint-disable no-param-reassign */ import { DataMask, + ensureIsArray, ExtraFormData, JsonObject, JsonResponse, @@ -146,8 +147,8 @@ export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) { } }); - const labelString = (props: AdhocFilter) => { - if (props.comparator.length >= 1) { + const labelString: (props: AdhocFilter) => string = (props: AdhocFilter) => { + if (ensureIsArray(props.comparator).length >= 2) { return `${props.subject} ${props.operator} (${props.comparator.join( ', ', )})`; From 3a6b4bfe85b4861c1ea118c344a535b94f67a893 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Thu, 29 Sep 2022 16:14:24 -0400 Subject: [PATCH 16/24] updating superset base image tag --- cccs-build/superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cccs-build/superset/Dockerfile b/cccs-build/superset/Dockerfile index 3b98aaeb5d55b..c9cfdfaef45ba 100644 --- a/cccs-build/superset/Dockerfile +++ b/cccs-build/superset/Dockerfile @@ -1,7 +1,7 @@ # Vault CA container import ARG VAULT_CA_CONTAINER=uchimera.azurecr.io/cccs/hogwarts/vault-ca:master_2921_22315d60 FROM $VAULT_CA_CONTAINER AS vault_ca -FROM uchimera.azurecr.io/cccs/superset-base:Adding-Dashboard-Adhoc-Filters_20220929174338_b5009 +FROM uchimera.azurecr.io/cccs/superset-base:Adding-Dashboard-Adhoc-Filters_20220929200100_b5016 USER root From 94de5848c51beb4004fecb23b9c4cb8150598b41 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 4 Oct 2022 09:47:33 -0400 Subject: [PATCH 17/24] added setFocused hooks to filter when hovering --- .../components/Adhoc/AdhocFilterPlugin.tsx | 48 +++++++++++++------ .../components/Time/TimeFilterPlugin.tsx | 30 +----------- .../src/filters/components/common.ts | 28 +++++++++++ 3 files changed, 63 insertions(+), 43 deletions(-) diff --git a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx index 67924b212a3d7..5bf971935705d 100644 --- a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx @@ -41,7 +41,12 @@ import { getClientErrorObject } from 'src/utils/getClientErrorObject'; // eslint-disable-next-line import/no-unresolved import { useChangeEffect } from 'src/hooks/useChangeEffect'; import { PluginFilterAdhocProps } from './types'; -import { StyledFormItem, FilterPluginStyle, StatusMessage } from '../common'; +import { + StyledFormItem, + FilterPluginStyle, + StatusMessage, + ControlContainer, +} from '../common'; import { getDataRecordFormatter, getAdhocExtraFormData } from '../../utils'; type DataMaskAction = @@ -80,8 +85,17 @@ function reducer( } export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) { - const { filterState, formData, height, width, setDataMask, appSection } = - props; + const { + filterState, + formData, + height, + width, + setDataMask, + setFocusedFilter, + unsetFocusedFilter, + setFilterActive, + appSection, + } = props; const { enableEmptyFilter, inverseSelection, defaultToFirstItem } = formData; const datasetId = useMemo( () => formData.datasource.split('_')[0], @@ -218,17 +232,23 @@ export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) { validateStatus={filterState.validateStatus} extra={formItemExtra} > - { - // New Adhoc Filters Selected - updateDataMask(filters); - }} - label={' '} - value={filterState.filters || []} - /> + + { + // New Adhoc Filters Selected + updateDataMask(filters); + }} + label={' '} + value={filterState.filters || []} + /> + ); diff --git a/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx b/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx index fabc9f136c80b..9a35cdb359429 100644 --- a/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx @@ -21,40 +21,12 @@ import React, { useCallback, useEffect } from 'react'; import DateFilterControl from 'src/explore/components/controls/DateFilterControl'; import { NO_TIME_RANGE } from 'src/explore/constants'; import { PluginFilterTimeProps } from './types'; -import { FilterPluginStyle } from '../common'; +import { ControlContainer, FilterPluginStyle } from '../common'; const TimeFilterStyles = styled(FilterPluginStyle)` overflow-x: auto; `; -const ControlContainer = styled.div<{ - validateStatus?: 'error' | 'warning' | 'info'; -}>` - padding: 2px; - & > span, - & > span:hover { - border: 2px solid transparent; - display: inline-block; - border: ${({ theme, validateStatus }) => - validateStatus && `2px solid ${theme.colors[validateStatus]?.base}`}; - } - &:focus { - & > span { - border: 2px solid - ${({ theme, validateStatus }) => - validateStatus - ? theme.colors[validateStatus]?.base - : theme.colors.primary.base}; - outline: 0; - box-shadow: 0 0 0 2px - ${({ validateStatus }) => - validateStatus - ? 'rgba(224, 67, 85, 12%)' - : 'rgba(32, 167, 201, 0.2)'}; - } - } -`; - export default function TimeFilterPlugin(props: PluginFilterTimeProps) { const { setDataMask, diff --git a/superset-frontend/src/filters/components/common.ts b/superset-frontend/src/filters/components/common.ts index af1fe9c791761..fe60ee1f5e107 100644 --- a/superset-frontend/src/filters/components/common.ts +++ b/superset-frontend/src/filters/components/common.ts @@ -25,6 +25,34 @@ export const FilterPluginStyle = styled.div` width: ${({ width }) => width}px; `; +export const ControlContainer = styled.div<{ + validateStatus?: 'error' | 'warning' | 'info'; +}>` + padding: 2px; + & > span, + & > span:hover { + border: 2px solid transparent; + display: inline-block; + border: ${({ theme, validateStatus }) => + validateStatus && `2px solid ${theme.colors[validateStatus]?.base}`}; + } + &:focus { + & > span { + border: 2px solid + ${({ theme, validateStatus }) => + validateStatus + ? theme.colors[validateStatus]?.base + : theme.colors.primary.base}; + outline: 0; + box-shadow: 0 0 0 2px + ${({ validateStatus }) => + validateStatus + ? 'rgba(224, 67, 85, 12%)' + : 'rgba(32, 167, 201, 0.2)'}; + } + } +`; + export const StyledFormItem = styled(FormItem)` &.ant-row.ant-form-item { margin: 0; From 58be3bbc24d004a1a2bc8e4c423189c2d5940aee Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Fri, 7 Oct 2022 11:25:45 -0400 Subject: [PATCH 18/24] fixed unused declaration error --- .../src/filters/components/Adhoc/AdhocFilterPlugin.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx index 5bf971935705d..dbb6f6e7ea95d 100644 --- a/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/Adhoc/AdhocFilterPlugin.tsx @@ -93,7 +93,6 @@ export default function PluginFilterAdhoc(props: PluginFilterAdhocProps) { setDataMask, setFocusedFilter, unsetFocusedFilter, - setFilterActive, appSection, } = props; const { enableEmptyFilter, inverseSelection, defaultToFirstItem } = formData; From ab53b1e39b637ff4abe920c938b1e1c45d11d3f7 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Fri, 7 Oct 2022 11:49:45 -0400 Subject: [PATCH 19/24] updating image --- cccs-build/superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cccs-build/superset/Dockerfile b/cccs-build/superset/Dockerfile index c9cfdfaef45ba..51e63399bb933 100644 --- a/cccs-build/superset/Dockerfile +++ b/cccs-build/superset/Dockerfile @@ -1,7 +1,7 @@ # Vault CA container import ARG VAULT_CA_CONTAINER=uchimera.azurecr.io/cccs/hogwarts/vault-ca:master_2921_22315d60 FROM $VAULT_CA_CONTAINER AS vault_ca -FROM uchimera.azurecr.io/cccs/superset-base:Adding-Dashboard-Adhoc-Filters_20220929200100_b5016 +FROM uchimera.azurecr.io/cccs/superset-base:Adding-Dashboard-Adhoc-Filters_20221007152625_b5071 USER root From 0b71bbf2cb8d95d123f80cba7c7d2e1b2c5d70da Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 11 Oct 2022 17:48:46 -0400 Subject: [PATCH 20/24] updating superset-base image tag --- cccs-build/superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cccs-build/superset/Dockerfile b/cccs-build/superset/Dockerfile index 51e63399bb933..0c5ae310e09ac 100644 --- a/cccs-build/superset/Dockerfile +++ b/cccs-build/superset/Dockerfile @@ -1,7 +1,7 @@ # Vault CA container import ARG VAULT_CA_CONTAINER=uchimera.azurecr.io/cccs/hogwarts/vault-ca:master_2921_22315d60 FROM $VAULT_CA_CONTAINER AS vault_ca -FROM uchimera.azurecr.io/cccs/superset-base:Adding-Dashboard-Adhoc-Filters_20221007152625_b5071 +FROM uchimera.azurecr.io/cccs/superset-base:Adding-Dashboard-Adhoc-Filters_20221011212043_b5112 USER root From fc2207b4c409afc70f8c4340e7b473cdfceb2f17 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:26:39 -0500 Subject: [PATCH 21/24] updating image --- cccs-build/superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cccs-build/superset/Dockerfile b/cccs-build/superset/Dockerfile index 0e1e13f04268b..e3001d43ecd6c 100644 --- a/cccs-build/superset/Dockerfile +++ b/cccs-build/superset/Dockerfile @@ -1,7 +1,7 @@ # Vault CA container import ARG VAULT_CA_CONTAINER=uchimera.azurecr.io/cccs/hogwarts/vault-ca:master_2921_22315d60 FROM $VAULT_CA_CONTAINER AS vault_ca -FROM uchimera.azurecr.io/cccs/superset-base:cccs-2.0_20221014182839_b5135 +FROM uchimera.azurecr.io/cccs/superset-base:Adding-Dashboard-Adhoc-Filters_20221121200533_b5557 USER root From 330f3e5065808ab1b593e929ce90cdcf3212e6f9 Mon Sep 17 00:00:00 2001 From: cccs-RyanK <102618419+cccs-RyanK@users.noreply.github.com> Date: Tue, 22 Nov 2022 13:29:15 -0500 Subject: [PATCH 22/24] Update cccs-build/superset/Dockerfile Co-authored-by: cccs-Dustin <96579982+cccs-Dustin@users.noreply.github.com> --- cccs-build/superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cccs-build/superset/Dockerfile b/cccs-build/superset/Dockerfile index e3001d43ecd6c..0e1e13f04268b 100644 --- a/cccs-build/superset/Dockerfile +++ b/cccs-build/superset/Dockerfile @@ -1,7 +1,7 @@ # Vault CA container import ARG VAULT_CA_CONTAINER=uchimera.azurecr.io/cccs/hogwarts/vault-ca:master_2921_22315d60 FROM $VAULT_CA_CONTAINER AS vault_ca -FROM uchimera.azurecr.io/cccs/superset-base:Adding-Dashboard-Adhoc-Filters_20221121200533_b5557 +FROM uchimera.azurecr.io/cccs/superset-base:cccs-2.0_20221014182839_b5135 USER root From 0d5fee8ce5e0ba4be4cba693ce142b4fbc5a4720 Mon Sep 17 00:00:00 2001 From: Reese <10563996+reesercollins@users.noreply.github.com> Date: Thu, 24 Nov 2022 11:40:51 -0500 Subject: [PATCH 23/24] Added ability to certify entities with multiple values (#224) * Added ability to certify entities with multiple values * Update description text to reflect new feature * Add tests for multiple certified by values --- .../components/CertifiedBadge/CertifiedBadge.test.tsx | 9 +++++++++ .../src/components/CertifiedBadge/index.tsx | 8 +++++--- .../src/components/Datasource/DatasourceEditor.jsx | 2 +- superset/connectors/sqla/views.py | 6 +++--- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/superset-frontend/src/components/CertifiedBadge/CertifiedBadge.test.tsx b/superset-frontend/src/components/CertifiedBadge/CertifiedBadge.test.tsx index dd7ebb8bf9371..eb593be1607bc 100644 --- a/superset-frontend/src/components/CertifiedBadge/CertifiedBadge.test.tsx +++ b/superset-frontend/src/components/CertifiedBadge/CertifiedBadge.test.tsx @@ -39,6 +39,15 @@ test('renders with certified by', async () => { expect(await screen.findByRole('tooltip')).toHaveTextContent(certifiedBy); }); +test('renders with multiple certified by values', async () => { + const certifiedBy = ['Trusted Authority', 'Other Authority']; + render(); + userEvent.hover(screen.getByRole('img')); + expect(await screen.findByRole('tooltip')).toHaveTextContent( + certifiedBy.join(', '), + ); +}); + test('renders with details', async () => { const details = 'All requirements have been met.'; render(); diff --git a/superset-frontend/src/components/CertifiedBadge/index.tsx b/superset-frontend/src/components/CertifiedBadge/index.tsx index 80581e6320d70..d2ef264c5dfb7 100644 --- a/superset-frontend/src/components/CertifiedBadge/index.tsx +++ b/superset-frontend/src/components/CertifiedBadge/index.tsx @@ -17,12 +17,12 @@ * under the License. */ import React from 'react'; -import { t, useTheme } from '@superset-ui/core'; +import { ensureIsArray, t, useTheme } from '@superset-ui/core'; import Icons, { IconType } from 'src/components/Icons'; import { Tooltip } from 'src/components/Tooltip'; export interface CertifiedBadgeProps { - certifiedBy?: string; + certifiedBy?: string | string[]; details?: string; size?: IconType['iconSize']; } @@ -41,7 +41,9 @@ function CertifiedBadge({ <> {certifiedBy && (
- {t('Certified by %s', certifiedBy)} + + {t('Certified by %s', ensureIsArray(certifiedBy).join(', '))} +
)}
{details}
diff --git a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx index 3354d9d42122a..cfc826da6aa71 100644 --- a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx +++ b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx @@ -905,7 +905,7 @@ class DatasourceEditor extends React.PureComponent { description={t( 'Extra data to specify table metadata. Currently supports ' + 'metadata of the format: `{ "certification": { "certified_by": ' + - '"Data Platform Team", "details": "This table is the source of truth." ' + + '["Data Platform Team", "Engineering Team"], "details": "This table is the source of truth." ' + '}, "warning_markdown": "This is a warning." }`.', )} control={ diff --git a/superset/connectors/sqla/views.py b/superset/connectors/sqla/views.py index 47e5f216d5647..c7b0271123924 100644 --- a/superset/connectors/sqla/views.py +++ b/superset/connectors/sqla/views.py @@ -144,7 +144,7 @@ class TableColumnInlineView( "extra": utils.markdown( "Extra data to specify column metadata. Currently supports " 'certification data of the format: `{ "certification": "certified_by": ' - '"Taylor Swift", "details": "This column is the source of truth." ' + '["Taylor Swift", "Harry Styles"], "details": "This column is the source of truth." ' "} }`. This should be modified from the edit datasource model in " "Explore to ensure correct formatting.", True, @@ -236,7 +236,7 @@ class SqlMetricInlineView( "extra": utils.markdown( "Extra data to specify metric metadata. Currently supports " 'metadata of the format: `{ "certification": { "certified_by": ' - '"Data Platform Team", "details": "This metric is the source of truth." ' + '["Data Platform Team", "Engineering Team"], "details": "This metric is the source of truth." ' '}, "warning_markdown": "This is a warning." }`. This should be modified ' "from the edit datasource model in Explore to ensure correct formatting.", True, @@ -443,7 +443,7 @@ class TableModelView( # pylint: disable=too-many-ancestors "extra": utils.markdown( "Extra data to specify table metadata. Currently supports " 'metadata of the format: `{ "certification": { "certified_by": ' - '"Data Platform Team", "details": "This table is the source of truth." ' + '["Data Platform Team", "Engineering Team"], "details": "This table is the source of truth." ' '}, "warning_markdown": "This is a warning." }`.', True, ), From f6cf4b0984578a232bdba2e80d4a616b5e6e1b26 Mon Sep 17 00:00:00 2001 From: cccs-RyanS <71385290+cccs-RyanS@users.noreply.github.com> Date: Fri, 25 Nov 2022 13:07:12 -0500 Subject: [PATCH 24/24] [bigdig] updating base image --- cccs-build/superset/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cccs-build/superset/Dockerfile b/cccs-build/superset/Dockerfile index 67b8914ff5c75..e8ea856c7f0fc 100644 --- a/cccs-build/superset/Dockerfile +++ b/cccs-build/superset/Dockerfile @@ -1,7 +1,7 @@ # Vault CA container import ARG VAULT_CA_CONTAINER=uchimera.azurecr.io/cccs/hogwarts/vault-ca:master_2921_22315d60 FROM $VAULT_CA_CONTAINER AS vault_ca -FROM uchimera.azurecr.io/cccs/superset-base:cccs-2.0_20221123151732_b5593 +FROM uchimera.azurecr.io/cccs/superset-base:cccs-2.0-bigdig12_20221125175138_b5636 USER root