feat(deckgl): add auto zoom option in deck gl multi layer (#37221)

This commit is contained in:
Ramiro Aquino Romero
2026-01-23 15:08:58 -04:00
committed by GitHub
parent 429d9b27f6
commit baaa8c5f54
4 changed files with 902 additions and 19 deletions

View File

@@ -0,0 +1,524 @@
/**
* 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 { render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import { supersetTheme, ThemeProvider } from '@apache-superset/core/ui';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import { DatasourceType, SupersetClient } from '@superset-ui/core';
import DeckMulti from './Multi';
import * as fitViewportModule from '../utils/fitViewport';
// Mock DeckGLContainer
jest.mock('../DeckGLContainer', () => ({
DeckGLContainerStyledWrapper: ({ viewport, layers }: any) => (
<div
data-test="deckgl-container"
data-viewport={JSON.stringify(viewport)}
data-layers-count={layers?.length || 0}
>
DeckGL Container Mock
</div>
),
}));
// Mock SupersetClient
jest.mock('@superset-ui/core', () => ({
...jest.requireActual('@superset-ui/core'),
SupersetClient: {
get: jest.fn(),
},
}));
const mockStore = configureStore({
reducer: {
dataMask: () => ({}),
},
});
const baseMockProps = {
formData: {
datasource: 'test_datasource',
viz_type: 'deck_multi',
deck_slices: [1, 2],
autozoom: false,
mapbox_style: 'mapbox://styles/mapbox/light-v9',
},
payload: {
data: {
slices: [
{
slice_id: 1,
form_data: {
viz_type: 'deck_scatter',
datasource: 'test_datasource',
},
},
{
slice_id: 2,
form_data: {
viz_type: 'deck_polygon',
datasource: 'test_datasource',
},
},
],
features: {
deck_scatter: [{ position: [0, 0] }],
deck_polygon: [
{
polygon: [
[1, 1],
[2, 2],
],
},
],
deck_path: [],
deck_grid: [],
deck_contour: [],
deck_heatmap: [],
deck_hex: [],
deck_arc: [],
deck_geojson: [],
deck_screengrid: [],
},
mapboxApiKey: 'test-key',
},
},
setControlValue: jest.fn(),
viewport: { longitude: 0, latitude: 0, zoom: 1 },
onAddFilter: jest.fn(),
height: 600,
width: 800,
datasource: {
id: 1,
type: DatasourceType.Table,
name: 'test_datasource',
columns: [],
metrics: [],
columnFormats: {},
currencyFormats: {},
verboseMap: {},
},
onSelect: jest.fn(),
};
const renderWithProviders = (component: React.ReactElement) =>
render(
<Provider store={mockStore}>
<ThemeProvider theme={supersetTheme}>{component}</ThemeProvider>
</Provider>,
);
describe('DeckMulti Autozoom Functionality', () => {
beforeEach(() => {
jest.clearAllMocks();
(SupersetClient.get as jest.Mock).mockResolvedValue({
json: {
data: {
features: [],
},
},
});
});
it('should NOT apply autozoom when autozoom is false', () => {
const fitViewportSpy = jest.spyOn(fitViewportModule, 'default');
const props = {
...baseMockProps,
formData: {
...baseMockProps.formData,
autozoom: false,
},
};
renderWithProviders(<DeckMulti {...props} />);
// fitViewport should not be called when autozoom is false
expect(fitViewportSpy).not.toHaveBeenCalled();
fitViewportSpy.mockRestore();
});
it('should apply autozoom when autozoom is true', () => {
const fitViewportSpy = jest.spyOn(fitViewportModule, 'default');
fitViewportSpy.mockReturnValue({
longitude: -122.4,
latitude: 37.8,
zoom: 10,
});
const props = {
...baseMockProps,
formData: {
...baseMockProps.formData,
autozoom: true,
},
};
renderWithProviders(<DeckMulti {...props} />);
// fitViewport should be called with the points from all layers
expect(fitViewportSpy).toHaveBeenCalledWith(
expect.objectContaining({
longitude: 0,
latitude: 0,
zoom: 1,
}),
expect.objectContaining({
width: 800,
height: 600,
points: expect.any(Array),
}),
);
fitViewportSpy.mockRestore();
});
it('should use adjusted viewport when autozoom is enabled', async () => {
const fitViewportSpy = jest.spyOn(fitViewportModule, 'default');
const adjustedViewport = {
longitude: -122.4,
latitude: 37.8,
zoom: 12,
};
fitViewportSpy.mockReturnValue(adjustedViewport);
const props = {
...baseMockProps,
formData: {
...baseMockProps.formData,
autozoom: true,
},
};
renderWithProviders(<DeckMulti {...props} />);
await waitFor(() => {
const container = screen.getByTestId('deckgl-container');
const viewportData = JSON.parse(
container.getAttribute('data-viewport') || '{}',
);
expect(viewportData.longitude).toBe(adjustedViewport.longitude);
expect(viewportData.latitude).toBe(adjustedViewport.latitude);
expect(viewportData.zoom).toBe(adjustedViewport.zoom);
});
fitViewportSpy.mockRestore();
});
it('should set zoom to 0 when calculated zoom is negative', async () => {
const fitViewportSpy = jest.spyOn(fitViewportModule, 'default');
fitViewportSpy.mockReturnValue({
longitude: 0,
latitude: 0,
zoom: -5, // negative zoom
});
const props = {
...baseMockProps,
formData: {
...baseMockProps.formData,
autozoom: true,
},
};
renderWithProviders(<DeckMulti {...props} />);
await waitFor(() => {
const container = screen.getByTestId('deckgl-container');
const viewportData = JSON.parse(
container.getAttribute('data-viewport') || '{}',
);
// Zoom should be 0, not negative
expect(viewportData.zoom).toBe(0);
});
fitViewportSpy.mockRestore();
});
it('should handle empty features gracefully when autozoom is enabled', () => {
const fitViewportSpy = jest.spyOn(fitViewportModule, 'default');
const props = {
...baseMockProps,
formData: {
...baseMockProps.formData,
autozoom: true,
},
payload: {
...baseMockProps.payload,
data: {
...baseMockProps.payload.data,
features: {
deck_scatter: [],
deck_polygon: [],
deck_path: [],
deck_grid: [],
deck_contour: [],
deck_heatmap: [],
deck_hex: [],
deck_arc: [],
deck_geojson: [],
deck_screengrid: [],
},
},
},
};
renderWithProviders(<DeckMulti {...props} />);
// fitViewport should not be called when there are no points
expect(fitViewportSpy).not.toHaveBeenCalled();
fitViewportSpy.mockRestore();
});
it('should collect points from all layer types when autozoom is enabled', () => {
const fitViewportSpy = jest.spyOn(fitViewportModule, 'default');
fitViewportSpy.mockReturnValue({
longitude: 0,
latitude: 0,
zoom: 10,
});
const props = {
...baseMockProps,
formData: {
...baseMockProps.formData,
autozoom: true,
},
payload: {
...baseMockProps.payload,
data: {
...baseMockProps.payload.data,
features: {
deck_scatter: [{ position: [1, 1] }, { position: [2, 2] }],
deck_polygon: [
{
polygon: [
[3, 3],
[4, 4],
],
},
],
deck_arc: [{ sourcePosition: [5, 5], targetPosition: [6, 6] }],
deck_path: [],
deck_grid: [],
deck_contour: [],
deck_heatmap: [],
deck_hex: [],
deck_geojson: [],
deck_screengrid: [],
},
},
},
};
renderWithProviders(<DeckMulti {...props} />);
expect(fitViewportSpy).toHaveBeenCalled();
const callArgs = fitViewportSpy.mock.calls[0];
const { points } = callArgs[1];
// Should have points from scatter (2), polygon (2), and arc (2) = 6 points total
expect(points.length).toBeGreaterThan(0);
fitViewportSpy.mockRestore();
});
it('should use original viewport when autozoom is disabled', async () => {
const fitViewportSpy = jest.spyOn(fitViewportModule, 'default');
const originalViewport = { longitude: -100, latitude: 40, zoom: 5 };
const props = {
...baseMockProps,
viewport: originalViewport,
formData: {
...baseMockProps.formData,
autozoom: false,
},
};
renderWithProviders(<DeckMulti {...props} />);
await waitFor(() => {
const container = screen.getByTestId('deckgl-container');
const viewportData = JSON.parse(
container.getAttribute('data-viewport') || '{}',
);
// Should use original viewport without modification
expect(viewportData.longitude).toBe(originalViewport.longitude);
expect(viewportData.latitude).toBe(originalViewport.latitude);
expect(viewportData.zoom).toBe(originalViewport.zoom);
});
// fitViewport should not have been called
expect(fitViewportSpy).not.toHaveBeenCalled();
fitViewportSpy.mockRestore();
});
it('should apply autozoom when autozoom is undefined (backward compatibility)', () => {
const fitViewportSpy = jest.spyOn(fitViewportModule, 'default');
fitViewportSpy.mockReturnValue({
longitude: -122.4,
latitude: 37.8,
zoom: 10,
});
const props = {
...baseMockProps,
formData: {
...baseMockProps.formData,
autozoom: undefined, // Simulating existing charts created before this feature
},
};
renderWithProviders(<DeckMulti {...props} />);
// fitViewport should be called for backward compatibility with existing charts
expect(fitViewportSpy).toHaveBeenCalledWith(
expect.objectContaining({
longitude: 0,
latitude: 0,
zoom: 1,
}),
expect.objectContaining({
width: 800,
height: 600,
points: expect.any(Array),
}),
);
fitViewportSpy.mockRestore();
});
it('should use adjusted viewport when autozoom is undefined', async () => {
const fitViewportSpy = jest.spyOn(fitViewportModule, 'default');
const adjustedViewport = {
longitude: -122.4,
latitude: 37.8,
zoom: 12,
};
fitViewportSpy.mockReturnValue(adjustedViewport);
const props = {
...baseMockProps,
formData: {
...baseMockProps.formData,
autozoom: undefined, // Simulating existing charts
},
};
renderWithProviders(<DeckMulti {...props} />);
await waitFor(() => {
const container = screen.getByTestId('deckgl-container');
const viewportData = JSON.parse(
container.getAttribute('data-viewport') || '{}',
);
// Should use adjusted viewport for backward compatibility
expect(viewportData.longitude).toBe(adjustedViewport.longitude);
expect(viewportData.latitude).toBe(adjustedViewport.latitude);
expect(viewportData.zoom).toBe(adjustedViewport.zoom);
});
fitViewportSpy.mockRestore();
});
});
describe('DeckMulti Component Rendering', () => {
beforeEach(() => {
jest.clearAllMocks();
(SupersetClient.get as jest.Mock).mockResolvedValue({
json: {
data: {
features: [],
},
},
});
});
it('should render DeckGLContainer', async () => {
renderWithProviders(<DeckMulti {...baseMockProps} />);
await waitFor(() => {
expect(screen.getByTestId('deckgl-container')).toBeInTheDocument();
});
});
it('should pass correct props to DeckGLContainer', async () => {
renderWithProviders(<DeckMulti {...baseMockProps} />);
await waitFor(() => {
const container = screen.getByTestId('deckgl-container');
const viewportData = JSON.parse(
container.getAttribute('data-viewport') || '{}',
);
expect(viewportData).toMatchObject({
longitude: baseMockProps.viewport.longitude,
latitude: baseMockProps.viewport.latitude,
zoom: baseMockProps.viewport.zoom,
});
});
});
it('should handle viewport changes', async () => {
const { rerender } = renderWithProviders(<DeckMulti {...baseMockProps} />);
// Wait for initial render
await waitFor(() => {
expect(screen.getByTestId('deckgl-container')).toBeInTheDocument();
});
const newViewport = { longitude: 10, latitude: 20, zoom: 8 };
const updatedProps = {
...baseMockProps,
viewport: newViewport,
formData: {
...baseMockProps.formData,
deck_slices: [1, 2, 3], // Change deck_slices to trigger loadLayers
},
};
rerender(
<Provider store={mockStore}>
<ThemeProvider theme={supersetTheme}>
<DeckMulti {...updatedProps} />
</ThemeProvider>
</Provider>,
);
await waitFor(() => {
const container = screen.getByTestId('deckgl-container');
const viewportData = JSON.parse(
container.getAttribute('data-viewport') || '{}',
);
expect(viewportData.longitude).toBe(newViewport.longitude);
expect(viewportData.latitude).toBe(newViewport.latitude);
});
});
});

View File

@@ -115,26 +115,33 @@ const DeckMulti = (props: DeckMultiProps) => {
const getAdjustedViewport = useCallback(() => {
let viewport = { ...props.viewport };
const points = [
...getPointsPolygon(props.payload.data.features.deck_polygon || []),
...getPointsPath(props.payload.data.features.deck_path || []),
...getPointsGrid(props.payload.data.features.deck_grid || []),
...getPointsScatter(props.payload.data.features.deck_scatter || []),
...getPointsContour(props.payload.data.features.deck_contour || []),
...getPointsHeatmap(props.payload.data.features.deck_heatmap || []),
...getPointsHex(props.payload.data.features.deck_hex || []),
...getPointsArc(props.payload.data.features.deck_arc || []),
...getPointsGeojson(props.payload.data.features.deck_geojson || []),
...getPointsScreengrid(props.payload.data.features.deck_screengrid || []),
];
if (props.formData) {
viewport = fitViewport(viewport, {
width: props.width,
height: props.height,
points,
});
// Default to autozoom enabled for backward compatibility (undefined treated as true)
if (props.formData.autozoom !== false) {
const points = [
...getPointsPolygon(props.payload.data.features.deck_polygon || []),
...getPointsPath(props.payload.data.features.deck_path || []),
...getPointsGrid(props.payload.data.features.deck_grid || []),
...getPointsScatter(props.payload.data.features.deck_scatter || []),
...getPointsContour(props.payload.data.features.deck_contour || []),
...getPointsHeatmap(props.payload.data.features.deck_heatmap || []),
...getPointsHex(props.payload.data.features.deck_hex || []),
...getPointsArc(props.payload.data.features.deck_arc || []),
...getPointsGeojson(props.payload.data.features.deck_geojson || []),
...getPointsScreengrid(
props.payload.data.features.deck_screengrid || [],
),
];
if (props.formData && points.length > 0) {
viewport = fitViewport(viewport, {
width: props.width,
height: props.height,
points,
});
}
}
if (viewport.zoom < 0) {
viewport.zoom = 0;
}

View File

@@ -0,0 +1,351 @@
/**
* 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 type {
ControlPanelSectionConfig,
ControlSetRow,
ControlSetItem,
} from '@superset-ui/chart-controls';
import controlPanel from './controlPanel';
test('controlPanel should have Map section', () => {
const mapSection = controlPanel.controlPanelSections.find(
(
section: ControlPanelSectionConfig | null,
): section is ControlPanelSectionConfig =>
section !== null && section.label === 'Map',
);
expect(mapSection).toBeDefined();
expect(mapSection?.expanded).toBe(true);
});
test('controlPanel should have Query section', () => {
const querySection = controlPanel.controlPanelSections.find(
(
section: ControlPanelSectionConfig | null,
): section is ControlPanelSectionConfig =>
section !== null && section.label === 'Query',
);
expect(querySection).toBeDefined();
expect(querySection?.expanded).toBe(true);
});
test('controlPanel Map section should include viewport control', () => {
const mapSection = controlPanel.controlPanelSections.find(
(
section: ControlPanelSectionConfig | null,
): section is ControlPanelSectionConfig =>
section !== null && section.label === 'Map',
);
// viewport is imported from Shared_DeckGL and included in controlSetRows
expect(mapSection?.controlSetRows).toBeDefined();
expect(mapSection?.controlSetRows.length).toBeGreaterThan(0);
});
test('controlPanel Map section should include autozoom control', () => {
const mapSection = controlPanel.controlPanelSections.find(
(
section: ControlPanelSectionConfig | null,
): section is ControlPanelSectionConfig =>
section !== null && section.label === 'Map',
);
// autozoom is imported from Shared_DeckGL and included in controlSetRows
expect(mapSection?.controlSetRows).toBeDefined();
expect(mapSection?.controlSetRows.length).toBeGreaterThan(0);
});
test('controlPanel should include deck_slices control with validation', () => {
const mapSection = controlPanel.controlPanelSections.find(
(
section: ControlPanelSectionConfig | null,
): section is ControlPanelSectionConfig =>
section !== null && section.label === 'Map',
);
const deckSlicesRow = mapSection?.controlSetRows.find((row: ControlSetRow) =>
row.some(
(control: ControlSetItem) =>
control &&
typeof control === 'object' &&
'name' in control &&
control.name === 'deck_slices',
),
);
const deckSlicesControl = deckSlicesRow?.find(
(control: ControlSetItem) =>
control &&
typeof control === 'object' &&
'name' in control &&
control.name === 'deck_slices',
) as any;
expect(deckSlicesControl).toBeDefined();
expect(deckSlicesControl?.config?.validators).toBeDefined();
expect(deckSlicesControl?.config?.validators.length).toBeGreaterThan(0);
expect(deckSlicesControl?.config?.multi).toBe(true);
expect(deckSlicesControl?.config?.type).toBe('SelectAsyncControl');
});
test('deck_slices control should have correct label and description', () => {
const mapSection = controlPanel.controlPanelSections.find(
(
section: ControlPanelSectionConfig | null,
): section is ControlPanelSectionConfig =>
section !== null && section.label === 'Map',
);
const deckSlicesRow = mapSection?.controlSetRows.find((row: ControlSetRow) =>
row.some(
(control: ControlSetItem) =>
control &&
typeof control === 'object' &&
'name' in control &&
control.name === 'deck_slices',
),
);
const deckSlicesControl = deckSlicesRow?.find(
(control: ControlSetItem) =>
control &&
typeof control === 'object' &&
'name' in control &&
control.name === 'deck_slices',
) as any;
expect(deckSlicesControl?.config?.label).toBeDefined();
expect(deckSlicesControl?.config?.description).toBeDefined();
expect(deckSlicesControl?.config?.placeholder).toBeDefined();
});
test('deck_slices control should have correct API endpoint', () => {
const mapSection = controlPanel.controlPanelSections.find(
(
section: ControlPanelSectionConfig | null,
): section is ControlPanelSectionConfig =>
section !== null && section.label === 'Map',
);
const deckSlicesRow = mapSection?.controlSetRows.find((row: ControlSetRow) =>
row.some(
(control: ControlSetItem) =>
control &&
typeof control === 'object' &&
'name' in control &&
control.name === 'deck_slices',
),
);
const deckSlicesControl = deckSlicesRow?.find(
(control: ControlSetItem) =>
control &&
typeof control === 'object' &&
'name' in control &&
control.name === 'deck_slices',
) as any;
expect(deckSlicesControl?.config?.dataEndpoint).toBe(
'api/v1/chart/?q=(filters:!((col:viz_type,opr:sw,value:deck)))',
);
});
test('deck_slices mutator should add index labels to selected charts', () => {
const mapSection = controlPanel.controlPanelSections.find(
(
section: ControlPanelSectionConfig | null,
): section is ControlPanelSectionConfig =>
section !== null && section.label === 'Map',
);
const deckSlicesRow = mapSection?.controlSetRows.find((row: ControlSetRow) =>
row.some(
(control: ControlSetItem) =>
control &&
typeof control === 'object' &&
'name' in control &&
control.name === 'deck_slices',
),
);
const deckSlicesControl = deckSlicesRow?.find(
(control: ControlSetItem) =>
control &&
typeof control === 'object' &&
'name' in control &&
control.name === 'deck_slices',
) as any;
const mockData = {
result: [
{ id: 1, slice_name: 'Chart A' },
{ id: 2, slice_name: 'Chart B' },
{ id: 3, slice_name: 'Chart C' },
],
};
const selectedIds = [2, 3];
const result = deckSlicesControl.config.mutator(mockData, selectedIds);
expect(result).toEqual([
{ value: 1, label: 'Chart A' },
{ value: 2, label: 'Chart B [1]' },
{ value: 3, label: 'Chart C [2]' },
]);
});
test('deck_slices mutator should handle empty result', () => {
const mapSection = controlPanel.controlPanelSections.find(
(
section: ControlPanelSectionConfig | null,
): section is ControlPanelSectionConfig =>
section !== null && section.label === 'Map',
);
const deckSlicesRow = mapSection?.controlSetRows.find((row: ControlSetRow) =>
row.some(
(control: ControlSetItem) =>
control &&
typeof control === 'object' &&
'name' in control &&
control.name === 'deck_slices',
),
);
const deckSlicesControl = deckSlicesRow?.find(
(control: ControlSetItem) =>
control &&
typeof control === 'object' &&
'name' in control &&
control.name === 'deck_slices',
) as any;
const mockData = {
result: undefined,
};
const result = deckSlicesControl.config.mutator(mockData, []);
expect(result).toEqual([]);
});
test('deck_slices mutator should handle undefined selected values', () => {
const mapSection = controlPanel.controlPanelSections.find(
(
section: ControlPanelSectionConfig | null,
): section is ControlPanelSectionConfig =>
section !== null && section.label === 'Map',
);
const deckSlicesRow = mapSection?.controlSetRows.find((row: ControlSetRow) =>
row.some(
(control: ControlSetItem) =>
control &&
typeof control === 'object' &&
'name' in control &&
control.name === 'deck_slices',
),
);
const deckSlicesControl = deckSlicesRow?.find(
(control: ControlSetItem) =>
control &&
typeof control === 'object' &&
'name' in control &&
control.name === 'deck_slices',
) as any;
const mockData = {
result: [
{ id: 1, slice_name: 'Chart A' },
{ id: 2, slice_name: 'Chart B' },
],
};
const result = deckSlicesControl.config.mutator(mockData, undefined);
expect(result).toEqual([
{ value: 1, label: 'Chart A' },
{ value: 2, label: 'Chart B' },
]);
});
test('deck_slices mutator should preserve order based on selection', () => {
const mapSection = controlPanel.controlPanelSections.find(
(
section: ControlPanelSectionConfig | null,
): section is ControlPanelSectionConfig =>
section !== null && section.label === 'Map',
);
const deckSlicesRow = mapSection?.controlSetRows.find((row: ControlSetRow) =>
row.some(
(control: ControlSetItem) =>
control &&
typeof control === 'object' &&
'name' in control &&
control.name === 'deck_slices',
),
);
const deckSlicesControl = deckSlicesRow?.find(
(control: ControlSetItem) =>
control &&
typeof control === 'object' &&
'name' in control &&
control.name === 'deck_slices',
) as any;
const mockData = {
result: [
{ id: 1, slice_name: 'Chart A' },
{ id: 2, slice_name: 'Chart B' },
{ id: 3, slice_name: 'Chart C' },
{ id: 4, slice_name: 'Chart D' },
],
};
// Select in specific order: 3, 1, 4
const selectedIds = [3, 1, 4];
const result = deckSlicesControl.config.mutator(mockData, selectedIds);
expect(result).toEqual([
{ value: 1, label: 'Chart A [2]' }, // second in selection
{ value: 2, label: 'Chart B' }, // not selected
{ value: 3, label: 'Chart C [1]' }, // first in selection
{ value: 4, label: 'Chart D [3]' }, // third in selection
]);
});
test('Query section should include adhoc_filters control', () => {
const querySection = controlPanel.controlPanelSections.find(
(
section: ControlPanelSectionConfig | null,
): section is ControlPanelSectionConfig =>
section !== null && section.label === 'Query',
);
const hasAdhocFilters = querySection?.controlSetRows.some(
(row: ControlSetRow) => row.includes('adhoc_filters'),
);
expect(hasAdhocFilters).toBe(true);
});

View File

@@ -18,7 +18,7 @@
*/
import { t } from '@apache-superset/core';
import { validateNonEmpty } from '@superset-ui/core';
import { viewport, mapboxStyle } from '../utilities/Shared_DeckGL';
import { viewport, mapboxStyle, autozoom } from '../utilities/Shared_DeckGL';
export default {
controlPanelSections: [
@@ -28,6 +28,7 @@ export default {
controlSetRows: [
[mapboxStyle],
[viewport],
[autozoom],
[
{
name: 'deck_slices',