You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by jl...@apache.org on 2026/03/12 06:10:28 UTC
(superset) 07/09: Merge remote-tracking branch 'origin/master' into alert-report-test-gaps
This is an automated email from the ASF dual-hosted git repository.
jli pushed a commit to branch alert-report-test-gaps
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 7136c04c3fdc3064183c30e65d00579cf1e892f2
Merge: 1d762b94529 a6c0d6321f6
Author: Joe Li <jo...@preset.io>
AuthorDate: Fri Mar 6 13:44:32 2026 -0800
Merge remote-tracking branch 'origin/master' into alert-report-test-gaps
# Conflicts:
# superset-frontend/src/features/alerts/AlertReportModal.test.tsx
.devcontainer/devcontainer.json | 2 +-
.github/workflows/docker.yml | 2 +-
.gitignore | 2 +-
RESOURCES/INTHEWILD.yaml | 3 +
UPDATING.md | 8 +-
docs/.gitignore | 8 +-
docs/.nvmrc | 2 +-
docs/admin_docs/configuration/alerts-reports.mdx | 14 +
docs/admin_docs/configuration/cache.mdx | 16 +-
docs/babel.config.js | 32 -
docs/developer_docs/api.mdx | 36 +-
docs/developer_docs/extensions/architecture.md | 66 +-
.../developer_docs/extensions/components/alert.mdx | 6 +-
.../developer_docs/extensions/components/index.mdx | 8 +-
.../extensions/contribution-types.md | 255 ++-
docs/developer_docs/extensions/dependencies.md | 6 +-
docs/developer_docs/extensions/development.md | 210 +-
.../extensions/extension-points/editors.md | 49 +-
.../extensions/extension-points/sqllab.md | 173 +-
docs/developer_docs/extensions/mcp-server.md | 679 ++++++
docs/developer_docs/extensions/mcp.md | 6 +-
docs/developer_docs/extensions/overview.md | 6 +-
docs/developer_docs/extensions/quick-start.md | 164 +-
docs/developer_docs/extensions/registry.md | 14 +-
docs/developer_docs/extensions/tasks.md | 14 +-
docs/developer_docs/sidebars.js | 1 +
docs/docusaurus.config.ts | 28 +
docs/package.json | 17 +-
docs/scripts/generate-database-docs.mjs | 8 +-
docs/scripts/generate-extension-components.mjs | 676 ------
docs/scripts/generate-if-changed.mjs | 307 +++
docs/scripts/generate-superset-components.mjs | 241 ++-
docs/sidebarTutorials.js | 11 -
docs/src/components/StorybookWrapper.jsx | 4 +-
docs/src/components/databases/DatabaseIndex.tsx | 1 +
.../components/ui-components/ComponentIndex.tsx | 262 +++
.../src/components/ui-components}/index.ts | 6 +-
.../src/components/ui-components/types.ts | 36 +-
docs/src/data/databases.json | 12 +-
docs/src/shims/superset-ui-core.ts | 57 +
docs/src/theme/Playground/Preview/index.tsx | 2 +-
docs/src/theme/ReactLiveScope/index.tsx | 2 +-
docs/src/types/apache-superset-core/index.d.ts | 6 +-
docs/src/webpack.extend.ts | 19 +-
.../{sql-snippets.png => editor-snippets.png} | Bin
docs/tsconfig.json | 7 +-
docs/yarn.lock | 1288 ++++++++---
pyproject.toml | 8 +-
requirements/base.in | 4 +-
requirements/base.txt | 16 +-
requirements/development.txt | 26 +-
superset-core/README.md | 4 +-
superset-core/src/superset_core/api/rest_api.py | 72 -
.../src/superset_core/{api => common}/__init__.py | 0
.../src/superset_core/{api => common}/daos.py | 82 +-
.../src/superset_core/{api => common}/models.py | 192 +-
.../src/superset_core/extensions/types.py | 109 +-
superset-core/src/superset_core/mcp/__init__.py | 145 --
.../mcp/{__init__.py => decorators.py} | 2 +-
.../src/superset_core/{api => queries}/__init__.py | 0
superset-core/src/superset_core/queries/daos.py | 63 +
superset-core/src/superset_core/queries/models.py | 79 +
.../src/superset_core/{api => queries}/query.py | 4 +-
.../src/superset_core/{api => queries}/types.py | 0
.../superset_core/{api => rest_api}/__init__.py | 0
.../{api/__init__.py => rest_api/api.py} | 16 +
.../src/superset_core/rest_api/decorators.py | 103 +
.../src/superset_core/{api => tasks}/__init__.py | 0
superset-core/src/superset_core/tasks/daos.py | 76 +
.../src/superset_core/tasks/decorators.py | 152 ++
superset-core/src/superset_core/tasks/models.py | 169 ++
.../superset_core/{api/tasks.py => tasks/types.py} | 128 +-
superset-embedded-sdk/.nvmrc | 2 +-
.../src/superset_extensions_cli/cli.py | 144 +-
.../templates/backend/pyproject.toml.j2 | 7 +
.../templates/backend/src/package/__init__.py.j2 | 0
.../templates/extension.json.j2 | 20 -
.../templates/frontend/src/index.tsx.j2 | 17 +-
.../templates/frontend/webpack.config.js.j2 | 3 +-
.../src/superset_extensions_cli/types.py | 4 +-
.../src/superset_extensions_cli/utils.py | 2 +-
superset-extensions-cli/tests/conftest.py | 9 +-
superset-extensions-cli/tests/test_cli_build.py | 237 +-
superset-extensions-cli/tests/test_cli_bundle.py | 5 +-
superset-extensions-cli/tests/test_cli_init.py | 32 +-
superset-extensions-cli/tests/test_cli_validate.py | 14 +-
.../tests/test_name_transformations.py | 11 +-
superset-extensions-cli/tests/test_templates.py | 55 +-
superset-frontend/.eslintrc.js | 41 -
superset-frontend/.nvmrc | 2 +-
superset-frontend/.storybook/preview.jsx | 2 +-
.../.storybook/shared/ResizableChartDemo.tsx | 2 +-
.../.storybook/shared/ResizablePanel.tsx | 2 +-
superset-frontend/.storybook/shared/VerifyCORS.tsx | 2 +-
.../e2e/dashboard/_skip.nativeFilters.test.ts | 4 +-
.../cypress/e2e/dashboard_list/filter.test.ts | 47 -
.../cypress/e2e/dashboard_list/list.test.ts | 279 ---
superset-frontend/oxlint.json | 4 +-
superset-frontend/package-lock.json | 2282 ++++++++------------
superset-frontend/package.json | 24 +-
.../packages/generator-superset/package.json | 2 +-
.../packages/superset-core/package.json | 62 +
.../superset-core/src/api/contributions.ts | 147 --
.../packages/superset-core/src/api/index.ts | 42 -
.../authentication.ts => authentication/index.ts} | 0
.../src/{api/commands.ts => commands/index.ts} | 51 +-
.../src/{api/core.ts => common/index.ts} | 74 +-
.../{ui => }/components/Alert/Alert.stories.tsx | 0
.../src/{ui => }/components/Alert/Alert.test.tsx | 0
.../src/{ui => }/components/Alert/index.tsx | 0
.../superset-core/src/{ui => }/components/index.ts | 0
.../superset-core/src/contributions/index.ts | 85 +
.../src/{api/editors.ts => editors/index.ts} | 105 +-
.../src/{api/extensions.ts => extensions/index.ts} | 2 +-
.../packages/superset-core/src/index.ts | 16 +-
.../packages/superset-core/src/menus/index.ts | 104 +
.../src/{api/sqlLab.ts => sqlLab/index.ts} | 4 +-
.../superset-core/src/{ui => }/testing.tsx | 0
.../src/{ui => }/theme/GlobalStyles.tsx | 11 +
.../src/{ui => }/theme/Theme.test.tsx | 73 +
.../superset-core/src/{ui => }/theme/Theme.tsx | 14 +
.../src/{ui => }/theme/exampleThemes.ts | 0
.../superset-core/src/{ui => }/theme/index.tsx | 0
.../superset-core/src/{ui => }/theme/types.ts | 0
.../src/{ui => }/theme/utils/index.ts | 0
.../src/{ui => }/theme/utils/themeUtils.test.ts | 0
.../src/{ui => }/theme/utils/themeUtils.ts | 0
.../src/{ui => }/theme/utils/utils.test.ts | 0
.../src/{ui => }/translation/README.md | 0
.../src/{ui => }/translation/Translator.ts | 2 +-
.../{ui => }/translation/TranslatorSingleton.ts | 0
.../src/{ui => }/translation/index.ts | 0
.../src/{ui => }/translation/types/index.ts | 0
.../src/{ui => }/translation/types/jed.ts | 0
.../src/{spec => }/utils/logging.test.ts | 4 +-
.../packages/superset-core/src/views/index.ts | 90 +
.../src/components/CertifiedIconWithTooltip.tsx | 4 +-
.../src/components/ColumnOption.tsx | 2 +-
.../components/ColumnTypeLabel/ColumnTypeLabel.tsx | 6 +-
.../src/components/ControlHeader.tsx | 4 +-
.../src/components/ControlSubSectionHeader.tsx | 2 +-
.../src/components/MetricOption.tsx | 2 +-
.../src/components/SQLPopover.tsx | 4 +-
.../src/components/labelUtils.tsx | 4 +-
.../superset-ui-chart-controls/src/constants.ts | 4 +-
.../superset-ui-chart-controls/src/fixtures.ts | 2 +-
.../src/sections/advancedAnalytics.tsx | 2 +-
.../src/sections/annotationsAndLayers.tsx | 2 +-
.../src/sections/chartTitle.tsx | 2 +-
.../src/sections/echartsTimeSeriesQuery.tsx | 2 +-
.../src/sections/forecastInterval.tsx | 2 +-
.../src/sections/matrixify.tsx | 2 +-
.../src/sections/sections.tsx | 2 +-
.../src/sections/timeComparison.tsx | 2 +-
.../components/RadioButtonControl.tsx | 2 +-
.../src/shared-controls/customControls.tsx | 4 +-
.../src/shared-controls/dndControls.tsx | 4 +-
.../src/shared-controls/matrixifyControls.tsx | 2 +-
.../src/shared-controls/mixins.tsx | 2 +-
.../src/shared-controls/sharedControls.tsx | 17 +-
.../superset-ui-chart-controls/src/types.ts | 3 +-
.../src/utils/D3Formatting.ts | 2 +-
.../src/utils/checkColumnType.ts | 2 +-
.../src/utils/columnChoices.ts | 2 +-
.../src/utils/getColorFormatters.ts | 2 +-
.../src/utils/isSortable.ts | 2 +-
.../src/utils/metricColumnFilter.test.ts | 8 +
.../test/components/ColumnOption.test.tsx | 2 +-
.../test/components/ColumnTypeLabel.test.tsx | 2 +-
.../test/shared-controls/customControls.test.tsx | 2 +-
.../test/utils/checkColumnType.test.ts | 2 +-
.../test/utils/columnChoices.test.tsx | 2 +-
.../test/utils/getColorFormatters.test.ts | 43 +-
.../test/utils/getTemporalColumns.test.ts | 2 +-
.../test/utils/isSortable.test.ts | 2 +-
.../packages/superset-ui-core/package.json | 2 +-
.../src/chart/components/FallbackComponent.tsx | 4 +-
.../Matrixify/MatrixifyGridCell.test.tsx | 2 +-
.../components/Matrixify/MatrixifyGridCell.tsx | 4 +-
.../Matrixify/MatrixifyGridGenerator.test.ts | 120 +
.../components/Matrixify/MatrixifyGridGenerator.ts | 8 +-
.../Matrixify/MatrixifyGridRenderer.test.tsx | 4 +-
.../components/Matrixify/MatrixifyGridRenderer.tsx | 2 +-
.../src/chart/components/NoResultsComponent.tsx | 3 +-
.../src/chart/components/StatefulChart.tsx | 2 +-
.../src/chart/components/SuperChartCore.tsx | 2 +-
.../src/chart/models/ChartProps.ts | 2 +-
.../src/chart/types/matrixify.mocks.test.ts | 11 +-
.../src/chart/types/matrixify.test.ts | 82 +
.../superset-ui-core/src/chart/types/matrixify.ts | 36 +-
.../src/components/ActionButton/index.tsx | 2 +-
.../src/components/AsyncAceEditor/index.tsx | 8 +-
.../AsyncAceEditor/useJsonValidation.test.ts | 25 +
.../src/components/Badge/index.tsx | 2 +-
.../src/components/Button/index.tsx | 2 +-
.../src/components/CachedLabel/TooltipContent.tsx | 2 +-
.../src/components/CachedLabel/index.tsx | 2 +-
.../superset-ui-core/src/components/Card/index.tsx | 2 +-
.../CertifiedBadge/CertifiedBadge.stories.tsx | 2 +-
.../src/components/CertifiedBadge/index.tsx | 4 +-
.../src/components/Checkbox/CheckboxIcons.tsx | 2 +-
.../src/components/CodeSyntaxHighlighter/index.tsx | 2 +-
.../src/components/Collapse/Collapse.tsx | 2 +-
.../components/Collapse/CollapseLabelInModal.tsx | 2 +-
.../components/ConfirmModal/ConfirmModal.test.tsx | 2 +-
.../src/components/ConfirmModal/index.tsx | 4 +-
.../src/components/CronPicker/index.tsx | 4 +-
.../src/components/DatePicker/index.tsx | 2 +-
.../src/components/DeleteModal/index.tsx | 4 +-
.../src/components/Divider/index.tsx | 2 +-
.../src/components/Dropdown/index.tsx | 2 +-
.../src/components/DropdownButton/index.tsx | 2 +-
.../DropdownContainer.stories.tsx | 2 +-
.../DropdownContainer/DropdownContainer.tsx | 4 +-
.../src/components/DynamicEditableTitle/index.tsx | 4 +-
.../src/components/EditableTitle/index.tsx | 4 +-
.../src/components/EmptyState/index.tsx | 4 +-
.../src/components/FaveStar/index.tsx | 4 +-
.../src/components/Flex/Flex.stories.tsx | 2 +-
.../src/components/Form/FormItem.tsx | 2 +-
.../src/components/Form/FormLabel.tsx | 2 +-
.../src/components/Form/LabeledErrorBoundInput.tsx | 4 +-
.../src/components/IconButton/index.tsx | 2 +-
.../components/IconTooltip/IconTooltip.stories.tsx | 2 +-
.../src/components/Icons/BaseIcon.tsx | 2 +-
.../src/components/Icons/Icons.stories.tsx | 2 +-
.../src/components/InfoTooltip/index.tsx | 4 +-
.../src/components/Label/index.tsx | 2 +-
.../components/Label/reusable/DatasetTypeLabel.tsx | 4 +-
.../components/Label/reusable/PublishedLabel.tsx | 4 +-
.../src/components/LastUpdated/index.tsx | 4 +-
.../src/components/List/List.test.tsx | 10 +
.../superset-ui-core/src/components/List/index.ts | 2 +-
.../src/components/ListViewCard/ImageLoader.tsx | 4 +-
.../src/components/ListViewCard/index.tsx | 2 +-
.../src/components/Loading/index.test.tsx | 2 +-
.../src/components/Loading/index.tsx | 4 +-
.../superset-ui-core/src/components/Menu/index.tsx | 2 +-
.../src/components/Metadata/index.tsx | 2 +-
.../src/components/MetadataBar/ContentConfig.tsx | 4 +-
.../components/MetadataBar/MetadataBar.stories.tsx | 2 +-
.../components/MetadataBar/MetadataBar.test.tsx | 2 +-
.../src/components/MetadataBar/MetadataBar.tsx | 2 +-
.../src/components/Modal/FormModal.tsx | 2 +-
.../src/components/Modal/Modal.tsx | 4 +-
.../src/components/PageHeaderWithActions/index.tsx | 4 +-
.../src/components/PopoverDropdown/index.tsx | 2 +-
.../src/components/PopoverSection/index.tsx | 2 +-
.../src/components/ProgressBar/index.tsx | 2 +-
.../src/components/Radio/Radio.stories.tsx | 2 +-
.../src/components/RefreshLabel/index.tsx | 2 +-
.../src/components/Select/AsyncSelect.tsx | 2 +-
.../src/components/Select/Select.tsx | 2 +-
.../src/components/Select/constants.test.ts | 49 +
.../src/components/Select/constants.ts | 2 +-
.../src/components/Select/styles.tsx | 5 +-
.../src/components/Select/utils.tsx | 2 +-
.../src/components/Table/VirtualTable.tsx | 2 +-
.../Table/cell-renderers/ActionCell/index.tsx | 2 +-
.../Table/cell-renderers/NullCell/index.tsx | 2 +-
.../Table/cell-renderers/NumericCell/index.tsx | 2 +-
.../header-renderers/HeaderWithRadioGroup.tsx | 2 +-
.../src/components/Table/index.tsx | 6 +-
.../Table/utils/InteractiveTableUtils.test.ts | 574 +++++
.../src/components/TableCollection/index.tsx | 2 +-
.../src/components/TableView/TableView.tsx | 2 +-
.../superset-ui-core/src/components/Tabs/Tabs.tsx | 2 +-
.../ThemedAgGridReact/ThemedAgGridReact.test.tsx | 8 +-
.../src/components/ThemedAgGridReact/index.tsx | 2 +-
.../src/components/Timer/index.tsx | 2 +-
.../src/components/TimezoneSelector/index.tsx | 2 +-
.../src/components/TruncatedList/index.tsx | 4 +-
.../src/components/Typography/index.tsx | 2 +-
.../src/components/UnsavedChangesModal/index.tsx | 2 +-
.../components/WarningIconWithTooltip/index.tsx | 2 +-
.../superset-ui-core/src/components/constants.ts | 2 +-
.../superset-ui-core/src/components/index.ts | 1 +
.../src/query/extractQueryFields.ts | 2 +-
.../src/query/getClientErrorObject.ts | 2 +-
.../superset-ui-core/src/query/types/Column.ts | 2 +-
.../superset-ui-core/src/query/types/Query.ts | 2 +-
.../src/query/types/QueryResponse.ts | 2 +-
.../packages/superset-ui-core/src/spec/index.tsx | 2 +-
.../src/style/stories/Theme.stories.tsx | 2 +-
.../src/types/react-syntax-highlighter.d.ts | 7 -
.../superset-ui-core/src/utils/featureFlags.ts | 2 +-
.../src/utils/rankedSearchCompare.test.ts | 20 +
.../packages/superset-ui-core/src/utils/tooltip.ts | 2 +-
.../List/List.test.tsx => utils/withLabel.test.ts} | 33 +-
.../src/validator/legacyValidateInteger.ts | 2 +-
.../src/validator/legacyValidateNumber.ts | 2 +-
.../src/validator/validateInteger.ts | 2 +-
.../src/validator/validateMapboxStylesUrl.ts | 2 +-
.../src/validator/validateMaxValue.ts | 2 +-
.../src/validator/validateNonEmpty.ts | 2 +-
.../src/validator/validateNumber.ts | 2 +-
.../src/validator/validateServerPagination.ts | 2 +-
.../validator/validateTimeComparisonRangeValues.ts | 2 +-
.../test/chart/clients/ChartClient.test.ts | 2 +-
.../test/chart/components/MockChartPlugins.tsx | 2 +-
.../test/chart/components/SuperChartCore.test.tsx | 2 +-
.../test/chart/models/ChartPlugin.test.tsx | 2 +-
.../test/chart/models/ChartProps.test.ts | 2 +-
.../Icons/AsyncIcon.integration.test.tsx | 2 +-
.../test/connection/SupersetClient.test.ts | 6 +
.../test/connection/SupersetClientClass.test.ts | 33 +
.../test/currency-format/CurrencyFormatter.test.ts | 78 +
.../test/currency-format/utils.test.ts | 193 ++
.../packages/superset-ui-core/test/fixtures.ts | 2 +-
.../test/query/extractQueryFields.test.ts | 2 +-
.../test/query/types/Column.test.ts | 14 +
.../test/query/types/Dashboard.test.ts | 33 +
.../test/utils/featureFlag.test.ts | 4 +-
.../superset-ui-core/test/validator/setup.ts | 2 +-
.../playwright/helpers/api/dashboard.ts | 170 ++
.../playwright/helpers/fixtures/testAssets.ts | 24 +-
.../playwright/pages/DashboardListPage.ts | 139 ++
.../experimental/dashboard/dashboard-list.spec.ts | 403 ++++
.../dashboard/dashboard-test-helpers.ts | 74 +
.../legacy-plugin-chart-calendar/src/Calendar.ts | 3 +-
.../src/ReactCalendar.tsx | 2 +-
.../src/controlPanel.ts | 2 +-
.../legacy-plugin-chart-calendar/src/index.ts | 2 +-
.../src/vendor/cal-heatmap.ts | 2 +-
.../legacy-plugin-chart-chord/src/ReactChord.tsx | 2 +-
.../legacy-plugin-chart-chord/src/controlPanel.ts | 2 +-
.../plugins/legacy-plugin-chart-chord/src/index.ts | 2 +-
.../src/ReactCountryMap.tsx | 2 +-
.../src/controlPanel.ts | 2 +-
.../legacy-plugin-chart-country-map/src/index.ts | 2 +-
.../src/stories/CountryMap.stories.tsx | 2 +-
.../src/HorizonChart.tsx | 2 +-
.../src/controlPanel.ts | 2 +-
.../legacy-plugin-chart-horizon/src/index.ts | 2 +-
.../src/controlPanel.ts | 2 +-
.../legacy-plugin-chart-map-box/src/index.ts | 2 +-
.../src/stories/MapBox.stories.tsx | 2 +-
.../src/stories/data.ts | 2 +-
.../src/PairedTTest.tsx | 2 +-
.../src/controlPanel.ts | 2 +-
.../legacy-plugin-chart-paired-t-test/src/index.ts | 2 +-
.../src/ReactParallelCoordinates.tsx | 2 +-
.../src/controlPanel.ts | 2 +-
.../src/index.ts | 2 +-
.../src/ReactPartition.tsx | 2 +-
.../src/controlPanel.tsx | 2 +-
.../legacy-plugin-chart-partition/src/index.ts | 2 +-
.../legacy-plugin-chart-rose/src/ReactRose.tsx | 2 +-
.../legacy-plugin-chart-rose/src/controlPanel.tsx | 2 +-
.../plugins/legacy-plugin-chart-rose/src/index.ts | 2 +-
.../src/ReactWorldMap.tsx | 2 +-
.../src/controlPanel.ts | 2 +-
.../legacy-plugin-chart-world-map/src/index.ts | 2 +-
.../src/DeckGLContainer.tsx | 2 +-
.../src/Multi/Multi.test.tsx | 2 +-
.../legacy-preset-chart-deckgl/src/Multi/Multi.tsx | 2 +-
.../src/Multi/controlPanel.ts | 2 +-
.../legacy-preset-chart-deckgl/src/Multi/index.ts | 2 +-
.../src/components/Legend.tsx | 2 +-
.../src/components/Tooltip.tsx | 2 +-
.../src/layers/Arc/controlPanel.ts | 2 +-
.../src/layers/Arc/index.ts | 2 +-
.../src/layers/Contour/Contour.tsx | 2 +-
.../src/layers/Contour/controlPanel.ts | 2 +-
.../src/layers/Contour/index.ts | 2 +-
.../src/layers/Geojson/controlPanel.ts | 2 +-
.../src/layers/Geojson/index.ts | 2 +-
.../src/layers/Grid/controlPanel.ts | 2 +-
.../src/layers/Grid/index.ts | 2 +-
.../src/layers/Heatmap/Heatmap.tsx | 2 +-
.../src/layers/Heatmap/controlPanel.ts | 2 +-
.../src/layers/Heatmap/index.ts | 2 +-
.../src/layers/Hex/controlPanel.ts | 2 +-
.../src/layers/Hex/index.ts | 2 +-
.../src/layers/Path/controlPanel.ts | 2 +-
.../src/layers/Path/index.ts | 2 +-
.../src/layers/Path/stories/Path.stories.tsx | 2 +-
.../src/layers/Polygon/Polygon.test.tsx | 2 +-
.../src/layers/Polygon/Polygon.tsx | 2 +-
.../src/layers/Polygon/controlPanel.ts | 2 +-
.../src/layers/Polygon/index.ts | 2 +-
.../src/layers/Scatter/Scatter.tsx | 2 +-
.../src/layers/Scatter/controlPanel.ts | 2 +-
.../src/layers/Scatter/index.ts | 2 +-
.../src/layers/Screengrid/Screengrid.tsx | 4 +-
.../src/layers/Screengrid/controlPanel.ts | 2 +-
.../src/layers/Screengrid/index.ts | 2 +-
.../src/layers/spatialUtils.test.ts | 2 +-
.../src/utilities/HandlebarsRenderer.tsx | 4 +-
.../src/utilities/Shared_DeckGL.tsx | 2 +-
.../src/utilities/TooltipTemplateControl.tsx | 4 +-
.../src/utilities/TooltipTemplateEditor.tsx | 2 +-
.../src/utilities/sharedDndControls.tsx | 2 +-
.../src/utilities/tooltipUtils.tsx | 2 +-
.../src/Bubble/controlPanel.ts | 2 +-
.../legacy-preset-chart-nvd3/src/Bubble/index.ts | 2 +-
.../src/Bullet/controlPanel.ts | 2 +-
.../legacy-preset-chart-nvd3/src/Bullet/index.ts | 2 +-
.../src/Compare/controlPanel.ts | 2 +-
.../legacy-preset-chart-nvd3/src/Compare/index.ts | 2 +-
.../legacy-preset-chart-nvd3/src/NVD3Controls.tsx | 2 +-
.../legacy-preset-chart-nvd3/src/NVD3Vis.ts | 2 +-
.../legacy-preset-chart-nvd3/src/ReactNVD3.tsx | 2 +-
.../src/TimePivot/controlPanel.ts | 2 +-
.../src/TimePivot/index.ts | 2 +-
.../src/vendor/superset/AnnotationTypes.ts | 2 +-
.../src/AgGridTable/components/CustomHeader.tsx | 2 +-
.../src/AgGridTable/components/Pagination.tsx | 2 +-
.../components/TimeComparisonVisibility.tsx | 2 +-
.../src/AgGridTable/index.tsx | 2 +-
.../src/AgGridTableChart.tsx | 4 +-
.../src/controlPanel.tsx | 4 +-
.../plugin-chart-ag-grid-table/src/index.ts | 2 +-
.../src/renderers/NumericCellRenderer.tsx | 6 +-
.../src/renderers/TextCellRenderer.tsx | 2 +-
.../plugin-chart-ag-grid-table/src/stories/data.ts | 2 +-
.../src/styles/index.tsx | 2 +-
.../src/transformProps.ts | 4 +-
.../src/utils/formatValue.ts | 2 +-
.../src/utils/useColDefs.ts | 2 +-
.../src/utils/useTableTheme.ts | 2 +-
.../test/controlPanel.test.tsx | 2 +-
.../test/utils/useColDefs.test.ts | 2 +-
.../src/CartodiagramPlugin.tsx | 2 +-
.../src/components/ChartLayer.tsx | 2 +-
.../src/components/ChartWrapper.tsx | 2 +-
.../src/plugin/controlPanel.ts | 2 +-
.../plugin-chart-cartodiagram/src/plugin/index.ts | 2 +-
.../plugins/plugin-chart-cartodiagram/src/types.ts | 2 +-
.../src/util/chartUtil.tsx | 2 +-
.../src/util/controlPanelUtil.tsx | 2 +-
.../test/plugin/transformProps.test.ts | 2 +-
.../plugins/plugin-chart-echarts/package.json | 4 +-
.../BigNumber/BigNumberPeriodOverPeriod/PopKPI.tsx | 4 +-
.../BigNumberPeriodOverPeriod/controlPanel.ts | 4 +-
.../BigNumber/BigNumberPeriodOverPeriod/index.ts | 2 +-
.../src/BigNumber/BigNumberTotal/controlPanel.ts | 4 +-
.../src/BigNumber/BigNumberTotal/index.ts | 2 +-
.../BigNumberTotal/transformProps.test.ts | 2 +-
.../src/BigNumber/BigNumberTotal/transformProps.ts | 2 +-
.../src/BigNumber/BigNumberViz.tsx | 4 +-
.../BigNumberWithTrendline/buildQuery.test.ts | 4 +-
.../BigNumber/BigNumberWithTrendline/buildQuery.ts | 2 +-
.../BigNumberWithTrendline/controlPanel.tsx | 2 +-
.../src/BigNumber/BigNumberWithTrendline/index.ts | 2 +-
.../BigNumberWithTrendline/transformProps.test.ts | 2 +-
.../BigNumberWithTrendline/transformProps.ts | 4 +-
.../src/BigNumber/sharedControls.ts | 2 +-
.../src/BoxPlot/controlPanel.ts | 2 +-
.../plugin-chart-echarts/src/BoxPlot/index.ts | 2 +-
.../src/Bubble/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Bubble/index.ts | 2 +-
.../src/Funnel/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Funnel/index.ts | 2 +-
.../src/Gantt/EchartsGantt.tsx | 2 +-
.../src/Gantt/controlPanel.tsx | 4 +-
.../plugin-chart-echarts/src/Gantt/index.ts | 2 +-
.../src/Gantt/transformProps.ts | 4 +-
.../plugin-chart-echarts/src/Gauge/constants.ts | 2 +-
.../src/Gauge/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Gauge/index.ts | 2 +-
.../src/Graph/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Graph/index.ts | 2 +-
.../src/Heatmap/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Heatmap/index.ts | 2 +-
.../src/Heatmap/transformProps.ts | 4 +-
.../src/Histogram/controlPanel.tsx | 4 +-
.../plugin-chart-echarts/src/Histogram/index.ts | 2 +-
.../src/MixedTimeseries/buildQuery.ts | 2 +-
.../src/MixedTimeseries/controlPanel.tsx | 3 +-
.../src/MixedTimeseries/index.ts | 2 +-
.../src/MixedTimeseries/transformProps.ts | 22 +-
.../plugin-chart-echarts/src/Pie/controlPanel.tsx | 2 +-
.../plugins/plugin-chart-echarts/src/Pie/index.ts | 2 +-
.../plugin-chart-echarts/src/Pie/transformProps.ts | 2 +-
.../src/Radar/controlPanel.tsx | 4 +-
.../plugin-chart-echarts/src/Radar/index.ts | 2 +-
.../src/Sankey/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Sankey/index.ts | 2 +-
.../src/Sunburst/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Sunburst/index.ts | 2 +-
.../src/Sunburst/transformProps.ts | 2 +-
.../src/Timeseries/Area/controlPanel.tsx | 3 +-
.../src/Timeseries/Area/index.ts | 2 +-
.../src/Timeseries/Regular/Bar/controlPanel.tsx | 5 +-
.../src/Timeseries/Regular/Bar/index.ts | 2 +-
.../src/Timeseries/Regular/Line/controlPanel.tsx | 3 +-
.../src/Timeseries/Regular/Line/index.ts | 2 +-
.../Timeseries/Regular/Scatter/controlPanel.tsx | 2 +-
.../src/Timeseries/Regular/Scatter/index.ts | 2 +-
.../Timeseries/Regular/SmoothLine/controlPanel.tsx | 3 +-
.../src/Timeseries/Regular/SmoothLine/index.ts | 2 +-
.../src/Timeseries/Step/controlPanel.tsx | 2 +-
.../src/Timeseries/Step/index.ts | 2 +-
.../src/Timeseries/buildQuery.ts | 6 +-
.../src/Timeseries/constants.ts | 2 +-
.../plugin-chart-echarts/src/Timeseries/index.ts | 2 +-
.../src/Timeseries/transformProps.ts | 152 +-
.../src/Timeseries/transformers.ts | 51 +-
.../plugin-chart-echarts/src/Timeseries/types.ts | 1 +
.../plugin-chart-echarts/src/Tree/controlPanel.tsx | 2 +-
.../plugins/plugin-chart-echarts/src/Tree/index.ts | 2 +-
.../src/Treemap/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Treemap/index.ts | 2 +-
.../src/Waterfall/constants.ts | 2 +-
.../src/Waterfall/controlPanel.tsx | 2 +-
.../plugin-chart-echarts/src/Waterfall/index.ts | 2 +-
.../src/Waterfall/transformProps.ts | 2 +-
.../plugin-chart-echarts/src/components/Echart.tsx | 12 +-
.../src/components/ExtraControls.tsx | 2 +-
.../plugins/plugin-chart-echarts/src/constants.ts | 2 +-
.../plugins/plugin-chart-echarts/src/controls.tsx | 30 +-
.../plugins/plugin-chart-echarts/src/index.ts | 3 +
.../src/utils/eChartOptionsSchema.ts | 827 +++++++
.../plugin-chart-echarts/src/utils/formatters.ts | 91 +-
.../src/utils/mergeCustomEChartOptions.test.ts | 163 ++
.../src/utils/mergeCustomEChartOptions.ts | 79 +
.../src/utils/safeEChartOptionsParser.test.ts | 525 +++++
.../src/utils/safeEChartOptionsParser.ts | 477 ++++
.../plugin-chart-echarts/src/utils/series.ts | 4 +-
.../src/utils/themeOverrides.test.ts | 623 ++++--
.../src/utils/themeOverrides.ts | 89 +
.../test/BigNumber/transformProps.test.ts | 2 +-
.../test/BoxPlot/transformProps.test.ts | 2 +-
.../test/Bubble/transformProps.test.ts | 2 +-
.../test/Funnel/transformProps.test.ts | 2 +-
.../test/Gantt/transformProps.test.ts | 2 +-
.../test/Gauge/transformProps.test.ts | 2 +-
.../test/Graph/transformProps.test.ts | 2 +-
.../test/Heatmap/transformProps.test.ts | 2 +-
.../test/MixedTimeseries/transformProps.test.ts | 2 +-
.../test/Pie/transformProps.test.ts | 2 +-
.../test/Radar/transformProps.test.ts | 2 +-
.../test/Timeseries/Bar/transformProps.test.ts | 285 ++-
.../test/Timeseries/Scatter/transformProps.test.ts | 12 +-
.../test/Timeseries/transformProps.test.ts | 6 +-
.../test/Timeseries/transformers.test.ts | 4 +-
.../test/Tree/transformProps.test.ts | 2 +-
.../test/Treemap/transformProps.test.ts | 2 +-
.../test/Waterfall/transformProps.test.ts | 2 +-
.../plugins/plugin-chart-echarts/test/helpers.ts | 2 +-
.../test/utils/formatters.test.ts | 174 +-
.../plugin-chart-echarts/test/utils/series.test.ts | 4 +-
.../test/utils/transformers.test.ts | 2 +-
.../plugin-chart-handlebars/src/Handlebars.tsx | 2 +-
.../src/components/Handlebars/HandlebarsViewer.tsx | 4 +-
.../src/plugin/controlPanel.tsx | 2 +-
.../src/plugin/controls/columns.tsx | 2 +-
.../src/plugin/controls/handlebarTemplate.tsx | 4 +-
.../src/plugin/controls/includeTime.ts | 2 +-
.../src/plugin/controls/metrics.tsx | 2 +-
.../src/plugin/controls/orderBy.tsx | 2 +-
.../src/plugin/controls/queryMode.tsx | 2 +-
.../src/plugin/controls/shared.ts | 2 +-
.../src/plugin/controls/style.tsx | 4 +-
.../plugin-chart-handlebars/src/plugin/index.ts | 2 +-
.../test/plugin/transformProps.test.ts | 2 +-
.../src/PivotTableChart.tsx | 4 +-
.../src/plugin/controlPanel.tsx | 2 +-
.../plugin-chart-pivot-table/src/plugin/index.ts | 2 +-
.../src/plugin/transformProps.ts | 2 +-
.../src/react-pivottable/Styles.ts | 2 +-
.../src/react-pivottable/TableRenderers.tsx | 2 +-
.../src/react-pivottable/utilities.ts | 2 +-
.../test/plugin/transformProps.test.ts | 2 +-
.../plugin-chart-table/src/DataTable/DataTable.tsx | 2 +-
.../src/DataTable/components/GlobalFilter.tsx | 2 +-
.../DataTable/components/SearchSelectDropdown.tsx | 2 +-
.../src/DataTable/components/SelectPageSize.tsx | 4 +-
.../src/DataTable/hooks/useSticky.tsx | 2 +-
.../plugins/plugin-chart-table/src/Styles.tsx | 2 +-
.../plugins/plugin-chart-table/src/TableChart.tsx | 7 +-
.../plugins/plugin-chart-table/src/consts.ts | 2 +-
.../plugin-chart-table/src/controlPanel.tsx | 4 +-
.../plugins/plugin-chart-table/src/index.ts | 2 +-
.../plugin-chart-table/src/stories/testData.ts | 2 +-
.../plugin-chart-table/src/transformProps.ts | 4 +-
.../plugin-chart-table/src/utils/formatValue.ts | 2 +-
.../plugin-chart-table/test/TableChart.test.tsx | 2 +-
.../plugin-chart-table/test/controlPanel.test.tsx | 2 +-
.../plugins/plugin-chart-table/test/testData.ts | 4 +-
.../plugin-chart-table/test/testHelpers.tsx | 2 +-
.../test/utils/formatValue.test.ts | 2 +-
.../plugins/plugin-chart-word-cloud/package.json | 2 +-
.../src/chart/WordCloud.tsx | 2 +-
.../src/plugin/controlPanel.tsx | 2 +-
.../ColorSchemeControl/ColorSchemeLabel.tsx | 2 +-
.../plugin/controls/ColorSchemeControl/index.tsx | 4 +-
.../src/plugin/controls/RotationControl.tsx | 2 +-
.../plugin-chart-word-cloud/src/plugin/index.ts | 2 +-
.../scripts/eslint-metrics-uploader.js | 144 --
.../scripts/oxlint-metrics-uploader.js | 7 +-
superset-frontend/spec/helpers/ProviderWrapper.tsx | 2 +-
superset-frontend/spec/helpers/shim.tsx | 2 +-
superset-frontend/spec/helpers/testing-library.tsx | 7 +-
.../src/SqlLab/SqlLabGlobalStyles.tsx | 2 +-
superset-frontend/src/SqlLab/actions/sqlLab.ts | 2 +-
.../src/SqlLab/components/App/index.tsx | 4 +-
.../SqlLab/components/AppLayout/AppLayout.test.tsx | 79 +-
.../src/SqlLab/components/AppLayout/index.tsx | 11 +-
.../src/SqlLab/components/ColumnElement/index.tsx | 4 +-
.../EditorAutoSync/EditorAutoSync.test.tsx | 6 +-
.../src/SqlLab/components/EditorAutoSync/index.tsx | 2 +-
.../src/SqlLab/components/EditorWrapper/index.tsx | 2 +-
.../components/EditorWrapper/useAnnotations.ts | 2 +-
.../SqlLab/components/EditorWrapper/useKeywords.ts | 2 +-
.../components/EstimateQueryCostButton/index.tsx | 5 +-
.../components/ExploreCtasResultsButton/index.tsx | 2 +-
.../components/ExploreResultsButton/index.tsx | 2 +-
.../src/SqlLab/components/HighlightedSql/index.tsx | 2 +-
.../components/KeyboardShortcutButton/index.tsx | 4 +-
.../src/SqlLab/components/QueryHistory/index.tsx | 4 +-
.../SqlLab/components/QueryLimitSelect/index.tsx | 2 +-
.../SqlLab/components/QueryStateLabel/index.tsx | 2 +-
.../src/SqlLab/components/QueryStatusBar/index.tsx | 3 +-
.../src/SqlLab/components/QueryTable/index.tsx | 4 +-
.../src/SqlLab/components/QueryTable/styles.ts | 2 +-
.../src/SqlLab/components/ResultSet/index.tsx | 7 +-
.../components/RunQueryActionButton/index.tsx | 4 +-
.../components/SaveDatasetActionButton/index.tsx | 2 +-
.../SqlLab/components/SaveDatasetModal/index.tsx | 4 +-
.../src/SqlLab/components/SaveQuery/index.tsx | 4 +-
.../components/ScheduleQueryButton/index.tsx | 4 +-
.../SqlLab/components/ShareSqlLabQuery/index.tsx | 4 +-
.../src/SqlLab/components/SouthPane/Results.tsx | 5 +-
.../src/SqlLab/components/SouthPane/index.tsx | 24 +-
.../src/SqlLab/components/SqlEditor/index.tsx | 5 +-
.../SqlLab/components/SqlEditorLeftBar/index.tsx | 4 +-
.../SqlLab/components/SqlEditorTabHeader/index.tsx | 9 +-
.../SqlLab/components/SqlEditorTopBar/index.tsx | 2 +-
.../SqlLab/components/StatusBar/StatusBar.test.tsx | 14 +-
.../src/SqlLab/components/StatusBar/index.tsx | 11 +-
.../SqlLab/components/TabbedSqlEditors/index.tsx | 4 +-
.../src/SqlLab/components/TableElement/index.tsx | 4 +-
.../TableExploreTree/TreeNodeRenderer.tsx | 3 +-
.../SqlLab/components/TableExploreTree/index.tsx | 3 +-
.../components/TableExploreTree/useTreeData.ts | 2 +-
.../src/SqlLab/components/TablePreview/index.tsx | 5 +-
.../components/TemplateParamsEditor/index.tsx | 4 +-
superset-frontend/src/SqlLab/constants.ts | 2 +-
superset-frontend/src/SqlLab/fixtures.ts | 2 +-
.../src/SqlLab/reducers/getInitialState.ts | 2 +-
superset-frontend/src/SqlLab/reducers/sqlLab.ts | 2 +-
.../src/SqlLab/utils/newQueryTabName.ts | 2 +-
.../DeckglLayerVisibilityCustomizationPlugin.tsx | 4 +-
.../components/DeckglLayerVisibility/index.ts | 2 +-
.../DynamicGroupBy/DynamicGroupByPlugin.tsx | 2 +-
.../components/DynamicGroupBy/controlPanel.ts | 2 +-
.../components/DynamicGroupBy/index.ts | 2 +-
.../TimeColumn/TimeColumnFilterPlugin.tsx | 4 +-
.../components/TimeColumn/controlPanel.ts | 2 +-
.../components/TimeColumn/index.ts | 2 +-
.../components/TimeGrain/TimeGrainFilterPlugin.tsx | 2 +-
.../components/TimeGrain/controlPanel.ts | 2 +-
.../components/TimeGrain/index.ts | 2 +-
.../src/chartCustomizations/components/common.ts | 2 +-
.../src/components/AlteredSliceTag/index.tsx | 2 +-
.../src/components/AuditInfo/index.tsx | 2 +-
superset-frontend/src/components/Chart/Chart.tsx | 7 +-
.../Chart/ChartContextMenu/ChartContextMenu.tsx | 4 +-
.../src/components/Chart/ChartRenderer.tsx | 4 +-
.../components/Chart/DisabledMenuItemTooltip.tsx | 2 +-
.../src/components/Chart/DrillBy/DrillByChart.tsx | 2 +-
.../src/components/Chart/DrillBy/DrillByModal.tsx | 5 +-
.../components/Chart/DrillBy/DrillBySubmenu.tsx | 4 +-
.../Chart/DrillBy/useDisplayModeToggle.tsx | 4 +-
.../Chart/DrillBy/useResultsTableView.tsx | 3 +-
.../Chart/DrillDetail/DrillDetailModal.tsx | 4 +-
.../Chart/DrillDetail/DrillDetailPane.tsx | 6 +-
.../Chart/DrillDetail/DrillDetailTableControls.tsx | 4 +-
.../src/components/Chart/DrillDetail/types.ts | 2 +-
.../components/Chart/MenuItemWithTruncation.tsx | 2 +-
.../src/components/Chart/chartAction.ts | 2 +-
.../src/components/Chart/chartReducer.ts | 2 +-
.../Chart/useDrillDetailMenuItems/index.tsx | 4 +-
.../src/components/CopyToClipboard/index.tsx | 4 +-
.../src/components/CrudThemeProvider.tsx | 2 +-
.../src/components/DatabaseSelector/index.tsx | 4 +-
.../src/components/DatabaseSelector/styles.ts | 2 +-
.../Datasource/ChangeDatasourceModal/index.tsx | 5 +-
.../Datasource/DatasourceModal/index.tsx | 5 +-
.../Datasource/FoldersEditor/TreeItem.styles.ts | 2 +-
.../Datasource/FoldersEditor/TreeItem.tsx | 5 +-
.../components/DragOverlayContent.tsx | 2 +-
.../components/FoldersToolbarComponent.tsx | 2 +-
.../FoldersEditor/components/ResetConfirmModal.tsx | 2 +-
.../Datasource/FoldersEditor/folderOperations.ts | 2 +-
.../Datasource/FoldersEditor/folderValidation.ts | 2 +-
.../FoldersEditor/hooks/useDragHandlers.ts | 2 +-
.../FoldersEditor/hooks/useItemHeights.ts | 2 +-
.../components/Datasource/FoldersEditor/styles.tsx | 2 +-
.../components/CollectionTable/index.tsx | 4 +-
.../DatasourceEditor/DatasourceEditor.tsx | 10 +-
.../components/DashboardLinksExternal/index.tsx | 2 +-
.../components/DatasetUsageTab/index.tsx | 4 +-
.../tests/DatasourceEditorCurrency.test.tsx | 2 +-
.../Datasource/components/Field/index.tsx | 2 +-
.../Datasource/components/Fieldset/index.tsx | 2 +-
.../src/components/Datasource/utils/index.ts | 2 +-
.../src/components/Datasource/utils/utils.test.tsx | 2 +-
.../src/components/DynamicPlugins/index.tsx | 2 +-
.../src/components/ErrorBoundary/index.tsx | 2 +-
.../ErrorMessage/BasicErrorAlert.test.tsx | 2 +-
.../components/ErrorMessage/BasicErrorAlert.tsx | 6 +-
.../ErrorMessage/DatabaseErrorMessage.tsx | 4 +-
.../ErrorMessage/DatasetNotFoundErrorMessage.tsx | 2 +-
.../src/components/ErrorMessage/ErrorAlert.tsx | 5 +-
.../ErrorMessage/ErrorMessageWithStackTrace.tsx | 2 +-
.../ErrorMessage/FrontendNetworkErrorMessage.tsx | 2 +-
.../ErrorMessage/InvalidSQLErrorMessage.tsx | 2 +-
.../src/components/ErrorMessage/IssueCode.tsx | 4 +-
.../ErrorMessage/MarshmallowErrorMessage.tsx | 2 +-
.../ErrorMessage/OAuth2RedirectMessage.tsx | 2 +-
.../ErrorMessage/ParameterErrorMessage.tsx | 4 +-
.../ErrorMessage/TimeoutErrorMessage.tsx | 4 +-
.../src/components/FilterableTable/utils.tsx | 2 +-
.../src/components/GridTable/Header.tsx | 4 +-
.../src/components/GridTable/HeaderMenu.tsx | 4 +-
.../src/components/GridTable/index.tsx | 2 +-
.../src/components/ImportModal/ErrorAlert.tsx | 5 +-
.../components/ImportModal/ImportErrorAlert.tsx | 2 +-
.../src/components/ImportModal/index.tsx | 4 +-
.../src/components/ImportModal/styles.ts | 2 +-
.../src/components/JsonModal/index.tsx | 2 +-
.../src/components/LastQueriedLabel/index.tsx | 4 +-
.../src/components/ListView/ActionsBar.tsx | 2 +-
.../src/components/ListView/CardCollection.tsx | 2 +-
.../src/components/ListView/CardSortSelect.tsx | 4 +-
.../src/components/ListView/CrossLinks.tsx | 2 +-
.../src/components/ListView/CrossLinksTooltip.tsx | 4 +-
.../src/components/ListView/Filters/Base.ts | 2 +-
.../src/components/ListView/Filters/DateRange.tsx | 2 +-
.../components/ListView/Filters/NumericalRange.tsx | 4 +-
.../src/components/ListView/Filters/Search.tsx | 4 +-
.../src/components/ListView/Filters/Select.tsx | 2 +-
.../src/components/ListView/Filters/index.tsx | 2 +-
.../src/components/ListView/ListView.tsx | 5 +-
.../src/components/MessageToasts/Toast.tsx | 9 +-
.../components/MessageToasts/ToastPresenter.tsx | 2 +-
.../components/Modal/CollapsibleModalSection.tsx | 2 +-
.../src/components/Modal/ModalFormField.tsx | 2 +-
.../src/components/Modal/StandardModal.tsx | 4 +-
.../src/components/Modal/useModalValidation.tsx | 4 +-
.../src/components/ModalTitleWithIcon/index.tsx | 2 +-
.../src/components/PanelToolbar/index.tsx | 42 +-
.../src/components/ResizableSidebar/index.tsx | 2 +-
.../src/components/RowCountLabel/index.tsx | 2 +-
.../components/SQLEditorWithValidation/index.tsx | 4 +-
.../StreamingExportModal/StreamingExportModal.tsx | 4 +-
.../src/components/TableSelector/index.tsx | 4 +-
superset-frontend/src/components/Tag/index.tsx | 2 +-
superset-frontend/src/components/Tag/utils.tsx | 2 +-
.../src/components/TagsList/index.tsx | 2 +-
.../ViewListExtension/ViewListExtension.test.tsx | 228 +-
.../src/components/ViewListExtension/index.tsx | 24 +-
superset-frontend/src/core/commands/index.ts | 44 +-
.../src/core/editors/AceEditorProvider.test.tsx | 2 +-
.../src/core/editors/AceEditorProvider.tsx | 7 +
superset-frontend/src/core/editors/EditorHost.tsx | 10 +-
.../src/core/editors/EditorProviders.test.ts | 88 +-
.../src/core/editors/EditorProviders.ts | 37 +-
superset-frontend/src/core/editors/index.ts | 69 +-
superset-frontend/src/core/extensions/index.ts | 6 +-
superset-frontend/src/core/index.ts | 16 +-
superset-frontend/src/core/menus/index.test.ts | 124 ++
superset-frontend/src/core/menus/index.ts | 83 +
superset-frontend/src/core/models.ts | 6 +-
superset-frontend/src/core/sqlLab/models.ts | 5 +-
superset-frontend/src/core/utils.ts | 2 +-
superset-frontend/src/core/views/index.test.ts | 112 +
superset-frontend/src/core/views/index.ts | 83 +
.../dashboard/actions/chartCustomizationActions.ts | 2 +-
.../src/dashboard/actions/dashboardInfo.ts | 2 +-
.../src/dashboard/actions/dashboardLayout.ts | 2 +-
.../src/dashboard/actions/dashboardState.ts | 4 +-
.../src/dashboard/actions/sliceEntities.ts | 2 +-
.../components/AddSliceCard/AddSliceCard.tsx | 4 +-
.../src/dashboard/components/AnchorLink/index.tsx | 2 +-
.../components/AutoRefreshIndicator/index.tsx | 3 +-
.../AutoRefreshStatus/StatusIndicatorDot.tsx | 2 +-
.../AutoRefreshStatus/StatusTooltipContent.tsx | 2 +-
.../components/BuilderComponentPane/index.tsx | 4 +-
.../components/ColorSchemeControlWrapper.tsx | 2 +-
.../src/dashboard/components/ColorSchemeSelect.tsx | 4 +-
.../components/CustomizationsBadge/index.tsx | 4 +-
.../src/dashboard/components/Dashboard.tsx | 2 +-
.../DashboardBuilder/DashboardBuilder.tsx | 22 +-
.../DashboardBuilder/DashboardWrapper.tsx | 2 +-
.../src/dashboard/components/DashboardGrid.tsx | 3 +-
.../dashboard/components/EmbeddedModal/index.tsx | 5 +-
.../components/FiltersBadge/DetailsPanel/index.tsx | 4 +-
.../FiltersBadge/FilterIndicator/index.tsx | 2 +-
.../dashboard/components/FiltersBadge/Styles.tsx | 2 +-
.../dashboard/components/FiltersBadge/index.tsx | 4 +-
.../src/dashboard/components/Header/index.tsx | 3 +-
.../components/Header/useDashboardMetadataBar.tsx | 2 +-
.../Header/useHeaderActionsDropdownMenu.tsx | 2 +-
.../src/dashboard/components/IconButton.tsx | 2 +-
.../src/dashboard/components/MissingChart.tsx | 2 +-
.../OverwriteConfirm/OverwriteConfirmModal.tsx | 4 +-
.../PropertiesModal/PropertiesModal.test.tsx | 2 +-
.../dashboard/components/PropertiesModal/index.tsx | 2 +-
.../PropertiesModal/sections/AccessSection.tsx | 2 +-
.../PropertiesModal/sections/AdvancedSection.tsx | 4 +-
.../PropertiesModal/sections/BasicInfoSection.tsx | 2 +-
.../sections/CertificationSection.tsx | 2 +-
.../PropertiesModal/sections/RefreshSection.tsx | 2 +-
.../PropertiesModal/sections/StylingSection.tsx | 5 +-
.../dashboard/components/PublishedStatus/index.tsx | 2 +-
.../dashboard/components/RefreshButton/index.tsx | 3 +-
.../RefreshFrequency/RefreshFrequencySelect.tsx | 4 +-
.../dashboard/components/RefreshIntervalModal.tsx | 4 +-
.../src/dashboard/components/SaveModal.tsx | 4 +-
.../src/dashboard/components/SliceAdder.tsx | 4 +-
.../src/dashboard/components/SliceHeader/index.tsx | 9 +-
.../ViewResultsModalTrigger.tsx | 4 +-
.../components/SliceHeaderControls/index.tsx | 4 +-
.../components/URLShortLinkButton/index.tsx | 4 +-
.../src/dashboard/components/dnd/DragDroppable.tsx | 11 +-
.../src/dashboard/components/dnd/DragHandle.tsx | 2 +-
.../components/filterscope/FilterScope.test.tsx | 2 +-
.../components/filterscope/FilterScopeModal.tsx | 2 +-
.../components/filterscope/FilterScopeSelector.tsx | 3 +-
.../filterscope/renderFilterScopeTreeNodes.tsx | 2 +-
.../dashboard/components/filterscope/treeIcons.tsx | 2 +-
.../components/gridComponents/Chart/Chart.test.tsx | 30 +
.../components/gridComponents/Chart/Chart.tsx | 4 +-
.../gridComponents/ChartHolder/ChartHolder.tsx | 2 +-
.../components/gridComponents/Column/Column.tsx | 5 +-
.../components/gridComponents/Divider/Divider.tsx | 2 +-
.../DynamicComponent/DynamicComponent.tsx | 2 +-
.../components/gridComponents/Header/Header.tsx | 2 +-
.../gridComponents/Markdown/Markdown.test.tsx | 26 +-
.../gridComponents/Markdown/Markdown.tsx | 3 +-
.../components/gridComponents/Row/Row.tsx | 29 +-
.../components/gridComponents/Tab/Tab.tsx | 3 +-
.../components/gridComponents/Tabs/Tabs.tsx | 3 +-
.../gridComponents/TabsRenderer/TabsRenderer.tsx | 2 +-
.../gridComponents/new/DraggableNewComponent.tsx | 2 +-
.../components/gridComponents/new/NewColumn.tsx | 2 +-
.../components/gridComponents/new/NewDivider.tsx | 2 +-
.../components/gridComponents/new/NewHeader.tsx | 2 +-
.../components/gridComponents/new/NewMarkdown.tsx | 2 +-
.../components/gridComponents/new/NewRow.tsx | 2 +-
.../components/gridComponents/new/NewTabs.tsx | 2 +-
.../components/menu/BackgroundStyleDropdown.tsx | 4 +-
.../components/menu/DownloadMenuItems/index.tsx | 3 +-
.../src/dashboard/components/menu/HoverMenu.tsx | 2 +-
.../components/menu/MarkdownModeDropdown.tsx | 2 +-
.../components/menu/ShareMenuItems/index.tsx | 3 +-
.../dashboard/components/menu/WithPopoverMenu.tsx | 17 +-
.../nativeFilters/ConfigModal/ModalFooter.tsx | 5 +-
.../nativeFilters/ConfigModal/SharedStyles.tsx | 2 +-
.../FilterBar/ActionButtons/index.tsx | 4 +-
.../FilterBar/CrossFilters/CrossFilter.tsx | 2 +-
.../FilterBar/CrossFilters/CrossFilterTag.tsx | 2 +-
.../FilterBar/CrossFilters/CrossFilterTitle.tsx | 4 +-
.../ScopingModal/ChartsScopingListPanel.tsx | 4 +-
.../CrossFilters/ScopingModal/ScopingModal.tsx | 2 +-
.../ScopingModal/ScopingModalContent.tsx | 2 +-
.../CrossFilters/ScopingModal/ScopingTreePanel.tsx | 5 +-
.../FilterBar/CrossFilters/VerticalCollapse.tsx | 4 +-
.../nativeFilters/FilterBar/CrossFilters/styles.ts | 2 +-
.../CustomizationsOutOfScopeCollapsible/index.tsx | 4 +-
.../FilterBar/FilterBarSettings/index.tsx | 4 +-
.../FilterControls/FilterControlShared.tsx | 2 +-
.../FilterBar/FilterControls/FilterControls.tsx | 9 +-
.../FilterControls/FilterDivider.stories.tsx | 2 +-
.../FilterBar/FilterControls/FilterDivider.tsx | 2 +-
.../FilterBar/FilterControls/FilterValue.tsx | 4 +-
.../FilterBar/FilterControls/GroupByFilterCard.tsx | 9 +-
.../FilterBar/FiltersDropdownContent/index.tsx | 2 +-
.../FiltersOutOfScopeCollapsible/index.tsx | 4 +-
.../nativeFilters/FilterBar/Header/index.tsx | 4 +-
.../nativeFilters/FilterBar/Horizontal.tsx | 4 +-
.../nativeFilters/FilterBar/Vertical.tsx | 4 +-
.../components/nativeFilters/FilterBar/index.tsx | 2 +-
.../nativeFilters/FilterBar/keyValue.tsx | 2 +-
.../nativeFilters/FilterCard/DependenciesRow.tsx | 4 +-
.../nativeFilters/FilterCard/NameRow.tsx | 2 +-
.../nativeFilters/FilterCard/ScopeRow.tsx | 4 +-
.../components/nativeFilters/FilterCard/Styles.ts | 2 +-
.../nativeFilters/FilterCard/TypeRow.tsx | 2 +-
.../nativeFilters/FilterCard/useFilterScope.ts | 2 +-
.../ConfigModalContent/ConfigModalContent.tsx | 2 +-
.../ConfigModalSidebar/ConfigModalSidebar.tsx | 240 +-
.../FiltersConfigModal/DividerConfigForm.tsx | 4 +-
.../FiltersConfigModal/DraggableFilter.test.tsx | 109 +-
.../FiltersConfigModal/DraggableFilter.tsx | 124 +-
.../FiltersConfigModal/FilterConfigPane.test.tsx | 26 +-
.../FiltersConfigModal/FilterConfigurePane.tsx | 2 +-
.../FiltersConfigModal/FilterTitleContainer.tsx | 106 +-
.../FiltersConfigModal/FilterTitlePane.tsx | 4 +-
.../FiltersConfigForm/CollapsibleControl.tsx | 2 +-
.../FiltersConfigForm/ColumnSelect.tsx | 2 +-
.../FiltersConfigForm/DatasetSelect.tsx | 2 +-
.../FiltersConfigForm/DefaultValue.tsx | 2 +-
.../FiltersConfigForm/DependencyList.tsx | 4 +-
.../FiltersConfigForm/FilterScope/FilterScope.tsx | 2 +-
.../FiltersConfigForm/FilterScope/ScopingTree.tsx | 2 +-
.../FiltersConfigForm/FilterScope/state.ts | 2 +-
.../FiltersConfigForm/FilterScope/utils.ts | 4 +-
.../FiltersConfigForm/FiltersConfigForm.tsx | 6 +-
.../FiltersConfigForm/RemovedFilter.tsx | 4 +-
.../FiltersConfigForm/constants.ts | 2 +-
.../FiltersConfigForm/getControlItemsMap.tsx | 4 +-
.../FiltersConfigModal/FiltersConfigForm/state.ts | 2 +-
.../FiltersConfigForm/utils.test.ts | 2 +-
.../FiltersConfigModal/FiltersConfigForm/utils.ts | 2 +-
.../FiltersConfigModal/FiltersConfigModal.test.tsx | 180 +-
.../FiltersConfigModal/FiltersConfigModal.tsx | 17 +-
.../Footer/CancelConfirmationAlert.tsx | 4 +-
.../FiltersConfigModal/Footer/Footer.tsx | 2 +-
.../FiltersConfigModal/ItemTitleContainer.tsx | 71 +-
.../FiltersConfigModal/ItemTitlePane.tsx | 2 +-
.../FiltersConfigModal/NewItemDropdown.tsx | 4 +-
.../hooks/useFilterOperations.ts | 2 +-
.../FiltersConfigModal/hooks/useModalSaveLogic.ts | 2 +-
.../nativeFilters/FiltersConfigModal/utils.ts | 2 +-
.../dashboard/components/nativeFilters/utils.ts | 2 +-
.../components/resizable/ResizableContainer.tsx | 2 +-
.../src/dashboard/containers/DashboardPage.tsx | 4 +-
.../src/dashboard/hooks/useDownloadScreenshot.ts | 4 +-
.../src/dashboard/reducers/sliceEntities.ts | 2 +-
superset-frontend/src/dashboard/styles.ts | 2 +-
superset-frontend/src/dashboard/types.ts | 2 +-
.../src/dashboard/util/backgroundStyleOptions.ts | 2 +-
.../src/dashboard/util/getFilterFieldNodesTree.ts | 2 +-
.../src/dashboard/util/getFilterScopeNodesTree.ts | 2 +-
.../src/dashboard/util/getSliceHeaderTooltip.tsx | 2 +-
.../src/dashboard/util/headerStyleOptions.ts | 2 +-
.../src/dashboard/util/newComponentFactory.ts | 2 +-
.../dashboard/util/updateComponentParentsList.ts | 2 +-
.../util/useFilterFocusHighlightStyles.ts | 2 +-
.../src/embedded/EmbeddedContextProviders.tsx | 2 +-
superset-frontend/src/embedded/index.tsx | 9 +-
superset-frontend/src/embedded/utils.ts | 2 +-
.../src/explore/actions/exploreActions.ts | 2 +-
.../src/explore/actions/saveModalActions.ts | 2 +-
.../src/explore/components/ChartPills.tsx | 2 +-
.../src/explore/components/Control.tsx | 2 +-
.../src/explore/components/ControlHeader.tsx | 4 +-
.../components/ControlPanelsContainer.test.tsx | 2 +-
.../explore/components/ControlPanelsContainer.tsx | 9 +-
.../explore/components/DataTableControl/index.tsx | 8 +-
.../DataTableControl/useTableColumns.test.ts | 2 +-
.../components/DataTablesPane/DataTablesPane.tsx | 4 +-
.../components/DataTableControls.tsx | 4 +-
.../components/ResultsPaneOnDashboard.tsx | 4 +-
.../DataTablesPane/components/SamplesPane.tsx | 6 +-
.../components/SingleQueryResultPane.tsx | 2 +-
.../DataTablesPane/components/useResultsPane.tsx | 4 +-
.../src/explore/components/DataTablesPane/types.ts | 2 +-
.../DatasourcePanelDragOption/index.tsx | 2 +-
.../DatasourcePanel/DatasourcePanelItem.tsx | 4 +-
.../components/DatasourcePanel/fixtures.tsx | 2 +-
.../explore/components/DatasourcePanel/index.tsx | 5 +-
.../DatasourcePanel/transformDatasourceFolders.ts | 2 +-
.../src/explore/components/EmbedCodeContent.tsx | 3 +-
.../src/explore/components/ExploreAlert.tsx | 2 +-
.../components/ExploreChartHeader/index.tsx | 5 +-
.../ExploreChartHeader/useExploreMetadataBar.tsx | 4 +-
.../explore/components/ExploreChartPanel/index.tsx | 5 +-
.../explore/components/ExploreContainer/index.tsx | 2 +-
.../explore/components/ExploreContentPopover.tsx | 2 +-
.../components/ExploreViewContainer/index.tsx | 5 +-
.../components/ExportToCSVDropdown/index.tsx | 4 +-
.../explore/components/PropertiesModal/index.tsx | 2 +-
.../explore/components/RunQueryButton/index.tsx | 4 +-
.../src/explore/components/SaveModal.tsx | 7 +-
.../AnnotationLayerControl/AnnotationLayer.tsx | 4 +-
.../AnnotationLayerControl/AnnotationTypes.ts | 2 +-
.../controls/AnnotationLayerControl/index.tsx | 4 +-
.../explore/components/controls/BoundsControl.tsx | 4 +-
.../components/controls/CheckboxControl.tsx | 2 +-
.../controls/CollectionControl/index.tsx | 4 +-
.../ColorBreakpointOption.tsx | 2 +-
.../ColorBreakpointPopoverControl.tsx | 4 +-
.../controls/ColorBreakpointsControl/index.tsx | 4 +-
.../ColorSchemeControl/ColorSchemeLabel.tsx | 2 +-
.../controls/ColorSchemeControl/index.tsx | 4 +-
.../ColumnConfigControl/ColumnConfigControl.tsx | 6 +-
.../ColumnConfigControl/ColumnConfigItem.tsx | 2 +-
.../ColumnConfigControl/ColumnConfigPopover.tsx | 2 +-
.../ControlForm/ControlFormItem.tsx | 2 +-
.../ColumnConfigControl/ControlForm/index.tsx | 2 +-
.../controls/ColumnConfigControl/constants.tsx | 4 +-
.../controls/ColumnConfigControl/types.ts | 2 +-
.../components/controls/ComparisonRangeLabel.tsx | 4 +-
.../ConditionalFormattingControl.tsx | 4 +-
.../FormattingPopoverContent.test.tsx | 2 +-
.../FormattingPopoverContent.tsx | 6 +-
.../ConditionalFormattingControl/constants.ts | 2 +-
.../controls/ConditionalFormattingControl/types.ts | 2 +-
.../controls/ContourControl/ContourOption.tsx | 4 +-
.../ContourControl/ContourPopoverControl.tsx | 4 +-
.../components/controls/ContourControl/index.tsx | 4 +-
.../controls/CurrencyControl/CurrencyControl.tsx | 4 +-
.../components/controls/CustomListItem/index.tsx | 2 +-
.../DatasourceControl/DatasourceControl.test.tsx | 268 +--
.../controls/DatasourceControl/index.tsx | 4 +-
.../controls/DateFilterControl/DateFilterLabel.tsx | 9 +-
.../DateFilterControl/components/AdvancedFrame.tsx | 2 +-
.../DateFilterControl/components/CalendarFrame.tsx | 2 +-
.../DateFilterControl/components/CommonFrame.tsx | 2 +-
.../components/CurrentCalendarFrame.tsx | 2 +-
.../DateFilterControl/components/CustomFrame.tsx | 2 +-
.../components/DateFunctionTooltip.tsx | 4 +-
.../DateFilterControl/components/DateLabel.tsx | 4 +-
.../controls/DateFilterControl/utils/constants.ts | 2 +-
.../DndColumnSelectControl/ColumnSelectPopover.tsx | 5 +-
.../ColumnSelectPopoverTrigger.tsx | 2 +-
.../DndAdhocFilterOption.tsx | 2 +-
.../DndColumnMetricSelect.tsx | 4 +-
.../DndColumnSelectControl/DndColumnSelect.tsx | 4 +-
.../DndColumnSelectPopoverTitle.tsx | 4 +-
.../DndFilterSelect.test.tsx | 2 +-
.../DndColumnSelectControl/DndFilterSelect.tsx | 3 +-
.../DndColumnSelectControl/DndMetricSelect.tsx | 6 +-
.../DndColumnSelectControl/DndSelectLabel.tsx | 2 +-
.../controls/DndColumnSelectControl/Option.tsx | 4 +-
.../DndColumnSelectControl/OptionWrapper.tsx | 2 +-
.../DndColumnSelectControl/useResizeButton.tsx | 2 +-
.../DndColumnSelectControl/utils/optionSelector.ts | 2 +-
.../FilterControl/AdhocFilterControl/index.tsx | 6 +-
.../FilterControl/AdhocFilterEditPopover/index.tsx | 6 +-
.../index.tsx | 4 +-
.../useAdvancedDataTypes.ts | 2 +-
.../AdhocFilterEditPopoverSqlTabContent.test.tsx | 84 +-
.../AdhocFilterEditPopoverSqlTabContent/index.tsx | 12 +-
.../utils/useDatePickerInAdhocFilter.tsx | 2 +-
.../controls/FixedOrMetricControl/index.tsx | 2 +-
.../components/controls/JSEditorControl.test.tsx | 125 ++
.../components/controls/JSEditorControl.tsx | 105 +
.../controls/LayerConfigsControl/FlatLayerTree.tsx | 4 +-
.../LayerConfigsControl/LayerConfigsControl.tsx | 4 +-
.../LayerConfigsPopoverContent.tsx | 4 +-
.../controls/MapViewControl/ExtentTag.tsx | 2 +-
.../controls/MapViewControl/MapViewControl.tsx | 4 +-
.../MapViewControl/MapViewPopoverContent.tsx | 4 +-
.../controls/MatrixifyDimensionControl.tsx | 2 +-
.../MetricControl/AdhocMetricEditPopover/index.tsx | 25 +-
.../MetricControl/AdhocMetricEditPopoverTitle.tsx | 4 +-
.../MetricControl/AdhocMetricPopoverTrigger.tsx | 2 +-
.../controls/MetricControl/MetricsControl.tsx | 2 +-
.../components/controls/NumberControl/index.tsx | 2 +-
.../components/controls/OptionControls/index.tsx | 4 +-
.../controls/SelectAsyncControl/index.tsx | 2 +-
.../explore/components/controls/SelectControl.tsx | 4 +-
.../explore/components/controls/SpatialControl.tsx | 2 +-
.../components/controls/TextAreaControl.tsx | 4 +-
.../controls/TimeSeriesColumnControl/index.tsx | 4 +-
.../src/explore/components/controls/ViewQuery.tsx | 4 +-
.../explore/components/controls/ViewQueryModal.tsx | 5 +-
.../components/controls/ViewQueryModalFooter.tsx | 2 +-
.../components/controls/ViewportControl.tsx | 2 +-
.../controls/VizTypeControl/FastVizSwitcher.tsx | 4 +-
.../components/controls/VizTypeControl/VizTile.tsx | 4 +-
.../controls/VizTypeControl/VizTypeGallery.tsx | 4 +-
.../controls/VizTypeControl/constants.tsx | 2 +-
.../components/controls/VizTypeControl/index.tsx | 4 +-
.../ZoomConfigControl/ZoomConfigControl.tsx | 4 +-
.../ZoomConfigControl/ZoomConfigsChart.tsx | 2 +-
.../src/explore/components/controls/index.ts | 2 +
.../components/controls/withAsyncVerification.tsx | 2 +-
.../src/explore/components/optionRenderers.tsx | 2 +-
.../DashboardsSubMenu.tsx | 4 +-
.../useExploreAdditionalActionsMenu/index.tsx | 3 +-
superset-frontend/src/explore/constants.ts | 2 +-
.../src/explore/controlPanels/Separator.ts | 2 +-
.../src/explore/controlPanels/sections.tsx | 2 +-
.../src/explore/controlUtils/controlUtils.test.tsx | 2 +-
.../src/explore/controlUtils/getColumnKeywords.tsx | 2 +-
superset-frontend/src/explore/controls.tsx | 2 +-
superset-frontend/src/explore/fixtures.tsx | 2 +-
.../src/extensions/ExtensionPlaceholder.tsx | 2 +-
.../src/extensions/ExtensionsContext.test.tsx | 150 --
.../src/extensions/ExtensionsContext.tsx | 93 -
.../src/extensions/ExtensionsContextUtils.test.ts | 74 -
.../src/extensions/ExtensionsContextUtils.ts | 32 -
.../src/extensions/ExtensionsList.test.tsx | 17 -
.../src/extensions/ExtensionsList.tsx | 31 +-
.../src/extensions/ExtensionsLoader.test.ts | 113 +
.../src/extensions/ExtensionsLoader.ts | 161 ++
.../src/extensions/ExtensionsManager.test.ts | 572 -----
.../src/extensions/ExtensionsManager.ts | 355 ---
.../src/extensions/ExtensionsStartup.test.tsx | 74 +-
.../src/extensions/ExtensionsStartup.tsx | 56 +-
.../src/features/alerts/AlertReportModal.test.tsx | 463 +++-
.../src/features/alerts/AlertReportModal.tsx | 49 +-
.../features/alerts/buildErrorTooltipMessage.tsx | 2 +-
.../alerts/components/AlertReportCronScheduler.tsx | 4 +-
.../features/alerts/components/AlertStatusIcon.tsx | 4 +-
.../alerts/components/NotificationMethod.tsx | 4 +-
.../features/alerts/components/RecipientIcon.tsx | 2 +-
superset-frontend/src/features/alerts/types.ts | 7 +
.../src/features/allEntities/AllEntitiesTable.tsx | 4 +-
.../annotationLayers/AnnotationLayerModal.tsx | 4 +-
.../src/features/annotations/AnnotationModal.tsx | 4 +-
.../src/features/charts/ChartCard.tsx | 4 +-
.../src/features/cssTemplates/CssTemplateModal.tsx | 4 +-
.../src/features/dashboards/DashboardCard.tsx | 2 +-
.../DatabaseConnectionForm/CommonParameters.tsx | 4 +-
.../DatabaseConnectionForm/EncryptedField.tsx | 4 +-
.../DatabaseConnectionForm/OAuth2ClientField.tsx | 2 +-
.../DatabaseConnectionForm/TableCatalog.tsx | 4 +-
.../DatabaseConnectionForm/ValidatedInputField.tsx | 2 +-
.../DatabaseModal/DatabaseConnectionForm/index.tsx | 2 +-
.../databases/DatabaseModal/ExtraOptions.test.tsx | 2 +-
.../databases/DatabaseModal/ExtraOptions.tsx | 4 +-
.../databases/DatabaseModal/ModalHeader.tsx | 10 +-
.../databases/DatabaseModal/SSHTunnelForm.tsx | 4 +-
.../databases/DatabaseModal/SSHTunnelSwitch.tsx | 4 +-
.../databases/DatabaseModal/SqlAlchemyForm.tsx | 4 +-
.../src/features/databases/DatabaseModal/index.tsx | 5 +-
.../src/features/databases/DatabaseModal/styles.ts | 2 +-
.../databases/UploadDataModel/ColumnsPreview.tsx | 4 +-
.../features/databases/UploadDataModel/index.tsx | 4 +-
.../features/databases/UploadDataModel/styles.ts | 2 +-
.../AddDataset/DatasetPanel/DatasetPanel.tsx | 5 +-
.../AddDataset/DatasetPanel/MessageContent.tsx | 4 +-
.../datasets/AddDataset/DatasetPanel/index.tsx | 4 +-
.../datasets/AddDataset/EditDataset/index.tsx | 4 +-
.../features/datasets/AddDataset/Footer/index.tsx | 4 +-
.../features/datasets/AddDataset/Header/index.tsx | 2 +-
.../datasets/AddDataset/LeftPanel/index.tsx | 4 +-
.../datasets/AddDataset/RightPanel/index.tsx | 2 +-
.../src/features/datasets/DatasetLayout/index.tsx | 2 +-
.../features/datasets/DatasetSelectLabel/index.tsx | 4 +-
.../datasets/DuplicateDatasetModal.test.tsx | 2 +-
.../features/datasets/DuplicateDatasetModal.tsx | 2 +-
.../src/features/datasets/constants.ts | 2 +-
.../src/features/datasets/hooks/useDatasetLists.ts | 4 +-
.../datasets/hooks/useGetDatasetRelatedCounts.ts | 4 +-
.../DatasetMetadataBar.skipped-stories.tsx | 2 +-
.../datasets/metadataBar/useDatasetMetadataBar.tsx | 4 +-
superset-frontend/src/features/datasets/styles.ts | 2 +-
.../src/features/groups/GroupListModal.tsx | 2 +-
superset-frontend/src/features/groups/utils.ts | 2 +-
.../src/features/home/ActivityTable.tsx | 4 +-
superset-frontend/src/features/home/ChartTable.tsx | 2 +-
.../src/features/home/DashboardTable.tsx | 2 +-
superset-frontend/src/features/home/EmptyState.tsx | 4 +-
.../src/features/home/LanguagePicker.tsx | 4 +-
superset-frontend/src/features/home/Menu.test.tsx | 115 +-
superset-frontend/src/features/home/Menu.tsx | 2 +-
superset-frontend/src/features/home/RightMenu.tsx | 9 +-
.../src/features/home/SavedQueries.tsx | 4 +-
superset-frontend/src/features/home/SubMenu.tsx | 9 +-
.../src/features/home/commonMenuData.ts | 2 +-
.../src/features/owners/OwnerSelectLabel/index.tsx | 2 +-
.../src/features/queries/QueryPreviewModal.tsx | 4 +-
.../features/queries/SavedQueryPreviewModal.tsx | 4 +-
.../src/features/queries/SyntaxHighlighterCopy.tsx | 4 +-
.../ReportModal/HeaderReportDropdown/index.tsx | 4 +-
.../src/features/reports/ReportModal/actions.ts | 2 +-
.../src/features/reports/ReportModal/index.tsx | 5 +-
.../src/features/reports/ReportModal/styles.tsx | 2 +-
.../src/features/rls/RowLevelSecurityModal.tsx | 4 +-
superset-frontend/src/features/rls/constants.ts | 2 +-
.../src/features/roles/RoleFormItems.tsx | 53 +-
.../src/features/roles/RoleListAddModal.test.tsx | 45 +-
.../src/features/roles/RoleListAddModal.tsx | 23 +-
.../src/features/roles/RoleListDuplicateModal.tsx | 2 +-
.../src/features/roles/RoleListEditModal.test.tsx | 328 ++-
.../src/features/roles/RoleListEditModal.tsx | 179 +-
superset-frontend/src/features/roles/types.ts | 12 +-
superset-frontend/src/features/roles/utils.test.ts | 547 +++++
superset-frontend/src/features/roles/utils.ts | 165 ++
.../src/features/tags/BulkTagModal.tsx | 4 +-
superset-frontend/src/features/tags/TagCard.tsx | 2 +-
superset-frontend/src/features/tags/TagModal.tsx | 4 +-
.../src/features/tasks/TaskPayloadPopover.tsx | 2 +-
.../src/features/tasks/TaskStackTracePopover.tsx | 4 +-
.../src/features/tasks/TaskStatusIcon.tsx | 3 +-
.../src/features/themes/ThemeModal.tsx | 5 +-
.../src/features/userInfo/UserInfoModal.tsx | 2 +-
.../src/features/users/UserListModal.tsx | 2 +-
superset-frontend/src/features/users/utils.ts | 2 +-
.../components/Range/RangeFilterPlugin.stories.tsx | 2 +-
.../components/Range/RangeFilterPlugin.test.tsx | 2 +-
.../filters/components/Range/RangeFilterPlugin.tsx | 4 +-
.../src/filters/components/Range/buildQuery.ts | 2 +-
.../src/filters/components/Range/controlPanel.ts | 2 +-
.../src/filters/components/Range/index.ts | 2 +-
.../components/Select/SelectFilterPlugin.tsx | 8 +-
.../filters/components/Select/buildQuery.test.ts | 2 +-
.../src/filters/components/Select/buildQuery.ts | 2 +-
.../src/filters/components/Select/controlPanel.ts | 2 +-
.../src/filters/components/Select/index.ts | 2 +-
.../filters/components/Select/transformProps.ts | 2 +-
.../src/filters/components/Select/types.ts | 2 +-
.../filters/components/Time/TimeFilterPlugin.tsx | 2 +-
.../src/filters/components/Time/controlPanel.ts | 2 +-
.../src/filters/components/Time/index.ts | 2 +-
.../TimeColumn/TimeColumnFilterPlugin.tsx | 4 +-
.../filters/components/TimeColumn/controlPanel.ts | 2 +-
.../src/filters/components/TimeColumn/index.ts | 2 +-
.../components/TimeGrain/TimeGrainFilterPlugin.tsx | 4 +-
.../filters/components/TimeGrain/controlPanel.ts | 2 +-
.../src/filters/components/TimeGrain/index.ts | 2 +-
superset-frontend/src/filters/components/common.ts | 2 +-
superset-frontend/src/filters/utils.test.ts | 2 +-
superset-frontend/src/filters/utils.ts | 2 +-
.../src/hooks/apiResources/datasets.ts | 2 +-
.../hooks/useConfirmModal/useConfirmModal.test.tsx | 2 +-
superset-frontend/src/hooks/useJsonTreeTheme.ts | 2 +-
.../src/hooks/useThemeMenuItems.test.tsx | 2 +-
superset-frontend/src/hooks/useThemeMenuItems.tsx | 4 +-
.../src/hooks/useUnsavedChangesPrompt/index.ts | 2 +-
superset-frontend/src/middleware/asyncEvent.ts | 2 +-
superset-frontend/src/pages/ActionLog/index.tsx | 4 +-
.../src/pages/AlertReportList/index.tsx | 4 +-
superset-frontend/src/pages/AllEntities/index.tsx | 4 +-
.../src/pages/AnnotationLayerList/index.tsx | 2 +-
.../src/pages/AnnotationList/index.tsx | 4 +-
superset-frontend/src/pages/Chart/index.tsx | 2 +-
.../src/pages/ChartCreation/ChartCreation.test.tsx | 2 +-
.../src/pages/ChartCreation/index.tsx | 4 +-
superset-frontend/src/pages/ChartList/index.tsx | 5 +-
.../src/pages/CssTemplateList/index.tsx | 2 +-
.../DashboardList/DashboardList.behavior.test.tsx | 394 ++++
.../DashboardList/DashboardList.cardview.test.tsx | 417 ++++
.../DashboardList/DashboardList.listview.test.tsx | 402 ++++
.../DashboardList.permissions.test.tsx | 340 +++
.../src/pages/DashboardList/DashboardList.test.tsx | 391 ++--
.../DashboardList/DashboardList.testHelpers.tsx | 360 +++
.../src/pages/DashboardList/index.tsx | 4 +-
superset-frontend/src/pages/DatabaseList/index.tsx | 4 +-
superset-frontend/src/pages/DatasetList/index.tsx | 4 +-
.../src/pages/ExecutionLogList/index.tsx | 4 +-
.../src/pages/FileHandler/index.test.tsx | 22 +-
superset-frontend/src/pages/FileHandler/index.tsx | 2 +-
superset-frontend/src/pages/GroupsList/index.tsx | 2 +-
superset-frontend/src/pages/Home/index.tsx | 4 +-
superset-frontend/src/pages/Login/index.tsx | 4 +-
.../src/pages/QueryHistoryList/index.tsx | 4 +-
.../src/pages/RedirectWarning/index.tsx | 175 ++
.../src/pages/RedirectWarning/utils.test.ts | 124 ++
.../src/pages/RedirectWarning/utils.ts | 96 +
superset-frontend/src/pages/Register/index.tsx | 4 +-
.../src/pages/RolesList/RolesList.test.tsx | 27 +-
superset-frontend/src/pages/RolesList/index.tsx | 75 +-
.../src/pages/RowLevelSecurityList/index.tsx | 2 +-
.../src/pages/SavedQueryList/index.tsx | 4 +-
superset-frontend/src/pages/SqlLab/index.tsx | 2 +-
superset-frontend/src/pages/Tags/index.tsx | 2 +-
superset-frontend/src/pages/TaskList/index.tsx | 3 +-
superset-frontend/src/pages/ThemeList/index.tsx | 5 +-
superset-frontend/src/pages/UserInfo/index.tsx | 4 +-
.../src/pages/UserRegistrations/index.tsx | 2 +-
superset-frontend/src/pages/UsersList/index.tsx | 2 +-
superset-frontend/src/preamble.ts | 4 +-
superset-frontend/src/setup/setupClient.ts | 2 +-
superset-frontend/src/theme/ThemeController.ts | 4 +-
superset-frontend/src/theme/ThemeProvider.tsx | 2 +-
.../src/theme/hooks/useThemeValidation.ts | 2 +-
.../src/theme/tests/ThemeController.test.ts | 2 +-
.../src/theme/tests/ThemeProvider.test.tsx | 2 +-
.../theme/utils/themeStructureValidation.test.ts | 2 +-
.../src/theme/utils/themeStructureValidation.ts | 2 +-
superset-frontend/src/types/bootstrapTypes.ts | 9 +-
superset-frontend/src/utils/downloadAsImage.tsx | 4 +-
superset-frontend/src/utils/downloadAsPdf.ts | 4 +-
superset-frontend/src/utils/export.test.ts | 4 +-
superset-frontend/src/utils/export.ts | 2 +-
superset-frontend/src/utils/fetchOptions.ts | 2 +-
.../utils/getChartRequiredFieldsMissingMessage.ts | 2 +-
superset-frontend/src/utils/pathUtils.test.ts | 71 +
superset-frontend/src/utils/pathUtils.ts | 37 +-
superset-frontend/src/views/App.tsx | 47 +-
superset-frontend/src/views/CRUD/hooks.ts | 2 +-
superset-frontend/src/views/CRUD/utils.tsx | 5 +-
.../src/views/RootContextProviders.tsx | 17 +-
superset-frontend/src/views/index.tsx | 2 +-
superset-frontend/src/views/menu.tsx | 3 +-
superset-frontend/src/views/routes.tsx | 11 +
.../src/visualizations/TimeTable/TimeTable.tsx | 4 +-
.../components/SparklineCell/SparklineCell.tsx | 2 +-
.../TimeTable/config/controlPanel/controlPanel.ts | 2 +-
.../config/transformProps/transformProps.test.ts | 2 +-
.../src/visualizations/TimeTable/constants.ts | 2 +-
.../src/visualizations/TimeTable/index.ts | 2 +-
.../ExampleComponent/ExampleComponent.tsx | 2 +-
superset-frontend/webpack.config.js | 7 +-
superset-websocket/package-lock.json | 72 +-
superset-websocket/package.json | 8 +-
superset-websocket/utils/client-ws-app/.nvmrc | 2 +-
superset/charts/schemas.py | 12 +-
superset/commands/database/export.py | 9 +
superset/commands/database/importers/v1/utils.py | 26 +-
superset/commands/distributed_lock/acquire.py | 2 +-
superset/commands/distributed_lock/base.py | 6 +-
superset/commands/distributed_lock/release.py | 2 +-
superset/commands/importers/v1/__init__.py | 4 +
superset/commands/importers/v1/assets.py | 4 +
superset/commands/importers/v1/utils.py | 19 +
superset/commands/tasks/cancel.py | 2 +-
superset/commands/tasks/internal_update.py | 2 +-
superset/commands/tasks/prune.py | 2 +-
superset/commands/tasks/submit.py | 2 +-
superset/commands/tasks/update.py | 2 +-
superset/config.py | 22 +-
superset/connectors/sqla/models.py | 2 +-
superset/core/api/core_api_injection.py | 159 +-
superset/core/mcp/core_mcp_injection.py | 74 +-
superset/daos/base.py | 4 +-
superset/daos/tag.py | 7 +
superset/daos/tasks.py | 2 +-
superset/dashboards/api.py | 34 +-
superset/databases/api.py | 14 +
superset/databases/schemas.py | 62 +
superset/db_engine_specs/base.py | 6 +
superset/db_engine_specs/databend.py | 21 +-
superset/distributed_lock/__init__.py | 2 +-
superset/extensions/context.py | 90 +
superset/extensions/contributions.py | 94 +
superset/extensions/utils.py | 7 +-
superset/importexport/api.py | 13 +
superset/initialization/__init__.py | 18 +-
superset/key_value/models.py | 2 +-
superset/mcp_service/CLAUDE.md | 16 +-
superset/mcp_service/app.py | 40 +-
superset/mcp_service/chart/chart_utils.py | 289 ++-
.../chart/prompts/create_chart_guided.py | 2 +-
superset/mcp_service/chart/schemas.py | 188 +-
superset/mcp_service/chart/tool/generate_chart.py | 196 +-
superset/mcp_service/chart/tool/get_chart_data.py | 2 +-
superset/mcp_service/chart/tool/get_chart_info.py | 2 +-
.../mcp_service/chart/tool/get_chart_preview.py | 2 +-
superset/mcp_service/chart/tool/list_charts.py | 2 +-
superset/mcp_service/chart/tool/update_chart.py | 2 +-
.../mcp_service/chart/tool/update_chart_preview.py | 2 +-
.../chart/validation/schema_validator.py | 169 +-
.../tool/add_chart_to_existing_dashboard.py | 2 +-
.../dashboard/tool/generate_dashboard.py | 2 +-
.../dashboard/tool/get_dashboard_info.py | 2 +-
.../mcp_service/dashboard/tool/list_dashboards.py | 2 +-
.../mcp_service/dataset/tool/get_dataset_info.py | 2 +-
superset/mcp_service/dataset/tool/list_datasets.py | 2 +-
.../explore/tool/generate_explore_link.py | 2 +-
superset/mcp_service/server.py | 27 +
superset/mcp_service/sql_lab/tool/execute_sql.py | 9 +-
.../sql_lab/tool/open_sql_lab_with_context.py | 2 +-
superset/mcp_service/system/prompts/quickstart.py | 2 +-
superset/mcp_service/system/schemas.py | 7 +
.../mcp_service/system/tool/get_instance_info.py | 13 +-
superset/mcp_service/system/tool/get_schema.py | 2 +-
superset/mcp_service/system/tool/health_check.py | 2 +-
superset/models/core.py | 9 +-
superset/models/dashboard.py | 2 +-
superset/models/helpers.py | 2 +-
superset/models/slice.py | 2 +-
superset/models/sql_lab.py | 5 +-
superset/models/task_subscribers.py | 2 +-
superset/models/tasks.py | 4 +-
superset/reports/notifications/email.py | 5 +
superset/sql/execution/executor.py | 24 +-
superset/tags/models.py | 2 +-
superset/tasks/constants.py | 2 +-
superset/tasks/context.py | 2 +-
superset/tasks/decorators.py | 17 +-
superset/tasks/locks.py | 4 +-
superset/tasks/manager.py | 10 +-
superset/tasks/scheduler.py | 2 +-
superset/tasks/utils.py | 2 +-
superset/templates/superset/spa.html | 5 +
superset/utils/cache_manager.py | 34 +-
superset/utils/json.py | 43 +
superset/utils/link_redirect.py | 149 ++
superset/utils/logging_configurator.py | 7 +
superset/utils/oauth2.py | 4 +
superset/utils/pandas_postprocessing/geography.py | 6 +-
superset/utils/pandas_postprocessing/resample.py | 2 +-
superset/utils/webdriver.py | 34 +-
superset/views/redirect.py | 76 +
superset/viz.py | 36 +-
tests/integration_tests/databases/api_tests.py | 91 +
tests/integration_tests/fixtures/importexport.py | 26 +
tests/integration_tests/security_tests.py | 1 +
tests/integration_tests/tasks/api_tests.py | 4 +-
.../tasks/commands/test_cancel.py | 2 +-
.../tasks/commands/test_internal_update.py | 2 +-
.../integration_tests/tasks/commands/test_prune.py | 2 +-
.../tasks/commands/test_submit.py | 2 +-
.../tasks/commands/test_update.py | 2 +-
.../integration_tests/tasks/test_event_handlers.py | 2 +-
.../tasks/test_subscription_visibility.py | 2 +-
.../integration_tests/tasks/test_sync_join_wait.py | 6 +-
tests/integration_tests/tasks/test_throttling.py | 2 +-
tests/integration_tests/tasks/test_timeout.py | 2 +-
.../integration_tests/views/test_redirect_view.py | 66 +
tests/unit_tests/dao/base_dao_test.py | 2 +-
tests/unit_tests/daos/test_tasks.py | 2 +-
tests/unit_tests/databases/api_test.py | 70 +
.../databases/commands/importers/v1/import_test.py | 123 ++
tests/unit_tests/databases/schema_tests.py | 106 +
tests/unit_tests/db_engine_specs/test_base.py | 91 +
tests/unit_tests/extension_tests.py | 21 +-
tests/unit_tests/extensions/test_types.py | 84 +-
tests/unit_tests/importexport/api_test.py | 52 +
.../mcp_service/chart/test_new_chart_types.py | 929 ++++++++
.../mcp_service/chart/tool/test_generate_chart.py | 74 +
.../mcp_service/sql_lab/tool/test_execute_sql.py | 2 +-
.../system/tool/test_get_current_user.py | 5 +
tests/unit_tests/mcp_service/test_mcp_server.py | 34 +
tests/unit_tests/sql/execution/test_executor.py | 19 +-
tests/unit_tests/tags/commands/update_test.py | 60 +
tests/unit_tests/tasks/test_decorators.py | 2 +-
tests/unit_tests/tasks/test_handlers.py | 6 +-
tests/unit_tests/tasks/test_manager.py | 34 +-
tests/unit_tests/tasks/test_timeout.py | 34 +-
tests/unit_tests/tasks/test_utils.py | 2 +-
tests/unit_tests/utils/json_tests.py | 139 ++
tests/unit_tests/utils/oauth2_tests.py | 56 +
tests/unit_tests/utils/test_link_redirect.py | 143 ++
1416 files changed, 23388 insertions(+), 9895 deletions(-)
diff --cc superset-frontend/src/features/alerts/AlertReportModal.test.tsx
index 1bf64bcf8a4,b025a27c018..a29a6d1322b
--- a/superset-frontend/src/features/alerts/AlertReportModal.test.tsx
+++ b/superset-frontend/src/features/alerts/AlertReportModal.test.tsx
@@@ -913,760 -867,502 +921,1211 @@@ test('dashboard with no tabs disables t
userEvent.click(screen.getByTestId('contents-panel'));
await screen.findByText(/test dashboard/i);
- const filterDropdown = screen.getByRole('combobox', {
- name: /select filter/i,
- });
- expect(filterDropdown).toBeInTheDocument();
+ const tabSelector = document.querySelector('.ant-select-disabled');
+ expect(tabSelector).toBeInTheDocument();
+});
- userEvent.click(filterDropdown);
+test('dashboard with no tabs and no filters hides filter add link', async () => {
+ fetchMock.removeRoute(tabsEndpoint);
+ fetchMock.get(tabsEndpoint, noTabsResponse, { name: tabsEndpoint });
- const filterOption = await waitFor(() => {
- const virtualList = document.querySelector('.rc-virtual-list');
- return within(virtualList as HTMLElement).getByText('Test Filter 1');
+ render(<AlertReportModal {...generateMockedProps(true, true)} />, {
+ useRedux: true,
});
- userEvent.click(filterOption);
+ userEvent.click(screen.getByTestId('contents-panel'));
+ await screen.findByText(/test dashboard/i);
+ // Wait for tabs fetch to complete
await waitFor(() => {
- const selectionItem = document.querySelector(
- '.ant-select-selection-item[title="Test Filter 1"]',
- );
- expect(selectionItem).toBeInTheDocument();
+ expect(
+ fetchMock.callHistory.calls(tabsEndpoint).length,
+ ).toBeGreaterThan(0);
});
- const selectContainer = filterDropdown.closest('.ant-select');
+ // Tab selector should be disabled (no tabs)
+ const disabledSelects = document.querySelectorAll('.ant-select-disabled');
+ expect(disabledSelects.length).toBeGreaterThanOrEqual(1);
- const clearIcon = selectContainer?.querySelector(
- '.ant-select-clear [aria-label="close-circle"]',
- );
- expect(clearIcon).toBeInTheDocument();
- userEvent.click(clearIcon as Element);
+ // Filter Select should also be disabled (no filter options available)
+ expect(disabledSelects.length).toBeGreaterThanOrEqual(2);
+
+ // "Apply another dashboard filter" link should NOT appear
+ // because noTabsResponse has empty native_filters ({})
+ expect(
+ screen.queryByText(/apply another dashboard filter/i),
+ ).not.toBeInTheDocument();
+});
+test('dashboard switching resets tab and filter selections', async () => {
+ // Return dashboard options so user can switch
+ const dashboardOptions = {
+ result: [
+ { text: 'Test Dashboard', value: 1 },
+ { text: 'Other Dashboard', value: 99 },
+ ],
+ count: 2,
+ };
+ fetchMock.removeRoute(dashboardEndpoint);
+ fetchMock.get(dashboardEndpoint, dashboardOptions);
+ fetchMock.removeRoute(reportDashboardEndpoint);
+ fetchMock.get(reportDashboardEndpoint, dashboardOptions);
+
+ // Dashboard 1 has tabs and filters
+ fetchMock.removeRoute(tabsEndpoint);
+ fetchMock.get(tabsEndpoint, tabsWithFilters, { name: tabsEndpoint });
+
+ // Dashboard 99 has no tabs
+ const tabs99 = 'glob:*/api/v1/dashboard/99/tabs';
+ fetchMock.get(tabs99, noTabsResponse, { name: tabs99 });
+
+ render(<AlertReportModal {...generateMockedProps(true, true)} />, {
+ useRedux: true,
+ });
+
+ userEvent.click(screen.getByTestId('contents-panel'));
+ await screen.findByText(/test dashboard/i);
+
+ // Wait for tabs to load from dashboard 1
await waitFor(() => {
- const selectionItem = document.querySelector(
- '.ant-select-selection-item[title="Test Filter 1"]',
- );
- expect(selectionItem).not.toBeInTheDocument();
+ expect(screen.getAllByText(/select tab/i)).toHaveLength(1);
});
- userEvent.click(filterDropdown);
+ // Verify filters are available
await waitFor(() => {
- const virtualList = document.querySelector('.rc-virtual-list');
expect(
- within(virtualList as HTMLElement).getByText('Test Filter 1'),
+ screen.getByRole('combobox', { name: /select filter/i }),
).toBeInTheDocument();
});
+
+ // Switch to "Other Dashboard"
+ const dashboardSelect = screen.getByRole('combobox', {
+ name: /dashboard/i,
+ });
+ userEvent.clear(dashboardSelect);
+ userEvent.type(dashboardSelect, 'Other Dashboard{enter}');
+
+ // Tab selector should reset: "Other Dashboard" has no tabs, so disabled with placeholder
+ await waitFor(() => {
+ expect(screen.getByText(/select a tab/i)).toBeInTheDocument();
+ });
+
+ // Filter row should reset to empty (no filter selected)
+ await waitFor(() => {
+ const filterSelects = screen.getAllByRole('combobox', {
+ name: /select filter/i,
+ });
+ // Filter select should have no selected value (placeholder state)
+ filterSelects.forEach(select => {
+ const container = select.closest('.ant-select');
+ expect(
+ container?.querySelector('.ant-select-selection-item'),
+ ).not.toBeInTheDocument();
+ });
+ });
+
+ // Restore dashboard endpoints
+ fetchMock.removeRoute(dashboardEndpoint);
+ fetchMock.get(dashboardEndpoint, { result: [] });
+ fetchMock.removeRoute(reportDashboardEndpoint);
+ fetchMock.get(reportDashboardEndpoint, { result: [] });
+ fetchMock.removeRoute(tabs99);
});
-const setupAnchorMocks = (
- nativeFilters: Record<string, unknown>,
- anchor = 'TAB-abc',
- tabsOverride?: {
- all_tabs: Record<string, string>;
- tab_tree: { title: string; value: string }[];
- },
-) => {
- const payloadWithAnchor = {
- ...generateMockPayload(true),
- extra: { dashboard: { anchor } },
- };
+test('different dashboard populates its own tabs and filters', async () => {
+ // Set up a report (id:99) that uses dashboard 99 instead of dashboard 1.
+ // This tests that the component correctly loads tabs and filters for a
+ // different dashboard (the "dashboard B has its own data" case).
+ const FETCH_REPORT_DASH99_ENDPOINT = 'glob:*/api/v1/report/99';
+ fetchMock.get(FETCH_REPORT_DASH99_ENDPOINT, {
+ result: {
+ ...generateMockPayload(true),
+ id: 99,
+ type: 'Report',
+ dashboard: { id: 99, dashboard_title: 'Other Dashboard' },
+ },
+ });
- const defaultTabs = {
- all_tabs: { [anchor]: `Tab ${anchor}` },
- tab_tree: [{ title: `Tab ${anchor}`, value: anchor }],
+ // Dashboard 99 has its own tabs (Tab Alpha, Tab Beta) and a Region Filter
+ const tabs99Endpoint = 'glob:*/api/v1/dashboard/99/tabs';
+ const dash99Tabs = {
+ result: {
+ all_tabs: { TAB_A: 'Tab Alpha', TAB_B: 'Tab Beta' },
+ tab_tree: [
+ { title: 'Tab Alpha', value: 'TAB_A' },
+ { title: 'Tab Beta', value: 'TAB_B' },
+ ],
+ native_filters: {
+ all: [
+ {
+ id: 'NATIVE_FILTER-R1',
+ name: 'Region Filter',
+ filterType: 'filter_select',
+ targets: [{ column: { name: 'region' }, datasetId: 3 }],
+ adhoc_filters: [],
+ },
+ ],
+ TAB_A: [
+ {
+ id: 'NATIVE_FILTER-R1',
+ name: 'Region Filter',
+ filterType: 'filter_select',
+ targets: [{ column: { name: 'region' }, datasetId: 3 }],
+ adhoc_filters: [],
+ },
+ ],
+ TAB_B: [],
+ },
+ },
};
- const tabs = tabsOverride ?? defaultTabs;
+ fetchMock.get(tabs99Endpoint, dash99Tabs, { name: 'tabs-99' });
- // Clear call history so waitFor assertions don't match calls from prior tests.
- fetchMock.callHistory.clear();
+ const props = generateMockedProps(true, true);
+ const dash99Props = { ...props, alert: { ...validAlert, id: 99 } };
- // Only replace the named routes that need anchor-specific overrides;
- // unnamed related-endpoint routes (owners, database, etc.) stay intact.
- fetchMock.removeRoute(FETCH_DASHBOARD_ENDPOINT);
- fetchMock.removeRoute(FETCH_CHART_ENDPOINT);
- fetchMock.removeRoute(tabsEndpoint);
+ render(<AlertReportModal {...dash99Props} />, { useRedux: true });
- fetchMock.get(
- FETCH_DASHBOARD_ENDPOINT,
- { result: payloadWithAnchor },
- { name: FETCH_DASHBOARD_ENDPOINT },
- );
- fetchMock.get(
- FETCH_CHART_ENDPOINT,
- { result: generateMockPayload(false) },
- { name: FETCH_CHART_ENDPOINT },
- );
- fetchMock.get(
- tabsEndpoint,
- {
- result: {
- ...tabs,
- native_filters: nativeFilters,
- },
+ userEvent.click(screen.getByTestId('contents-panel'));
+ await screen.findByText(/other dashboard/i);
+
+ // Wait for dashboard 99 tabs to load
+ await waitFor(
+ () => {
+ expect(
+ fetchMock.callHistory.calls('tabs-99').length,
+ ).toBeGreaterThan(0);
},
- { name: tabsEndpoint },
+ { timeout: 5000 },
);
-};
-const restoreAnchorMocks = () => {
- fetchMock.removeRoute(FETCH_DASHBOARD_ENDPOINT);
- fetchMock.get(
- FETCH_DASHBOARD_ENDPOINT,
- { result: generateMockPayload(true) },
- { name: FETCH_DASHBOARD_ENDPOINT },
- );
- fetchMock.removeRoute(FETCH_CHART_ENDPOINT);
- fetchMock.get(
- FETCH_CHART_ENDPOINT,
- { result: generateMockPayload(false) },
- { name: FETCH_CHART_ENDPOINT },
- );
- fetchMock.removeRoute(tabsEndpoint);
- fetchMock.get(
- tabsEndpoint,
- { result: { all_tabs: {}, tab_tree: [] } },
- { name: tabsEndpoint },
+ // Tab selector should be enabled (dashboard 99 has tabs)
+ await waitFor(() => {
+ const treeSelect = document.querySelector('.ant-tree-select');
+ expect(treeSelect).toBeInTheDocument();
+ expect(treeSelect).not.toHaveClass('ant-select-disabled');
+ });
+
+ // Filter dropdown should show dashboard 99's "Region Filter"
+ const filterSelect = await waitFor(() =>
+ screen.getByRole('combobox', { name: /select filter/i }),
);
-};
+ userEvent.click(filterSelect);
-test('no error toast when anchor tab has no scoped native filters', async () => {
- setupAnchorMocks({
- all: [
- {
- id: 'NATIVE_FILTER-1',
- name: 'Filter 1',
- filterType: 'filter_select',
- targets: [{ column: { name: 'col' } }],
- adhoc_filters: [],
- },
- ],
+ await waitFor(() => {
+ const virtualLists = document.querySelectorAll('.rc-virtual-list');
+ const lastVirtualList = virtualLists[virtualLists.length - 1];
+ expect(
+ within(lastVirtualList as HTMLElement).getByText('Region Filter'),
+ ).toBeInTheDocument();
});
- const store = createStore({}, reducerIndex);
+ // Dashboard 1's filters (Country/City) should NOT appear
+ const virtualLists = document.querySelectorAll('.rc-virtual-list');
+ const lastVirtualList = virtualLists[virtualLists.length - 1];
+ expect(
+ within(lastVirtualList as HTMLElement).queryByText('Country Filter'),
+ ).not.toBeInTheDocument();
- try {
- render(<AlertReportModal {...generateMockedProps(true, true)} />, {
- store,
- });
+ // Cleanup
+ fetchMock.removeRoute(FETCH_REPORT_DASH99_ENDPOINT);
+ fetchMock.removeRoute('tabs-99');
+});
- userEvent.click(screen.getByTestId('contents-panel'));
+test('dashboard tabs fetch failure shows error toast', async () => {
+ fetchMock.removeRoute(tabsEndpoint);
+ fetchMock.get(tabsEndpoint, 500, { name: tabsEndpoint });
+
+ const store = createStore({}, reducerIndex);
+
+ render(<AlertReportModal {...generateMockedProps(true, true)} />, {
+ useRedux: true,
+ store,
+ });
+
+ userEvent.click(screen.getByTestId('contents-panel'));
+ await screen.findByText(/test dashboard/i);
+
+ // Tab selector should remain disabled (no tabs loaded)
+ const tabSelector = document.querySelector('.ant-select-disabled');
+ expect(tabSelector).toBeInTheDocument();
+
+ // Verify the tabs request was attempted
+ const tabsCalls = fetchMock.callHistory.calls(tabsEndpoint);
+ expect(tabsCalls.length).toBeGreaterThan(0);
+
+ // Verify danger toast was dispatched for the fetch failure
+ await waitFor(() => {
+ const toasts = (store.getState() as any).messageToasts;
+ expect(toasts.length).toBeGreaterThan(0);
+ expect(
+ toasts.some((t: { text: string }) =>
+ t.text.includes('error retrieving dashboard tabs'),
+ ),
+ ).toBe(true);
+ });
+});
+
+test('switching content type to chart hides tab and filter sections', async () => {
+ fetchMock.removeRoute(tabsEndpoint);
+ fetchMock.get(tabsEndpoint, tabsWithFilters, { name: tabsEndpoint });
+
+ render(<AlertReportModal {...generateMockedProps(true, true)} />, {
+ useRedux: true,
+ });
+
+ userEvent.click(screen.getByTestId('contents-panel'));
+ await screen.findByText(/test dashboard/i);
+
+ // Tab selector and filter dropdowns should be visible for dashboard
+ expect(screen.getAllByText(/select tab/i)).toHaveLength(1);
+ expect(
+ screen.getByRole('combobox', { name: /select filter/i }),
+ ).toBeInTheDocument();
+
+ // Switch to chart
+ const contentTypeSelector = screen.getByRole('combobox', {
+ name: /select content type/i,
+ });
+ await comboboxSelect(contentTypeSelector, 'Chart', () =>
+ screen.getByRole('combobox', { name: /chart/i }),
+ );
+
+ // Tab and filter sections should be hidden
+ expect(screen.queryByText(/select tab/i)).not.toBeInTheDocument();
+ expect(
+ screen.queryByRole('combobox', { name: /select filter/i }),
+ ).not.toBeInTheDocument();
+});
+
+test('adding and removing dashboard filter rows', async () => {
+ fetchMock.removeRoute(tabsEndpoint);
+ fetchMock.get(tabsEndpoint, tabsWithFilters, { name: tabsEndpoint });
+
+ render(<AlertReportModal {...generateMockedProps(true, true)} />, {
+ useRedux: true,
+ });
+
+ userEvent.click(screen.getByTestId('contents-panel'));
+ await screen.findByText(/test dashboard/i);
+
+ // Wait for filter options to load
+ await waitFor(() => {
+ expect(
+ screen.getByRole('combobox', { name: /select filter/i }),
+ ).toBeInTheDocument();
+ });
+
+ // Should start with 1 filter row
+ const initialFilterSelects = screen.getAllByRole('combobox', {
+ name: /select filter/i,
+ });
+ expect(initialFilterSelects).toHaveLength(1);
+
+ // Click "Apply another dashboard filter"
+ const addFilterButton = screen.getByText(/apply another dashboard filter/i);
+ userEvent.click(addFilterButton);
+
+ // Should now have 2 filter rows
+ await waitFor(() => {
+ expect(
+ screen.getAllByRole('combobox', { name: /select filter/i }),
+ ).toHaveLength(2);
+ });
+
+ // Remove the second filter row by clicking its delete icon
+ const deleteIcons = document.querySelectorAll('.filters-trashcan');
+ expect(deleteIcons.length).toBeGreaterThanOrEqual(2);
+ fireEvent.click(deleteIcons[deleteIcons.length - 1]);
+
+ // Should be back to 1 filter row
+ await waitFor(() => {
+ expect(
+ screen.getAllByRole('combobox', { name: /select filter/i }),
+ ).toHaveLength(1);
+ });
+});
+
+test('alert shows condition section, report does not', () => {
+ // Alert has 5 sections
+ const { unmount } = render(
+ <AlertReportModal {...generateMockedProps(false)} />,
+ { useRedux: true },
+ );
+ expect(screen.getAllByRole('tab')).toHaveLength(5);
+ expect(screen.getByTestId('alert-condition-panel')).toBeInTheDocument();
+ unmount();
+
+ // Report has 4 sections, no condition panel
+ render(<AlertReportModal {...generateMockedProps(true)} />, {
+ useRedux: true,
+ });
+ expect(screen.getAllByRole('tab')).toHaveLength(4);
+ expect(screen.queryByTestId('alert-condition-panel')).not.toBeInTheDocument();
+});
+
+test('submit includes conditionNotNull without threshold in alert payload', async () => {
+ // Mock payload returns id:1, so updateResource PUTs to /api/v1/report/1
+ fetchMock.put(
+ 'glob:*/api/v1/report/1',
+ { id: 1, result: {} },
+ { name: 'put-condition' },
+ );
+
+ render(<AlertReportModal {...generateMockedProps(false, true, false)} />, {
+ useRedux: true,
+ });
+
+ // Wait for resource to load and all validation to pass
+ await waitFor(() => {
+ expect(
+ screen.queryAllByRole('img', { name: /check-circle/i }),
+ ).toHaveLength(5);
+ });
+
+ // Open condition panel and select "not null"
+ userEvent.click(screen.getByTestId('alert-condition-panel'));
+ await screen.findByText(/smaller than/i);
+ const condition = screen.getByRole('combobox', { name: /condition/i });
+ await comboboxSelect(condition, 'not null', () =>
+ screen.getAllByText(/not null/i)[0],
+ );
+
+ expect(screen.getByRole('spinbutton')).toBeDisabled();
+
+ // Wait for Save to be enabled and click
+ await waitFor(() => {
+ expect(
+ screen.getByRole('button', { name: /save/i }),
+ ).toBeEnabled();
+ });
+ await waitFor(() =>
+ userEvent.click(screen.getByRole('button', { name: /save/i })),
+ );
+
+ // Verify the PUT payload
+ await waitFor(() => {
+ const calls = fetchMock.callHistory.calls('put-condition');
+ expect(calls.length).toBeGreaterThan(0);
+ });
+
+ const calls = fetchMock.callHistory.calls('put-condition');
+ const body = JSON.parse(calls[calls.length - 1].options.body as string);
+ expect(body.validator_type).toBe('not null');
+ expect(body.validator_config_json).toEqual({});
+
+ fetchMock.removeRoute('put-condition');
+});
+
+test('edit mode submit uses PUT and excludes read-only fields', async () => {
+ // Mock payload returns id:1, so updateResource PUTs to /api/v1/report/1
+ fetchMock.put(
+ 'glob:*/api/v1/report/1',
+ { id: 1, result: {} },
+ { name: 'put-edit' },
+ );
+
+ render(<AlertReportModal {...generateMockedProps(false, true, false)} />, {
+ useRedux: true,
+ });
+
+ // Wait for resource to load and all validation to pass
+ await waitFor(() => {
+ expect(
+ screen.queryAllByRole('img', { name: /check-circle/i }),
+ ).toHaveLength(5);
+ });
+
+ await waitFor(() => {
+ expect(
+ screen.getByRole('button', { name: /save/i }),
+ ).toBeEnabled();
+ });
+ await waitFor(() =>
+ userEvent.click(screen.getByRole('button', { name: /save/i })),
+ );
+
+ await waitFor(() => {
+ const calls = fetchMock.callHistory.calls('put-edit');
+ expect(calls.length).toBeGreaterThan(0);
+ });
+
+ const calls = fetchMock.callHistory.calls('put-edit');
+ const body = JSON.parse(calls[calls.length - 1].options.body as string);
+
+ // Edit mode strips these read-only fields
+ expect(body).not.toHaveProperty('id');
+ expect(body).not.toHaveProperty('created_by');
+ expect(body).not.toHaveProperty('last_eval_dttm');
+ expect(body).not.toHaveProperty('last_state');
+ expect(body).not.toHaveProperty('last_value');
+ expect(body).not.toHaveProperty('last_value_row_json');
+
+ // Core fields remain
+ expect(body.type).toBe('Alert');
+ expect(body.name).toBe('Test Alert');
+
+ // Recipients from the loaded resource should be in payload
+ expect(body.recipients).toBeDefined();
+ expect(body.recipients.length).toBeGreaterThan(0);
+ expect(body.recipients[0].type).toBe('Email');
+
+ fetchMock.removeRoute('put-edit');
+});
+
+test('create mode submits POST and calls onAdd with response', async () => {
+ fetchMock.post(
+ 'glob:*/api/v1/report/',
+ { id: 100, result: {} },
+ { name: 'create-post' },
+ );
+
+ const props = generateMockedProps(true); // isReport, create mode (no alert)
+ const onAdd = jest.fn();
+ const createProps = { ...props, onAdd };
+
+ render(<AlertReportModal {...createProps} />, { useRedux: true });
+
+ expect(screen.getByText('Add report')).toBeInTheDocument();
+
+ // Fill name
+ const nameInput = screen.getByPlaceholderText(/enter report name/i);
+ userEvent.type(nameInput, 'My New Report');
+
+ // Open contents panel — content type defaults to Dashboard
+ userEvent.click(screen.getByTestId('contents-panel'));
+ await screen.findByRole('combobox', { name: /select content type/i });
+
+ // Switch content type to Chart (default is Dashboard)
+ const contentTypeSelect = screen.getByRole('combobox', {
+ name: /select content type/i,
+ });
+ userEvent.click(contentTypeSelect);
+ const chartOption = await screen.findByText('Chart');
+ userEvent.click(chartOption);
+
+ // Select a chart from the chart combobox
+ const chartSelect = await screen.findByRole('combobox', {
+ name: /chart/i,
+ });
+ userEvent.type(chartSelect, 'table');
+ const tableChart = await screen.findByText('table chart');
+ userEvent.click(tableChart);
+
+ // Open notification panel and type recipient email
+ userEvent.click(screen.getByTestId('notification-method-panel'));
+ const recipientInput = await screen.findByTestId('recipients');
+ userEvent.type(recipientInput, 'test@example.com');
+
+ // Wait for Add button to be enabled (use exact name to avoid matching
+ // "Add CC Recipients" and "Add BCC Recipients" buttons)
+ await waitFor(
+ () => {
+ expect(
+ screen.getByRole('button', { name: 'Add' }),
+ ).toBeEnabled();
+ },
+ { timeout: 5000 },
+ );
+
+ // Click Add
+ await waitFor(() =>
+ userEvent.click(screen.getByRole('button', { name: 'Add' })),
+ );
+
+ // Verify POST was called (not PUT)
+ await waitFor(() => {
+ const calls = fetchMock.callHistory.calls('create-post');
+ expect(calls.length).toBeGreaterThan(0);
+ });
+
+ const calls = fetchMock.callHistory.calls('create-post');
+ const body = JSON.parse(calls[0].options.body as string);
+
+ expect(body.type).toBe('Report');
+ expect(body.name).toBe('My New Report');
+ expect(body.chart).toBe(1);
+ // Chart content type means dashboard is null (mutually exclusive)
+ expect(body.dashboard).toBeNull();
+ expect(body.recipients).toBeDefined();
+ expect(body.recipients[0].type).toBe('Email');
+ expect(body.recipients[0].recipient_config_json.target).toBe(
+ 'test@example.com',
+ );
+
+ // Verify onAdd was called with the response id
+ await waitFor(() => {
+ expect(onAdd).toHaveBeenCalledWith(100);
+ });
+
+ fetchMock.removeRoute('create-post');
+});
+
+test('create mode defaults to dashboard content type with chart null', async () => {
+ // Coverage strategy: the create-mode POST pathway is tested via the chart
+ // POST test above. The dashboard content payload (dashboard=value, chart=null)
+ // is tested via the edit-mode PUT test below. This test verifies that create
+ // mode reports default to Dashboard content type, completing the coverage:
+ // create POST method ← chart POST test
+ // dashboard payload shape ← edit-mode dashboard PUT test
+ // default content type = Dashboard ← THIS test
+
+ const props = generateMockedProps(true); // isReport, create mode
+ render(<AlertReportModal {...props} />, { useRedux: true });
+
+ // Open contents panel
+ userEvent.click(screen.getByTestId('contents-panel'));
+ const contentTypeSelect = await screen.findByRole('combobox', {
+ name: /select content type/i,
+ });
+
+ // Default content type should be "Dashboard" (not "Chart")
+ const selectedItem = contentTypeSelect
+ .closest('.ant-select')
+ ?.querySelector('.ant-select-selection-item');
+ expect(selectedItem).toBeInTheDocument();
+ expect(selectedItem?.textContent).toBe('Dashboard');
+
+ // Dashboard selector should be rendered (not chart selector)
+ expect(
+ screen.getByRole('combobox', { name: /dashboard/i }),
+ ).toBeInTheDocument();
+ expect(
+ screen.queryByRole('combobox', { name: /chart/i }),
+ ).not.toBeInTheDocument();
+});
+
+test('dashboard content type submits dashboard id and null chart', async () => {
+ // Use a custom alert prop with dashboard content from the start,
+ // matching the fetched resource shape (FETCH_DASHBOARD_ENDPOINT).
+ const dashboardAlert: AlertObject = {
+ ...validAlert,
+ id: 1,
+ dashboard_id: 1,
+ chart_id: 0,
+ dashboard: {
+ id: 1,
+ value: 1,
+ label: 'Test Dashboard',
+ dashboard_title: 'Test Dashboard',
+ } as any,
+ chart: undefined as any,
+ };
+
+ fetchMock.put(
+ 'glob:*/api/v1/report/1',
+ { id: 1, result: {} },
+ { name: 'put-dashboard-payload' },
+ );
+
+ const props = generateMockedProps(false, false);
+ const editProps = { ...props, alert: dashboardAlert };
+
+ render(<AlertReportModal {...editProps} />, {
+ useRedux: true,
+ });
+
+ // Wait for resource to load and all validation to pass
+ await waitFor(
+ () => {
+ expect(
+ screen.queryAllByRole('img', { name: /check-circle/i }),
+ ).toHaveLength(5);
+ },
+ { timeout: 5000 },
+ );
+
+ await waitFor(() => {
+ expect(
+ screen.getByRole('button', { name: /save/i }),
+ ).toBeEnabled();
+ });
+ await waitFor(() =>
+ userEvent.click(screen.getByRole('button', { name: /save/i })),
+ );
+
+ await waitFor(() => {
+ const calls = fetchMock.callHistory.calls('put-dashboard-payload');
+ expect(calls.length).toBeGreaterThan(0);
+ });
+
+ const calls = fetchMock.callHistory.calls('put-dashboard-payload');
+ const body = JSON.parse(calls[calls.length - 1].options.body as string);
+
+ // Dashboard payload: dashboard field is the numeric ID, chart is null
+ expect(body.dashboard).toBe(1);
+ expect(body.chart).toBeNull();
+ expect(body.name).toBe('Test Alert');
+ expect(body.recipients).toBeDefined();
+
+ fetchMock.removeRoute('put-dashboard-payload');
+});
+
+// --------------- Existing filter reappear test ------------------
+
+test('filter reappears in dropdown after clearing with X icon', async () => {
+ const chartDataEndpoint = 'glob:*/api/v1/chart/data*';
+
+ fetchMock.removeRoute(tabsEndpoint);
+ fetchMock.get(
+ tabsEndpoint,
+ {
+ result: {
+ all_tabs: { tab1: 'Tab 1' },
+ tab_tree: [{ title: 'Tab 1', value: 'tab1' }],
+ native_filters: {
+ all: [
+ {
+ id: 'NATIVE_FILTER-test1',
+ name: 'Test Filter 1',
+ filterType: 'filter_select',
+ targets: [{ column: { name: 'test_column_1' } }],
+ adhoc_filters: [],
+ },
+ ],
+ tab1: [
+ {
+ id: 'NATIVE_FILTER-test2',
+ name: 'Test Filter 2',
+ filterType: 'filter_select',
+ targets: [{ column: { name: 'test_column_2' } }],
+ adhoc_filters: [],
+ },
+ ],
+ },
+ },
+ },
+ { name: tabsEndpoint },
+ );
+
+ fetchMock.post(
+ chartDataEndpoint,
+ { result: [{ data: [] }] },
+ {
+ name: 'clear-icon-chart-data',
+ },
+ );
+
+ render(<AlertReportModal {...generateMockedProps(true, true)} />, {
+ useRedux: true,
+ });
+
+ userEvent.click(screen.getByTestId('contents-panel'));
+ await screen.findByText(/test dashboard/i);
+
+ const filterDropdown = screen.getByRole('combobox', {
+ name: /select filter/i,
+ });
+ expect(filterDropdown).toBeInTheDocument();
+
+ // Value selector should be disabled before any filter is selected
+ const valueSelect = screen.getByRole('combobox', { name: /select value/i });
+ expect(valueSelect.closest('.ant-select')).toHaveClass('ant-select-disabled');
+
+ userEvent.click(filterDropdown);
+
+ const filterOption = await waitFor(() => {
+ const virtualList = document.querySelector('.rc-virtual-list');
+ return within(virtualList as HTMLElement).getByText('Test Filter 1');
+ });
+
+ userEvent.click(filterOption);
+
+ await waitFor(() => {
+ const selectionItem = document.querySelector(
+ '.ant-select-selection-item[title="Test Filter 1"]',
+ );
+ expect(selectionItem).toBeInTheDocument();
+ });
+
+ // After selecting a filter, getChartDataRequest resolves and value selector becomes enabled
+ await waitFor(() => {
+ expect(valueSelect.closest('.ant-select')).not.toHaveClass(
+ 'ant-select-disabled',
+ );
+ });
+
+ const selectContainer = filterDropdown.closest('.ant-select');
+
+ const clearIcon = selectContainer?.querySelector(
+ '.ant-select-clear [aria-label="close-circle"]',
+ );
+ expect(clearIcon).toBeInTheDocument();
+ userEvent.click(clearIcon as Element);
+
+ await waitFor(() => {
+ const selectionItem = document.querySelector(
+ '.ant-select-selection-item[title="Test Filter 1"]',
+ );
+ expect(selectionItem).not.toBeInTheDocument();
+ });
+
+ // After clearing, value selector should be disabled again (optionFilterValues reset)
+ await waitFor(() => {
+ expect(valueSelect.closest('.ant-select')).toHaveClass(
+ 'ant-select-disabled',
+ );
+ });
+
+ userEvent.click(filterDropdown);
+ await waitFor(() => {
+ const virtualList = document.querySelector('.rc-virtual-list');
+ expect(
+ within(virtualList as HTMLElement).getByText('Test Filter 1'),
+ ).toBeInTheDocument();
+ });
+});
+
++const setupAnchorMocks = (
++ nativeFilters: Record<string, unknown>,
++ anchor = 'TAB-abc',
++ tabsOverride?: {
++ all_tabs: Record<string, string>;
++ tab_tree: { title: string; value: string }[];
++ },
++) => {
++ const payloadWithAnchor = {
++ ...generateMockPayload(true),
++ extra: { dashboard: { anchor } },
++ };
++
++ const defaultTabs = {
++ all_tabs: { [anchor]: `Tab ${anchor}` },
++ tab_tree: [{ title: `Tab ${anchor}`, value: anchor }],
++ };
++ const tabs = tabsOverride ?? defaultTabs;
++
++ // Clear call history so waitFor assertions don't match calls from prior tests.
++ fetchMock.callHistory.clear();
++
++ // Only replace the named routes that need anchor-specific overrides;
++ // unnamed related-endpoint routes (owners, database, etc.) stay intact.
++ fetchMock.removeRoute(FETCH_DASHBOARD_ENDPOINT);
++ fetchMock.removeRoute(FETCH_CHART_ENDPOINT);
++ fetchMock.removeRoute(tabsEndpoint);
++
++ fetchMock.get(
++ FETCH_DASHBOARD_ENDPOINT,
++ { result: payloadWithAnchor },
++ { name: FETCH_DASHBOARD_ENDPOINT },
++ );
++ fetchMock.get(
++ FETCH_CHART_ENDPOINT,
++ { result: generateMockPayload(false) },
++ { name: FETCH_CHART_ENDPOINT },
++ );
++ fetchMock.get(
++ tabsEndpoint,
++ {
++ result: {
++ ...tabs,
++ native_filters: nativeFilters,
++ },
++ },
++ { name: tabsEndpoint },
++ );
++};
++
++const restoreAnchorMocks = () => {
++ fetchMock.removeRoute(FETCH_DASHBOARD_ENDPOINT);
++ fetchMock.get(
++ FETCH_DASHBOARD_ENDPOINT,
++ { result: generateMockPayload(true) },
++ { name: FETCH_DASHBOARD_ENDPOINT },
++ );
++ fetchMock.removeRoute(FETCH_CHART_ENDPOINT);
++ fetchMock.get(
++ FETCH_CHART_ENDPOINT,
++ { result: generateMockPayload(false) },
++ { name: FETCH_CHART_ENDPOINT },
++ );
++ fetchMock.removeRoute(tabsEndpoint);
++ fetchMock.get(
++ tabsEndpoint,
++ { result: { all_tabs: {}, tab_tree: [] } },
++ { name: tabsEndpoint },
++ );
++};
++
++test('no error toast when anchor tab has no scoped native filters', async () => {
++ setupAnchorMocks({
++ all: [
++ {
++ id: 'NATIVE_FILTER-1',
++ name: 'Filter 1',
++ filterType: 'filter_select',
++ targets: [{ column: { name: 'col' } }],
++ adhoc_filters: [],
++ },
++ ],
++ });
++
++ const store = createStore({}, reducerIndex);
++
++ try {
++ render(<AlertReportModal {...generateMockedProps(true, true)} />, {
++ store,
++ });
++
++ userEvent.click(screen.getByTestId('contents-panel'));
+ await screen.findByText(/test dashboard/i);
+
+ await waitFor(() => {
+ expect(
+ fetchMock.callHistory
+ .calls()
+ .some(c => c.url.includes('/dashboard/1/tabs')),
+ ).toBe(true);
+ });
+
+ const toasts = (store.getState() as Record<string, unknown>)
+ .messageToasts as { text: string }[];
+ expect(
+ toasts.some(
+ (toast: { text: string }) =>
+ toast.text === 'There was an error retrieving dashboard tabs.',
+ ),
+ ).toBe(false);
+ } finally {
+ restoreAnchorMocks();
+ }
+ });
+
+ test('no error toast when anchor tab set and dashboard has zero native filters', async () => {
+ setupAnchorMocks({});
+
+ const store = createStore({}, reducerIndex);
+
+ try {
+ render(<AlertReportModal {...generateMockedProps(true, true)} />, {
+ store,
+ });
+
+ userEvent.click(screen.getByTestId('contents-panel'));
+ await screen.findByText(/test dashboard/i);
+
+ await waitFor(() => {
+ expect(
+ fetchMock.callHistory
+ .calls()
+ .some(c => c.url.includes('/dashboard/1/tabs')),
+ ).toBe(true);
+ });
+
+ const toasts = (store.getState() as Record<string, unknown>)
+ .messageToasts as { text: string }[];
+ expect(
+ toasts.some(
+ (toast: { text: string }) =>
+ toast.text === 'There was an error retrieving dashboard tabs.',
+ ),
+ ).toBe(false);
+ } finally {
+ restoreAnchorMocks();
+ }
+ });
+
+ test('stale JSON array anchor is cleared without crash or toast', async () => {
+ const staleAnchor = JSON.stringify(['TAB-abc', 'TAB-missing']);
+ setupAnchorMocks(
+ {
+ all: [
+ {
+ id: 'NATIVE_FILTER-1',
+ name: 'Filter 1',
+ filterType: 'filter_select',
+ targets: [{ column: { name: 'col' } }],
+ adhoc_filters: [],
+ },
+ ],
+ },
+ staleAnchor,
+ {
+ all_tabs: { 'TAB-abc': 'Tab ABC' },
+ tab_tree: [{ title: 'Tab ABC', value: 'TAB-abc' }],
+ },
+ );
+
+ const store = createStore({}, reducerIndex);
+
+ try {
+ render(<AlertReportModal {...generateMockedProps(true, true)} />, {
+ store,
+ });
+
+ userEvent.click(screen.getByTestId('contents-panel'));
+ await screen.findByText(/test dashboard/i);
+
+ // Wait for the tabs useEffect to process the stale anchor
+ await waitFor(() => {
+ expect(
+ fetchMock.callHistory
+ .calls()
+ .some(c => c.url.includes('/dashboard/1/tabs')),
+ ).toBe(true);
+ });
+
+ // No error toast dispatched (the .then() handler ran without crashing)
+ const toasts = (store.getState() as Record<string, unknown>)
+ .messageToasts as { text: string }[];
+ expect(
+ toasts.some(
+ (toast: { text: string }) =>
+ toast.text === 'There was an error retrieving dashboard tabs.',
+ ),
+ ).toBe(false);
+
+ // Verify anchor was cleared at the payload level: trigger save and
+ // inspect the PUT body to confirm extra.dashboard.anchor is undefined
+ const updateEndpoint = 'glob:*/api/v1/report/1';
+ fetchMock.put(
+ updateEndpoint,
+ { id: 1, result: {} },
+ { name: 'put-report-1' },
+ );
+
+ const saveButton = screen.getByRole('button', { name: /save/i });
+ expect(saveButton).not.toBeDisabled();
+ userEvent.click(saveButton);
+
+ await waitFor(() => {
+ const putCalls = fetchMock.callHistory
+ .calls()
+ .filter(
+ c => c.url.includes('/api/v1/report/') && c.options?.method === 'put',
+ );
+ expect(putCalls).toHaveLength(1);
+ });
+
+ const putCall = fetchMock.callHistory
+ .calls()
+ .find(
+ c => c.url.includes('/api/v1/report/') && c.options?.method === 'put',
+ );
+ const body = JSON.parse(putCall!.options?.body as string);
+ expect(body.extra.dashboard.anchor).toBeUndefined();
+ } finally {
+ fetchMock.removeRoute('put-report-1');
+ restoreAnchorMocks();
+ }
+ });
+
+ test('tabs API failure shows danger toast via Redux store', async () => {
+ fetchMock.removeRoute(tabsEndpoint);
+ fetchMock.get(tabsEndpoint, 500, { name: tabsEndpoint });
+
+ const store = createStore({}, reducerIndex);
+
+ try {
+ render(<AlertReportModal {...generateMockedProps(true, true)} />, {
+ store,
+ });
+
+ userEvent.click(screen.getByTestId('contents-panel'));
+
+ await waitFor(() => {
+ const toasts = (store.getState() as Record<string, unknown>)
+ .messageToasts as { text: string }[];
+ expect(
+ toasts.some(
+ (toast: { text: string }) =>
+ toast.text === 'There was an error retrieving dashboard tabs.',
+ ),
+ ).toBe(true);
+ });
+ } finally {
+ fetchMock.removeRoute(tabsEndpoint);
+ fetchMock.get(
+ tabsEndpoint,
+ { result: { all_tabs: {}, tab_tree: [] } },
+ { name: tabsEndpoint },
+ );
+ }
+ });
+
+ test('null all_tabs does not crash or show error toast', async () => {
+ setupAnchorMocks({
+ all: [
+ {
+ id: 'NATIVE_FILTER-1',
+ name: 'Filter 1',
+ filterType: 'filter_select',
+ targets: [{ column: { name: 'col' } }],
+ adhoc_filters: [],
+ },
+ ],
+ });
+
+ // Override tabs endpoint with null all_tabs
+ fetchMock.removeRoute(tabsEndpoint);
+ fetchMock.get(
+ tabsEndpoint,
+ {
+ result: {
+ all_tabs: null,
+ tab_tree: [{ title: 'Tab ABC', value: 'TAB-abc' }],
+ native_filters: {
+ all: [
+ {
+ id: 'NATIVE_FILTER-1',
+ name: 'Filter 1',
+ filterType: 'filter_select',
+ targets: [{ column: { name: 'col' } }],
+ adhoc_filters: [],
+ },
+ ],
+ },
+ },
+ },
+ { name: tabsEndpoint },
+ );
+
+ const store = createStore({}, reducerIndex);
+
+ try {
+ render(<AlertReportModal {...generateMockedProps(true, true)} />, {
+ store,
+ });
+
+ userEvent.click(screen.getByTestId('contents-panel'));
+ await screen.findByText(/test dashboard/i);
+
+ // Wait for tabs useEffect to complete
+ await waitFor(() => {
+ expect(
+ fetchMock.callHistory
+ .calls()
+ .some(c => c.url.includes('/dashboard/1/tabs')),
+ ).toBe(true);
+ });
+
+ // No error toast dispatched
+ const toasts = (store.getState() as Record<string, unknown>)
+ .messageToasts as { text: string }[];
+ expect(
+ toasts.some(
+ (toast: { text: string }) =>
+ toast.text === 'There was an error retrieving dashboard tabs.',
+ ),
+ ).toBe(false);
+
+ // Component remains interactive
+ expect(
+ screen.getByRole('combobox', { name: /select filter/i }),
+ ).toBeInTheDocument();
+ } finally {
+ restoreAnchorMocks();
+ }
+ });
+
+ test('missing native_filters in tabs response does not crash or show error toast', async () => {
+ setupAnchorMocks({});
+
+ // Override tabs endpoint with no native_filters key
+ fetchMock.removeRoute(tabsEndpoint);
+ fetchMock.get(
+ tabsEndpoint,
+ {
+ result: {
+ all_tabs: { 'TAB-abc': 'Tab ABC' },
+ tab_tree: [{ title: 'Tab ABC', value: 'TAB-abc' }],
+ },
+ },
+ { name: tabsEndpoint },
+ );
+
+ const store = createStore({}, reducerIndex);
+
+ try {
+ render(<AlertReportModal {...generateMockedProps(true, true)} />, {
+ store,
+ });
+
+ userEvent.click(screen.getByTestId('contents-panel'));
+ await screen.findByText(/test dashboard/i);
+
+ // Wait for tabs useEffect to complete
+ await waitFor(() => {
+ expect(
+ fetchMock.callHistory
+ .calls()
+ .some(c => c.url.includes('/dashboard/1/tabs')),
+ ).toBe(true);
+ });
+
+ // No error toast dispatched
+ const toasts = (store.getState() as Record<string, unknown>)
+ .messageToasts as { text: string }[];
+ expect(
+ toasts.some(
+ (toast: { text: string }) =>
+ toast.text === 'There was an error retrieving dashboard tabs.',
+ ),
+ ).toBe(false);
+ } finally {
+ restoreAnchorMocks();
+ }
+ });
+
+ test('anchor tab with scoped filters loads filter options correctly', async () => {
+ // Use JSON-parseable non-array anchor to exercise the scoped filter
+ // code path at line 1108 (JSON.parse('42') → 42, not an array)
+ setupAnchorMocks(
+ {
+ all: [
+ {
+ id: 'NATIVE_FILTER-1',
+ name: 'Global Filter',
+ filterType: 'filter_select',
+ targets: [{ column: { name: 'col' } }],
+ adhoc_filters: [],
+ },
+ ],
+ '42': [
+ {
+ id: 'NATIVE_FILTER-2',
+ name: 'Tab Scoped Filter',
+ filterType: 'filter_select',
+ targets: [{ column: { name: 'col2' } }],
+ adhoc_filters: [],
+ },
+ ],
+ },
+ '42',
+ );
+
+ const store = createStore({}, reducerIndex);
+
+ try {
+ render(<AlertReportModal {...generateMockedProps(true, true)} />, {
+ store,
+ });
+
+ userEvent.click(screen.getByTestId('contents-panel'));
+ await screen.findByText(/test dashboard/i);
+
+ const filterDropdown = screen.getByRole('combobox', {
+ name: /select filter/i,
+ });
+ userEvent.click(filterDropdown);
+
+ const filterOption = await screen.findByRole('option', {
+ name: /Tab Scoped Filter/,
+ });
+ expect(filterOption).toBeInTheDocument();
+
+ const toasts = (store.getState() as Record<string, unknown>)
+ .messageToasts as { text: string }[];
+ expect(
+ toasts.some(
+ (toast: { text: string }) =>
+ toast.text === 'There was an error retrieving dashboard tabs.',
+ ),
+ ).toBe(false);
+ } finally {
+ restoreAnchorMocks();
+ }
+ });
+
test('edit mode shows friendly filter names instead of raw IDs', async () => {
const props = generateMockedProps(true, true);
const editProps = {