diff --git a/superset-frontend/src/explore/components/Control.tsx b/superset-frontend/src/explore/components/Control.tsx index f1b6925bd69..d3ae6bb1c17 100644 --- a/superset-frontend/src/explore/components/Control.tsx +++ b/superset-frontend/src/explore/components/Control.tsx @@ -19,7 +19,7 @@ import React, { ReactNode } from 'react'; import { ControlType } from '@superset-ui/chart-controls'; import { JsonValue, QueryFormData } from '@superset-ui/core'; -import { ExploreActions } from '../actions/exploreActions'; +import { ExploreActions } from 'src/explore/actions/exploreActions'; import controlMap from './controls'; import './Control.less'; diff --git a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx index a53395aa4d3..8fafaa4bbbf 100644 --- a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx +++ b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx @@ -40,12 +40,13 @@ import Collapse from 'src/common/components/Collapse'; import { PluginContext } from 'src/components/DynamicPlugins'; import Loading from 'src/components/Loading'; -import { sectionsToRender } from 'src/explore/controlUtils'; +import { getSectionsToRender } from 'src/explore/controlUtils'; import { ExploreActions, exploreActions, } from 'src/explore/actions/exploreActions'; import { ExplorePageState } from 'src/explore/reducers/getInitialState'; +import { ChartState } from 'src/explore/types'; import ControlRow from './ControlRow'; import Control from './Control'; @@ -53,7 +54,8 @@ import Control from './Control'; export type ControlPanelsContainerProps = { actions: ExploreActions; datasource_type: DatasourceType; - exploreState: Record; + exploreState: ExplorePageState['explore']; + chart: ChartState; controls: Record; form_data: QueryFormData; isDatasourceMetaLoading: boolean; @@ -100,7 +102,7 @@ const ControlPanelsTabs = styled(Tabs)` } `; -class ControlPanelsContainer extends React.Component { +export class ControlPanelsContainer extends React.Component { // trigger updates to the component when async plugins load static contextType = PluginContext; @@ -111,7 +113,7 @@ class ControlPanelsContainer extends React.Component { + const { + controlOverrides = {}, + controlPanelSections = [], + } = controlPanelConfig; + const control = expandControlConfig( + findControlItem(controlPanelSections, controlKey), + controlOverrides, + ); + return control && 'config' in control ? control.config : control; + }, +); + +/** + * Find control item from control panel config. + */ +export function findControlItem( + controlPanelSections: ControlPanelSectionConfig[], + controlKey: string, +) { + return ( + controlPanelSections + .map(section => section.controlSetRows) + .flat(2) + .find( + control => + controlKey === control || + (control !== null && + typeof control === 'object' && + 'name' in control && + control.name === controlKey), + ) ?? null + ); +} + +export const getControlConfig = function getControlConfig( + controlKey: string, + vizType: string, +) { + const controlPanelConfig = getChartControlPanelRegistry().get(vizType) || {}; + return getMemoizedControlConfig(controlKey, controlPanelConfig); +}; diff --git a/superset-frontend/src/explore/controlUtils/getSectionsToRender.ts b/superset-frontend/src/explore/controlUtils/getSectionsToRender.ts new file mode 100644 index 00000000000..244114fc409 --- /dev/null +++ b/superset-frontend/src/explore/controlUtils/getSectionsToRender.ts @@ -0,0 +1,95 @@ +/** + * 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 memoizeOne from 'memoize-one'; +import { + DatasourceType, + getChartControlPanelRegistry, +} from '@superset-ui/core'; +import { + ControlPanelConfig, + expandControlConfig, +} from '@superset-ui/chart-controls'; + +import * as SECTIONS from 'src/explore/controlPanels/sections'; + +const getMemoizedSectionsToRender = memoizeOne( + (datasourceType: DatasourceType, controlPanelConfig: ControlPanelConfig) => { + const { + sectionOverrides = {}, + controlOverrides, + controlPanelSections = [], + } = controlPanelConfig; + + // default control panel sections + const sections = { ...SECTIONS }; + + // apply section overrides + Object.entries(sectionOverrides).forEach(([section, overrides]) => { + if (typeof overrides === 'object' && overrides.constructor === Object) { + sections[section] = { + ...sections[section], + ...overrides, + }; + } else { + sections[section] = overrides; + } + }); + + const { datasourceAndVizType } = sections; + + // list of datasource-specific controls that should be removed + const invalidControls = + datasourceType === 'table' + ? ['granularity', 'druid_time_origin'] + : ['granularity_sqla', 'time_grain_sqla']; + + return [datasourceAndVizType] + .concat(controlPanelSections) + .filter(section => !!section) + .map(section => { + const { controlSetRows } = section; + return { + ...section, + controlSetRows: + controlSetRows?.map(row => + row + .filter( + control => + typeof control !== 'string' || + !invalidControls.includes(control), + ) + .map(item => expandControlConfig(item, controlOverrides)), + ) || [], + }; + }); + }, +); + +/** + * Get the clean and processed control panel sections + */ +export function getSectionsToRender( + vizType: string, + datasourceType: DatasourceType, +) { + const controlPanelConfig = + // TODO: update `chartControlPanelRegistry` type to use ControlPanelConfig + (getChartControlPanelRegistry().get(vizType) as ControlPanelConfig) || {}; + return getMemoizedSectionsToRender(datasourceType, controlPanelConfig); +} diff --git a/superset-frontend/src/explore/controlUtils/index.js b/superset-frontend/src/explore/controlUtils/index.js index 4e1f0ac6ccc..b426754b708 100644 --- a/superset-frontend/src/explore/controlUtils/index.js +++ b/superset-frontend/src/explore/controlUtils/index.js @@ -16,12 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -import memoizeOne from 'memoize-one'; -import { getChartControlPanelRegistry } from '@superset-ui/core'; -import { expandControlConfig } from '@superset-ui/chart-controls'; -import * as SECTIONS from '../controlPanels/sections'; +import { getSectionsToRender } from './getSectionsToRender'; +import { getControlConfig } from './getControlConfig'; export * from './getFormDataFromControls'; +export * from './getControlConfig'; +export * from './getSectionsToRender'; export function validateControl(control, processedState) { const { validators } = control; @@ -38,44 +38,6 @@ export function validateControl(control, processedState) { return { ...control, validationErrors }; } -/** - * Find control item from control panel config. - */ -export function findControlItem(controlPanelSections, controlKey) { - return ( - controlPanelSections - .map(section => section.controlSetRows) - .flat(2) - .find( - control => - controlKey === control || - (control !== null && - typeof control === 'object' && - control.name === controlKey), - ) ?? null - ); -} - -const getMemoizedControlConfig = memoizeOne( - (controlKey, controlPanelConfig) => { - const { - controlOverrides = {}, - controlPanelSections = [], - } = controlPanelConfig; - - const control = expandControlConfig( - findControlItem(controlPanelSections, controlKey), - controlOverrides, - ); - return control?.config || control; - }, -); - -export const getControlConfig = function getControlConfig(controlKey, vizType) { - const controlPanelConfig = getChartControlPanelRegistry().get(vizType) || {}; - return getMemoizedControlConfig(controlKey, controlPanelConfig); -}; - function handleMissingChoice(control) { // If the value is not valid anymore based on choices, clear it const { value } = control; @@ -160,68 +122,9 @@ export function getControlState(controlKey, vizType, state, value) { ); } -const getMemoizedSectionsToRender = memoizeOne( - (datasourceType, controlPanelConfig) => { - const { - sectionOverrides = {}, - controlOverrides, - controlPanelSections = [], - } = controlPanelConfig; - - // default control panel sections - const sections = { ...SECTIONS }; - - // apply section overrides - Object.entries(sectionOverrides).forEach(([section, overrides]) => { - if (typeof overrides === 'object' && overrides.constructor === Object) { - sections[section] = { - ...sections[section], - ...overrides, - }; - } else { - sections[section] = overrides; - } - }); - - const { datasourceAndVizType } = sections; - // list of datasource-specific controls that should be removed - const invalidControls = - datasourceType === 'table' - ? ['granularity', 'druid_time_origin'] - : ['granularity_sqla', 'time_grain_sqla']; - - return [] - .concat(datasourceAndVizType, controlPanelSections) - .filter(section => !!section) - .map(section => { - const { controlSetRows } = section; - return { - ...section, - controlSetRows: - controlSetRows?.map(row => - row - .filter(control => !invalidControls.includes(control)) - .map(item => expandControlConfig(item, controlOverrides)), - ) || [], - }; - }); - }, -); - -/** - * Get the clean and processed control panel sections - */ -export const sectionsToRender = function sectionsToRender( - vizType, - datasourceType, -) { - const controlPanelConfig = getChartControlPanelRegistry().get(vizType) || {}; - return getMemoizedSectionsToRender(datasourceType, controlPanelConfig); -}; - export function getAllControlsState(vizType, datasourceType, state, formData) { const controlsState = {}; - sectionsToRender(vizType, datasourceType).forEach(section => + getSectionsToRender(vizType, datasourceType).forEach(section => section.controlSetRows.forEach(fieldsetRow => fieldsetRow.forEach(field => { if (field && field.config && field.name) {