diff --git a/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts b/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts new file mode 100644 index 0000000000..b38f4bce16 --- /dev/null +++ b/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts @@ -0,0 +1,126 @@ +/** + * 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 { Behavior, FeatureFlag } from '@superset-ui/core'; +import * as featureFlags from 'src/featureFlags'; +import { nativeFilterGate } from './utils'; + +let isFeatureEnabledMock: jest.MockInstance; + +describe('nativeFilterGate', () => { + describe('with all feature flags disabled', () => { + beforeAll(() => { + isFeatureEnabledMock = jest + .spyOn(featureFlags, 'isFeatureEnabled') + .mockImplementation(() => false); + }); + + afterAll(() => { + // @ts-ignore + isFeatureEnabledMock.restore(); + }); + + it('should return true for regular chart', () => { + expect(nativeFilterGate([])).toEqual(true); + }); + + it('should return true for cross filter chart', () => { + expect(nativeFilterGate([Behavior.INTERACTIVE_CHART])).toEqual(true); + }); + + it('should return false for native filter chart with cross filter support', () => { + expect( + nativeFilterGate([Behavior.NATIVE_FILTER, Behavior.INTERACTIVE_CHART]), + ).toEqual(false); + }); + + it('should return false for native filter behavior', () => { + expect(nativeFilterGate([Behavior.NATIVE_FILTER])).toEqual(false); + }); + }); + + describe('with only native filters feature flag enabled', () => { + beforeAll(() => { + isFeatureEnabledMock = jest + .spyOn(featureFlags, 'isFeatureEnabled') + .mockImplementation( + (featureFlag: FeatureFlag) => + featureFlag === FeatureFlag.DASHBOARD_NATIVE_FILTERS, + ); + }); + + afterAll(() => { + // @ts-ignore + isFeatureEnabledMock.restore(); + }); + + it('should return true for regular chart', () => { + expect(nativeFilterGate([])).toEqual(true); + }); + + it('should return true for cross filter chart', () => { + expect(nativeFilterGate([Behavior.INTERACTIVE_CHART])).toEqual(true); + }); + + it('should return false for native filter chart with cross filter support', () => { + expect( + nativeFilterGate([Behavior.NATIVE_FILTER, Behavior.INTERACTIVE_CHART]), + ).toEqual(false); + }); + + it('should return false for native filter behavior', () => { + expect(nativeFilterGate([Behavior.NATIVE_FILTER])).toEqual(false); + }); + }); + + describe('with native filters and experimental feature flag enabled', () => { + beforeAll(() => { + isFeatureEnabledMock = jest + .spyOn(featureFlags, 'isFeatureEnabled') + .mockImplementation((featureFlag: FeatureFlag) => + [ + FeatureFlag.DASHBOARD_CROSS_FILTERS, + FeatureFlag.DASHBOARD_FILTERS_EXPERIMENTAL, + ].includes(featureFlag), + ); + }); + + afterAll(() => { + // @ts-ignore + isFeatureEnabledMock.restore(); + }); + + it('should return true for regular chart', () => { + expect(nativeFilterGate([])).toEqual(true); + }); + + it('should return true for cross filter chart', () => { + expect(nativeFilterGate([Behavior.INTERACTIVE_CHART])).toEqual(true); + }); + + it('should return true for native filter chart with cross filter support', () => { + expect( + nativeFilterGate([Behavior.NATIVE_FILTER, Behavior.INTERACTIVE_CHART]), + ).toEqual(true); + }); + + it('should return false for native filter behavior', () => { + expect(nativeFilterGate([Behavior.NATIVE_FILTER])).toEqual(false); + }); + }); +}); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/utils.ts b/superset-frontend/src/dashboard/components/nativeFilters/utils.ts index 776a3a3234..d85684ca09 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/utils.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/utils.ts @@ -24,11 +24,13 @@ import { EXTRA_FORM_DATA_APPEND_KEYS, EXTRA_FORM_DATA_OVERRIDE_KEYS, AdhocFilter, + FeatureFlag, } from '@superset-ui/core'; import { Charts } from 'src/dashboard/types'; import { RefObject } from 'react'; import { DataMaskStateWithId } from 'src/dataMask/types'; import extractUrlParams from 'src/dashboard/util/extractUrlParams'; +import { isFeatureEnabled } from 'src/featureFlags'; import { Filter } from './types'; export const getFormData = ({ @@ -131,3 +133,12 @@ export function getExtraFormData( }); return extraFormData; } + +export function nativeFilterGate(behaviors: Behavior[]): boolean { + return ( + !behaviors.includes(Behavior.NATIVE_FILTER) || + (isFeatureEnabled(FeatureFlag.DASHBOARD_FILTERS_EXPERIMENTAL) && + isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS) && + behaviors.includes(Behavior.INTERACTIVE_CHART)) + ); +} diff --git a/superset-frontend/src/explore/components/controls/VizTypeControl/index.jsx b/superset-frontend/src/explore/components/controls/VizTypeControl/index.jsx index e720cbdd4f..b7efbd8bc7 100644 --- a/superset-frontend/src/explore/components/controls/VizTypeControl/index.jsx +++ b/superset-frontend/src/explore/components/controls/VizTypeControl/index.jsx @@ -19,14 +19,14 @@ import React, { useEffect, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import { Input, Row, Col } from 'src/common/components'; -import { Behavior, t, getChartMetadataRegistry } from '@superset-ui/core'; +import { t, getChartMetadataRegistry } from '@superset-ui/core'; import { useDynamicPluginContext } from 'src/components/DynamicPlugins'; import Modal from 'src/components/Modal'; import { Tooltip } from 'src/components/Tooltip'; import Label from 'src/components/Label'; import ControlHeader from 'src/explore/components/ControlHeader'; +import { nativeFilterGate } from 'src/dashboard/components/nativeFilters/utils'; import './VizTypeControl.less'; -import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; const propTypes = { description: PropTypes.string, @@ -108,11 +108,6 @@ function VizSupportValidation({ vizType }) { ); } -const nativeFilterGate = behaviors => - !behaviors.includes(Behavior.NATIVE_FILTER) || - (isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS) && - behaviors.includes(Behavior.INTERACTIVE_CHART)); - const VizTypeControl = props => { const [showModal, setShowModal] = useState(false); const [filter, setFilter] = useState(''); diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx index e2ee33d5cc..4f2211ebe7 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx @@ -17,35 +17,35 @@ * under the License. */ import { - SupersetClient, getChartMetadataRegistry, - t, styled, + SupersetClient, + t, } from '@superset-ui/core'; import React, { useMemo, useState } from 'react'; import rison from 'rison'; import { uniqBy } from 'lodash'; -import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; +import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; import { - createFetchRelated, createErrorHandler, + createFetchRelated, handleBulkChartExport, handleChartDelete, } from 'src/views/CRUD/utils'; import { - useListViewResource, - useFavoriteStatus, useChartEditModal, + useFavoriteStatus, + useListViewResource, } from 'src/views/CRUD/hooks'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu'; import FaveStar from 'src/components/FaveStar'; import ListView, { - ListViewProps, Filter, - Filters, - SelectOption, FilterOperator, + Filters, + ListViewProps, + SelectOption, } from 'src/components/ListView'; import { getFromLocalStorage } from 'src/utils/localStorageHelpers'; import withToasts from 'src/messageToasts/enhancers/withToasts'; @@ -54,6 +54,7 @@ import ImportModelsModal from 'src/components/ImportModal/index'; import Chart from 'src/types/Chart'; import { Tooltip } from 'src/components/Tooltip'; import Icons from 'src/components/Icons'; +import { nativeFilterGate } from 'src/dashboard/components/nativeFilters/utils'; import ChartCard from './ChartCard'; const PAGE_SIZE = 25; @@ -454,6 +455,7 @@ function ChartList(props: ChartListProps) { unfilteredLabel: t('All'), selects: registry .keys() + .filter(k => nativeFilterGate(registry.get(k)?.behaviors || [])) .map(k => ({ label: registry.get(k)?.name || k, value: k })) .sort((a, b) => { if (!a.label || !b.label) { diff --git a/superset-frontend/src/visualizations/presets/MainPreset.js b/superset-frontend/src/visualizations/presets/MainPreset.js index 03ea7a2602..ee4d806a49 100644 --- a/superset-frontend/src/visualizations/presets/MainPreset.js +++ b/superset-frontend/src/visualizations/presets/MainPreset.js @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { Preset } from '@superset-ui/core'; +import { isFeatureEnabled, Preset } from '@superset-ui/core'; import { BigNumberChartPlugin, BigNumberTotalChartPlugin, @@ -76,9 +76,16 @@ import { import { PivotTableChartPlugin as PivotTableChartPluginV2 } from '@superset-ui/plugin-chart-pivot-table'; import FilterBoxChartPlugin from '../FilterBox/FilterBoxChartPlugin'; import TimeTableChartPlugin from '../TimeTable/TimeTableChartPlugin'; +import { FeatureFlag } from '../../featureFlags'; export default class MainPreset extends Preset { constructor() { + const experimentalplugins = isFeatureEnabled( + FeatureFlag.DASHBOARD_FILTERS_EXPERIMENTAL, + ) + ? [new GroupByFilterPlugin().configure({ key: 'filter_groupby' })] + : []; + super({ name: 'Legacy charts', presets: [new DeckGLChartPreset()], @@ -135,8 +142,8 @@ export default class MainPreset extends Preset { new TimeFilterPlugin().configure({ key: 'filter_time' }), new TimeColumnFilterPlugin().configure({ key: 'filter_timecolumn' }), new TimeGrainFilterPlugin().configure({ key: 'filter_timegrain' }), - new GroupByFilterPlugin().configure({ key: 'filter_groupby' }), new EchartsTreeChartPlugin().configure({ key: 'tree_chart' }), + ...experimentalplugins, ], }); }