From 19e8a7049bb870e0e643f59fb687c978667af547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90=E1=BB=97=20Tr=E1=BB=8Dng=20H=E1=BA=A3i?= <41283691+hainenber@users.noreply.github.com> Date: Wed, 29 Jan 2025 18:40:33 +0700 Subject: [PATCH 01/23] feat(fe): upgrade `superset-frontend` to Typescript v5 (#31979) Signed-off-by: hainenber Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com> --- superset-frontend/package-lock.json | 167 +++++++++++++++--- superset-frontend/package.json | 2 +- .../superset-ui-chart-controls/src/types.ts | 7 + .../test/types.test.ts | 17 ++ .../src/query/getClientErrorObject.ts | 2 +- .../src/query/types/Filter.ts | 4 + .../superset-ui-core/src/query/types/Query.ts | 20 ++- .../src/query/types/QueryResponse.ts | 3 + .../src/utils/featureFlags.ts | 2 +- .../src/validator/validateNumber.ts | 2 +- .../customTimeRangeDecode.test.ts | 78 ++++++++ .../src/CategoricalDeckGLContainer.tsx | 2 +- .../src/Multi/Multi.tsx | 1 + .../src/components/Legend.tsx | 4 +- .../src/layers/Geojson/Geojson.tsx | 2 +- .../src/layers/common.tsx | 6 +- .../legacy-preset-chart-deckgl/src/utils.ts | 7 +- .../src/utils/explore.ts | 2 +- .../src/utils/sandbox.ts | 13 +- .../src/util/controlPanelUtil.tsx | 2 +- .../src/util/transformPropsUtil.ts | 2 +- .../test/util/layerUtil.test.ts | 1 + .../transformProps.ts | 3 +- .../BigNumberPeriodOverPeriod/utils.ts | 2 +- .../BigNumber/BigNumberTotal/controlPanel.ts | 9 +- .../src/Bubble/transformProps.ts | 35 +++- .../src/MixedTimeseries/transformProps.ts | 4 +- .../src/utils/formDataSuffix.ts | 4 +- .../test/Timeseries/transformProps.test.ts | 2 +- .../src/plugin/controlPanel.tsx | 7 +- .../src/DataTable/components/Pagination.tsx | 5 +- .../plugin-chart-table/src/controlPanel.tsx | 6 +- superset-frontend/spec/helpers/Cache.ts | 4 +- superset-frontend/spec/helpers/shim.tsx | 5 +- .../KeyboardShortcutButton.test.tsx | 4 +- .../KeyboardShortcutButton/index.tsx | 19 +- .../SqlLab/components/QueryTable/index.tsx | 15 +- .../components/ScheduleQueryButton/index.tsx | 4 +- superset-frontend/src/SqlLab/constants.ts | 2 +- .../middlewares/persistSqlLabStateEnhancer.js | 3 +- .../src/SqlLab/reducers/getInitialState.ts | 2 +- .../SqlLab/utils/emptyQueryResults.test.ts | 2 +- .../utils/reduxStateToLocalStorageHelper.ts | 39 +++- .../src/components/AlteredSliceTag/index.tsx | 2 +- .../components/Chart/DrillBy/DrillByModal.tsx | 9 +- .../Chart/DrillDetail/DrillDetailPane.tsx | 9 +- .../components/Checkbox/Checkbox.stories.tsx | 4 +- ...able.test.jsx => CollectionTable.test.tsx} | 1 + .../components/Datasource/CollectionTable.tsx | 23 +-- .../DatePicker/DatePicker.stories.tsx | 4 +- .../src/components/DatePicker/index.tsx | 6 +- .../src/components/EmptyState/index.tsx | 5 +- .../ErrorMessage/MarshmallowErrorMessage.tsx | 10 +- .../src/components/FilterableTable/index.tsx | 4 +- .../src/components/FlashProvider/index.tsx | 2 +- .../src/components/Icons/AntdEnhanced.tsx | 2 + .../src/components/Icons/Icons.stories.tsx | 2 +- .../src/components/Label/index.tsx | 7 +- .../src/components/ListView/types.ts | 20 ++- .../src/components/ListView/utils.ts | 5 +- .../components/Select/AsyncSelect.test.tsx | 6 +- .../src/components/Select/utils.tsx | 15 +- .../src/components/Steps/Steps.stories.tsx | 3 +- .../src/components/Table/Table.stories.tsx | 52 ++++-- .../Table/cell-renderers/ActionCell/index.tsx | 2 +- .../src/components/Table/index.tsx | 14 +- .../src/components/Table/sorters.test.ts | 8 + .../src/components/Table/sorters.ts | 14 +- .../src/components/TimezoneSelector/index.tsx | 4 +- .../components/ColorSchemeControlWrapper.jsx | 76 -------- .../components/ColorSchemeControlWrapper.tsx | 64 +++++++ .../DashboardBuilder/DashboardContainer.tsx | 2 +- .../components/FiltersBadge/index.tsx | 16 +- .../components/PropertiesModal/index.tsx | 2 - .../components/SyncDashboardState/index.tsx | 5 +- .../ScopingModal/ScopingModal.tsx | 5 +- .../nativeFilters/FilterBar/index.tsx | 6 +- .../FiltersConfigForm/utils.ts | 4 +- .../components/nativeFilters/selectors.ts | 44 +++-- .../components/nativeFilters/state.ts | 4 +- .../components/nativeFilters/utils.ts | 22 ++- .../src/dashboard/reducers/nativeFilters.ts | 3 +- superset-frontend/src/dashboard/types.ts | 27 +-- .../util/activeAllDashboardFilters.ts | 15 +- .../charts/getFormDataWithExtraFilters.ts | 34 +++- .../src/dashboard/util/crossFilters.ts | 8 +- .../src/dashboard/util/findParentId.ts | 8 +- ...t.js => findTabIndexByComponentId.test.ts} | 9 +- ...nentId.js => findTabIndexByComponentId.ts} | 13 +- .../util/getFormDataWithExtraFilters.test.ts | 4 +- .../src/dashboard/util/isValidChild.test.ts | 2 +- .../src/dashboard/util/isValidChild.ts | 2 +- superset-frontend/src/dataMask/reducer.ts | 20 +-- .../src/explore/components/Control.tsx | 5 +- .../components/ControlPanelsContainer.tsx | 4 +- .../components/DataTableControl/index.tsx | 2 +- .../components/DatasourcePanel/index.tsx | 3 +- .../src/explore/components/ExploreAlert.tsx | 6 +- .../ColorSchemeControl.test.tsx | 1 - .../controls/ColorSchemeControl/index.tsx | 6 +- .../controls/ComparisonRangeLabel.tsx | 3 +- .../ContourControl/ContourPopoverControl.tsx | 4 +- .../controls/CustomListItem/index.tsx | 2 +- .../index.tsx | 6 +- .../FilterControl/utils/translateToSQL.ts | 8 +- .../controls/SliderControl.stories.tsx | 19 +- .../components/controls/TimeOffsetControl.tsx | 2 +- .../VizTypeControl/VizTypeGallery.tsx | 2 +- .../explore/controlUtils/getControlState.ts | 2 +- .../controlUtils/getFormDataFromControls.ts | 4 +- .../getFormDataWithDashboardContext.ts | 7 +- .../controlUtils/getSectionsToRender.ts | 10 +- .../controlUtils/standardizedFormData.test.ts | 4 +- .../controlUtils/standardizedFormData.ts | 2 +- .../exploreUtils/getParsedExploreURLParams.ts | 64 ++++--- .../src/features/alerts/AlertReportModal.tsx | 10 +- .../features/allEntities/AllEntitiesTable.tsx | 20 +-- .../annotationLayers/AnnotationLayerModal.tsx | 8 +- .../features/annotations/AnnotationModal.tsx | 3 +- .../cssTemplates/CssTemplateModal.tsx | 8 +- .../DatabaseConnectionForm/EncryptedField.tsx | 15 +- .../ValidatedInputField.tsx | 14 +- .../DatabaseConnectionForm/index.tsx | 1 + .../databases/DatabaseModal/ModalHeader.tsx | 10 +- .../databases/DatabaseModal/index.tsx | 61 +++++-- .../src/features/databases/types.ts | 48 ++--- .../AddDataset/DatasetPanel/DatasetPanel.tsx | 9 +- .../datasets/AddDataset/Footer/index.tsx | 2 +- .../src/features/home/ActivityTable.tsx | 14 +- .../src/features/home/RightMenu.tsx | 6 +- .../src/features/tags/TagModal.tsx | 2 +- superset-frontend/src/features/tags/tags.ts | 7 +- .../src/hooks/apiResources/datasets.ts | 2 +- superset-frontend/src/hooks/useLocale.ts | 7 +- .../src/pages/AllEntities/index.tsx | 24 +-- superset-frontend/src/reduxUtils.ts | 14 +- superset-frontend/src/types/TaggedObject.ts | 23 ++- .../types.ts} | 25 +-- superset-frontend/src/views/CRUD/hooks.ts | 11 +- .../TimeTable/transformProps.ts | 4 +- superset-frontend/tsconfig.json | 3 - 141 files changed, 1095 insertions(+), 572 deletions(-) rename superset-frontend/src/components/Datasource/{CollectionTable.test.jsx => CollectionTable.test.tsx} (98%) delete mode 100644 superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx create mode 100644 superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.tsx rename superset-frontend/src/dashboard/util/{findTabIndexByComponentId.test.js => findTabIndexByComponentId.test.ts} (91%) rename superset-frontend/src/dashboard/util/{findTabIndexByComponentId.js => findTabIndexByComponentId.ts} (86%) rename superset-frontend/src/{dashboard/util/replaceUndefinedByNull.ts => utils/types.ts} (57%) diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 8e099819900fd..8f9a07604ad53 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -290,7 +290,7 @@ "thread-loader": "^4.0.4", "ts-jest": "^29.2.5", "ts-loader": "^9.5.1", - "typescript": "^4.8.4", + "typescript": "5.1.6", "vm-browserify": "^1.1.2", "webpack": "^5.97.1", "webpack-bundle-analyzer": "^4.10.1", @@ -13499,6 +13499,38 @@ "node": ">=12.0.0" } }, + "node_modules/@wdio/config/node_modules/@types/node": { + "version": "18.19.74", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.74.tgz", + "integrity": "sha512-HMwEkkifei3L605gFdV+/UwtpxP6JSzM+xFk2Ia6DNFSwSVBRh9qp5Tgf4lNFOMfPVuU0WnkcWpXZpgn5ufO4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@wdio/config/node_modules/@wdio/types": { + "version": "7.30.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.30.2.tgz", + "integrity": "sha512-uZ8o7FX8RyBsaXiOWa59UKTCHTtADNvOArYTcHNEIzt+rh4JdB/uwqfc8y4TCNA2kYm7PWaQpUFwpStLeg0H1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^18.0.0", + "got": "^11.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "^4.6.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@wdio/config/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -13543,6 +13575,29 @@ "node": ">=10" } }, + "node_modules/@wdio/config/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/@wdio/config/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, "node_modules/@wdio/logger": { "version": "7.26.0", "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.26.0.tgz", @@ -13586,7 +13641,32 @@ "node": ">=12.0.0" } }, - "node_modules/@wdio/types": { + "node_modules/@wdio/utils": { + "version": "7.30.2", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.30.2.tgz", + "integrity": "sha512-np7I+smszFUennbQKdzbMN/zUL3s3EZq9pCCUcTRjjs9TE4tnn0wfmGdoz2o7REYu6kn9NfFFJyVIM2VtBbKEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@wdio/logger": "7.26.0", + "@wdio/types": "7.30.2", + "p-iteration": "^1.1.8" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@wdio/utils/node_modules/@types/node": { + "version": "18.19.74", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.74.tgz", + "integrity": "sha512-HMwEkkifei3L605gFdV+/UwtpxP6JSzM+xFk2Ia6DNFSwSVBRh9qp5Tgf4lNFOMfPVuU0WnkcWpXZpgn5ufO4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@wdio/utils/node_modules/@wdio/types": { "version": "7.30.2", "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.30.2.tgz", "integrity": "sha512-uZ8o7FX8RyBsaXiOWa59UKTCHTtADNvOArYTcHNEIzt+rh4JdB/uwqfc8y4TCNA2kYm7PWaQpUFwpStLeg0H1Q==", @@ -13608,38 +13688,29 @@ } } }, - "node_modules/@wdio/types/node_modules/@types/node": { - "version": "18.19.74", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.74.tgz", - "integrity": "sha512-HMwEkkifei3L605gFdV+/UwtpxP6JSzM+xFk2Ia6DNFSwSVBRh9qp5Tgf4lNFOMfPVuU0WnkcWpXZpgn5ufO4A==", + "node_modules/@wdio/utils/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" + "license": "Apache-2.0", + "optional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" } }, - "node_modules/@wdio/types/node_modules/undici-types": { + "node_modules/@wdio/utils/node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true, "license": "MIT" }, - "node_modules/@wdio/utils": { - "version": "7.30.2", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.30.2.tgz", - "integrity": "sha512-np7I+smszFUennbQKdzbMN/zUL3s3EZq9pCCUcTRjjs9TE4tnn0wfmGdoz2o7REYu6kn9NfFFJyVIM2VtBbKEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@wdio/logger": "7.26.0", - "@wdio/types": "7.30.2", - "p-iteration": "^1.1.8" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -49029,16 +49100,16 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/typescript-json-schema": { @@ -50328,6 +50399,44 @@ "undici-types": "~5.26.4" } }, + "node_modules/webdriver/node_modules/@wdio/types": { + "version": "7.30.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.30.2.tgz", + "integrity": "sha512-uZ8o7FX8RyBsaXiOWa59UKTCHTtADNvOArYTcHNEIzt+rh4JdB/uwqfc8y4TCNA2kYm7PWaQpUFwpStLeg0H1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^18.0.0", + "got": "^11.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "^4.6.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/webdriver/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/webdriver/node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 7fe0b309082aa..7edc690f35e39 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -357,7 +357,7 @@ "thread-loader": "^4.0.4", "ts-jest": "^29.2.5", "ts-loader": "^9.5.1", - "typescript": "^4.8.4", + "typescript": "5.1.6", "vm-browserify": "^1.1.2", "webpack": "^5.97.1", "webpack-bundle-analyzer": "^4.10.1", diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts index e9606c6ba40a3..09667004a5659 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts @@ -363,6 +363,13 @@ export type CustomControlItem = { config: BaseControlConfig; }; +export const isCustomControlItem = (obj: unknown): obj is CustomControlItem => + typeof obj === 'object' && + obj !== null && + typeof ('name' in obj && obj.name) === 'string' && + typeof ('config' in obj && obj.config) === 'object' && + (obj as CustomControlItem).config !== null; + // use ReactElement instead of ReactNode because `string`, `number`, etc. may // interfere with other ControlSetItem types export type ExpandedControlItem = CustomControlItem | ReactElement | null; diff --git a/superset-frontend/packages/superset-ui-chart-controls/test/types.test.ts b/superset-frontend/packages/superset-ui-chart-controls/test/types.test.ts index 4a298645efc63..c4447485b72f4 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/test/types.test.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/test/types.test.ts @@ -20,8 +20,10 @@ import { AdhocColumn } from '@superset-ui/core'; import { ColumnMeta, ControlPanelSectionConfig, + CustomControlItem, isColumnMeta, isControlPanelSectionConfig, + isCustomControlItem, isSavedExpression, } from '../src'; @@ -43,6 +45,13 @@ const CONTROL_PANEL_SECTION_CONFIG: ControlPanelSectionConfig = { description: 'My Description', controlSetRows: [], }; +const CUSTOM_CONTROL_ITEM: CustomControlItem = { + name: 'Custom Control Item', + config: { + type: 'config', + foo: 'bar', + }, +}; test('isColumnMeta returns false for AdhocColumn', () => { expect(isColumnMeta(ADHOC_COLUMN)).toEqual(false); @@ -73,3 +82,11 @@ test('isControlPanelSectionConfig returns true for section', () => { test('isControlPanelSectionConfig returns true for null value', () => { expect(isControlPanelSectionConfig(null)).toEqual(false); }); + +test('isCustomControlItem returns true for proper CustomControlItem', () => { + expect(isCustomControlItem(CUSTOM_CONTROL_ITEM)).toEqual(true); +}); + +test('isCustomControlItem returns false for generic object', () => { + expect(isCustomControlItem({})).toEqual(false); +}); diff --git a/superset-frontend/packages/superset-ui-core/src/query/getClientErrorObject.ts b/superset-frontend/packages/superset-ui-core/src/query/getClientErrorObject.ts index dd26aec6192d7..8097134e6d67f 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/getClientErrorObject.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/getClientErrorObject.ts @@ -105,7 +105,7 @@ export function parseStringResponse(str: string): string { } export function getErrorFromStatusCode(status: number): string | null { - return ERROR_CODE_LOOKUP[status] || null; + return ERROR_CODE_LOOKUP[status as keyof typeof ERROR_CODE_LOOKUP] || null; } export function retrieveErrorMessage( diff --git a/superset-frontend/packages/superset-ui-core/src/query/types/Filter.ts b/superset-frontend/packages/superset-ui-core/src/query/types/Filter.ts index bd7983f73630d..a9aa7b653eeba 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/types/Filter.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/types/Filter.ts @@ -62,6 +62,10 @@ export interface FreeFormAdhocFilter extends BaseAdhocFilter { sqlExpression: string; } +export interface LatestPartitionAdhocFilter extends BaseAdhocFilter { + datasource?: { schema?: string; datasource_name?: string }; +} + export type AdhocFilter = SimpleAdhocFilter | FreeFormAdhocFilter; //--------------------------------------------------- diff --git a/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts b/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts index a83d17a937f6a..4a5f2a6859080 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts @@ -349,15 +349,17 @@ export type Query = { }; export type QueryResults = { - results: { - displayLimitReached: boolean; - columns: QueryColumn[]; - data: Record[]; - expanded_columns: QueryColumn[]; - selected_columns: QueryColumn[]; - query: { limit: number }; - query_id?: number; - }; + results: InnerQueryResults; +}; + +export type InnerQueryResults = { + displayLimitReached: boolean; + columns: QueryColumn[]; + data: Record[]; + expanded_columns: QueryColumn[]; + selected_columns: QueryColumn[]; + query: { limit: number }; + query_id?: number; }; export type QueryResponse = Query & QueryResults; diff --git a/superset-frontend/packages/superset-ui-core/src/query/types/QueryResponse.ts b/superset-frontend/packages/superset-ui-core/src/query/types/QueryResponse.ts index 93c7475d42e11..b2a3c08cdfeff 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/types/QueryResponse.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/types/QueryResponse.ts @@ -79,6 +79,9 @@ export interface ChartDataResponseResult { | 'timed_out'; from_dttm: number | null; to_dttm: number | null; + // TODO(hainenber): define proper type for below attributes + rejected_filters?: any[]; + applied_filters?: any[]; } export interface TimeseriesChartDataResponseResult diff --git a/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts b/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts index 216a4ba58138c..7c6041d0060e6 100644 --- a/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts +++ b/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts @@ -93,7 +93,7 @@ export function initFeatureFlags(featureFlags?: FeatureFlagMap) { export function isFeatureEnabled(feature: FeatureFlag): boolean { try { - return !!window.featureFlags[feature]; + return !!window.featureFlags[feature as keyof FeatureFlagMap]; } catch (error) { logger.error(`Failed to query feature flag ${feature}`); } diff --git a/superset-frontend/packages/superset-ui-core/src/validator/validateNumber.ts b/superset-frontend/packages/superset-ui-core/src/validator/validateNumber.ts index 7de5f3f27cc85..3775a56cb5c5e 100644 --- a/superset-frontend/packages/superset-ui-core/src/validator/validateNumber.ts +++ b/superset-frontend/packages/superset-ui-core/src/validator/validateNumber.ts @@ -19,7 +19,7 @@ import { t } from '../translation'; -export default function validateInteger(v: unknown) { +export default function validateInteger(v: any) { if ( (typeof v === 'string' && v.trim().length > 0 && diff --git a/superset-frontend/packages/superset-ui-core/test/time-comparison/customTimeRangeDecode.test.ts b/superset-frontend/packages/superset-ui-core/test/time-comparison/customTimeRangeDecode.test.ts index d5342098a5771..b33841134823e 100644 --- a/superset-frontend/packages/superset-ui-core/test/time-comparison/customTimeRangeDecode.test.ts +++ b/superset-frontend/packages/superset-ui-core/test/time-comparison/customTimeRangeDecode.test.ts @@ -202,4 +202,82 @@ describe('customTimeRangeDecode', () => { matchedFlag: false, }); }); + + it('9) empty string returns default', () => { + const SEVEN_DAYS_AGO = new Date(); + SEVEN_DAYS_AGO.setHours(0, 0, 0, 0); + + const MIDNIGHT = new Date(); + MIDNIGHT.setHours(0, 0, 0, 0); + + expect(customTimeRangeDecode('')).toEqual({ + customRange: { + sinceDatetime: SEVEN_DAYS_AGO.setDate( + SEVEN_DAYS_AGO.getDate() - 7, + ).toString(), + sinceMode: 'relative', + sinceGrain: 'day', + sinceGrainValue: -7, + untilDatetime: MIDNIGHT.toString(), + untilMode: 'specific', + untilGrain: 'day', + untilGrainValue: 7, + anchorMode: 'now', + anchorValue: 'now', + }, + matchedFlag: false, + }); + }); + + it('10) both undefined returns default', () => { + const SEVEN_DAYS_AGO = new Date(); + SEVEN_DAYS_AGO.setHours(0, 0, 0, 0); + + const MIDNIGHT = new Date(); + MIDNIGHT.setHours(0, 0, 0, 0); + + expect(customTimeRangeDecode('undefined : undefined')).toEqual({ + customRange: { + sinceDatetime: SEVEN_DAYS_AGO.setDate( + SEVEN_DAYS_AGO.getDate() - 7, + ).toString(), + sinceMode: 'relative', + sinceGrain: 'day', + sinceGrainValue: -7, + untilDatetime: MIDNIGHT.toString(), + untilMode: 'specific', + untilGrain: 'day', + untilGrainValue: 7, + anchorMode: 'now', + anchorValue: 'now', + }, + matchedFlag: false, + }); + }); + + it('11) 1 side undefined returns default', () => { + const SEVEN_DAYS_AGO = new Date(); + SEVEN_DAYS_AGO.setHours(0, 0, 0, 0); + + const MIDNIGHT = new Date(); + MIDNIGHT.setHours(0, 0, 0, 0); + + expect(customTimeRangeDecode('undefined : now')).toEqual({ + customRange: { + sinceDatetime: SEVEN_DAYS_AGO.setDate( + SEVEN_DAYS_AGO.getDate() - 7, + ).toString(), + sinceMode: 'relative', + sinceGrain: 'day', + sinceGrainValue: -7, + untilDatetime: MIDNIGHT.toString(), + untilMode: 'specific', + untilGrain: 'day', + untilGrainValue: 7, + anchorMode: 'now', + anchorValue: 'now', + }, + matchedFlag: false, + }); + }); }); diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/CategoricalDeckGLContainer.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/CategoricalDeckGLContainer.tsx index a7f32b5410abf..8f3d1dac61220 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/CategoricalDeckGLContainer.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/CategoricalDeckGLContainer.tsx @@ -54,7 +54,7 @@ function getCategories(fd: QueryFormData, data: JsonObject[]) { const fixedColor = [c.r, c.g, c.b, 255 * c.a]; const appliedScheme = fd.color_scheme; const colorFn = getScale(appliedScheme); - const categories = {}; + const categories: Record = {}; data.forEach(d => { if (d.cat_color != null && !categories.hasOwnProperty(d.cat_color)) { let color; diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/Multi/Multi.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/Multi/Multi.tsx index f26976f2ad364..a0391de7d8b24 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/Multi/Multi.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/Multi/Multi.tsx @@ -97,6 +97,7 @@ const DeckMulti = (props: DeckMultiProps) => { endpoint: url, }) .then(({ json }) => { + // @ts-ignore TODO(hainenber): define proper type for `form_data.viz_type` and call signature for functions in layerGenerators. const layer = layerGenerators[subsliceCopy.form_data.viz_type]( subsliceCopy.form_data, json, diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/components/Legend.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/components/Legend.tsx index ab8ae78b73efb..681b114ff82e2 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/components/Legend.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/components/Legend.tsx @@ -59,7 +59,7 @@ export type LegendProps = { format: string | null; forceCategorical?: boolean; position?: null | 'tl' | 'tr' | 'bl' | 'br'; - categories: Record; + categories: Record; toggleCategory?: (key: string) => void; showSingleCategory?: (key: string) => void; }; @@ -101,7 +101,7 @@ const Legend = ({ } const categories = Object.entries(categoriesObject).map(([k, v]) => { - const style = { color: `rgba(${v.color.join(', ')})` }; + const style = { color: `rgba(${v.color?.join(', ')})` }; const icon = v.enabled ? '\u25FC' : '\u25FB'; return ( diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Geojson/Geojson.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Geojson/Geojson.tsx index 197e56717047c..6960fed801215 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Geojson/Geojson.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/Geojson/Geojson.tsx @@ -59,7 +59,7 @@ const alterProps = (props: JsonObject, propOverrides: JsonObject) => { const newProps: JsonObject = {}; Object.keys(props).forEach(k => { if (k in propertyMap) { - newProps[propertyMap[k]] = props[k]; + newProps[propertyMap[k as keyof typeof propertyMap]] = props[k]; } else { newProps[k] = props[k]; } diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.tsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.tsx index 4185b38167807..4a6fb117bccc3 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.tsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/layers/common.tsx @@ -122,7 +122,11 @@ export function getAggFunc( sortedArr = arr.sort(d3ascending); } - return d3quantile(sortedArr, percentiles[type], acc); + return d3quantile( + sortedArr, + percentiles[type as keyof typeof percentiles], + acc, + ); }; } else if (type in d3functions) { d3func = d3functions[type]; diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.ts index 55d05fe5b6bdf..7e172d3ab81de 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils.ts @@ -172,8 +172,11 @@ export function getBuckets( ) { const breakPoints = getBreakPoints(fd, features, accessor); const colorScaler = getBreakPointColorScaler(fd, features, accessor); - const buckets = {}; - breakPoints.slice(1).forEach((value, i) => { + const buckets: Record< + string, + { color: [number, number, number, number] | undefined; enabled: boolean } + > = {}; + breakPoints.slice(1).forEach((_, i) => { const range = `${breakPoints[i]} - ${breakPoints[i + 1]}`; const mid = 0.5 * (parseFloat(breakPoints[i]) + parseFloat(breakPoints[i + 1])); diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils/explore.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils/explore.ts index 5d3ba876ee440..8b8ad24aabe37 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils/explore.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils/explore.ts @@ -36,7 +36,7 @@ export function getExploreLongUrl( formData: JsonObject, endpointType: string, allowOverflow = true, - extraSearch = {}, + extraSearch: Record = {}, ): string | undefined { if (!formData.datasource) { return undefined; diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils/sandbox.ts b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils/sandbox.ts index f9accf445dec9..1f4362b0232a3 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils/sandbox.ts +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utils/sandbox.ts @@ -34,19 +34,26 @@ const GLOBAL_CONTEXT = { d3array, }; +type GlobalContext = { + console: Console; + _: _.UnderscoreStatic; + colors: typeof colors; + d3array: typeof d3array; +}; + // Copied/modified from https://github.com/hacksparrow/safe-eval/blob/master/index.js export default function sandboxedEval( code: string, context?: Context, opts?: RunningScriptOptions | string, ) { - const sandbox = {}; + const sandbox: Context = {}; const resultKey = `SAFE_EVAL_${Math.floor(Math.random() * 1000000)}`; sandbox[resultKey] = {}; const codeToEval = `${resultKey}=${code}`; - const sandboxContext = { ...GLOBAL_CONTEXT, ...context }; + const sandboxContext: GlobalContext = { ...GLOBAL_CONTEXT, ...context }; Object.keys(sandboxContext).forEach(key => { - sandbox[key] = sandboxContext[key]; + sandbox[key] = sandboxContext[key as keyof GlobalContext]; }); try { vm.runInNewContext(codeToEval, sandbox, opts); diff --git a/superset-frontend/plugins/plugin-chart-cartodiagram/src/util/controlPanelUtil.tsx b/superset-frontend/plugins/plugin-chart-cartodiagram/src/util/controlPanelUtil.tsx index 1fc79f4aa7520..96aaf7ef3c143 100644 --- a/superset-frontend/plugins/plugin-chart-cartodiagram/src/util/controlPanelUtil.tsx +++ b/superset-frontend/plugins/plugin-chart-cartodiagram/src/util/controlPanelUtil.tsx @@ -71,7 +71,7 @@ export const selectedChartMutator = ( return []; } - const data: Record = []; + const data: any[] = []; if (value && typeof value === 'string') { const parsedValue = JSON.parse(value); let itemFound = false; diff --git a/superset-frontend/plugins/plugin-chart-cartodiagram/src/util/transformPropsUtil.ts b/superset-frontend/plugins/plugin-chart-cartodiagram/src/util/transformPropsUtil.ts index 1987a75da7130..fb416005739ae 100644 --- a/superset-frontend/plugins/plugin-chart-cartodiagram/src/util/transformPropsUtil.ts +++ b/superset-frontend/plugins/plugin-chart-cartodiagram/src/util/transformPropsUtil.ts @@ -208,7 +208,7 @@ export const stripGeomColumnFromLabelMap = ( labelMap: { [key: string]: string[] }, geomColumn: string, ) => { - const newLabelMap = {}; + const newLabelMap: Record = {}; Object.entries(labelMap).forEach(([key, value]) => { if (key === geomColumn) { return; diff --git a/superset-frontend/plugins/plugin-chart-cartodiagram/test/util/layerUtil.test.ts b/superset-frontend/plugins/plugin-chart-cartodiagram/test/util/layerUtil.test.ts index f5b55bdf77911..d2912105e0207 100644 --- a/superset-frontend/plugins/plugin-chart-cartodiagram/test/util/layerUtil.test.ts +++ b/superset-frontend/plugins/plugin-chart-cartodiagram/test/util/layerUtil.test.ts @@ -75,6 +75,7 @@ describe('layerUtil', () => { // @ts-ignore expect(style!.length).toEqual(3); + // @ts-ignore upgrade `ol` package for better type of StyleLike type. const colorAtLayer = style![1].getImage().getFill().getColor(); expect(colorToExpect).toEqual(colorAtLayer); }); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts index 9ba0458024600..b434fbbc58e43 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/transformProps.ts @@ -165,7 +165,8 @@ export default function transformProps(chartProps: ChartProps) { percentDifferenceNum = (bigNumber - prevNumber) / Math.abs(prevNumber); } - const compType = compTitles[formData.timeComparison]; + const compType = + compTitles[formData.timeComparison as keyof typeof compTitles]; bigNumber = numberFormatter(bigNumber); prevNumber = numberFormatter(prevNumber); valueDifference = numberFormatter(valueDifference); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts index a12f0c975483e..4fb45ab88ac21 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberPeriodOverPeriod/utils.ts @@ -35,7 +35,7 @@ const getFontSizeMapping = ( proportionValues: number[], actualSizes: number[], ) => - proportionValues.reduce((acc, value, index) => { + proportionValues.reduce>((acc, value, index) => { acc[value] = actualSizes[index] ?? actualSizes[actualSizes.length - 1]; return acc; }, {}); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts index 31758164e7407..b466ae04f1777 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts @@ -112,12 +112,15 @@ export default { Array.isArray(colnames) && Array.isArray(coltypes) ? colnames .filter( - (colname: string, index: number) => + (_: string, index: number) => coltypes[index] === GenericDataType.Numeric, ) - .map(colname => ({ + .map((colname: string | number) => ({ value: colname, - label: verboseMap[colname] ?? colname, + label: + (Array.isArray(verboseMap) + ? verboseMap[colname as number] + : verboseMap[colname as string]) ?? colname, })) : []; return { diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/transformProps.ts index f1a62ab7de055..1888383a5232a 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/transformProps.ts @@ -38,18 +38,37 @@ import { getPadding } from '../Timeseries/transformers'; import { convertInteger } from '../utils/convertInteger'; import { NULL_STRING } from '../constants'; +const isIterable = (obj: any): obj is Iterable => + obj != null && typeof obj[Symbol.iterator] === 'function'; + function normalizeSymbolSize( nodes: ScatterSeriesOption[], maxBubbleValue: number, ) { - const [bubbleMinValue, bubbleMaxValue] = extent(nodes, x => x.data?.[0]?.[2]); - const nodeSpread = bubbleMaxValue - bubbleMinValue; - nodes.forEach(node => { - // eslint-disable-next-line no-param-reassign - node.symbolSize = - (((node.data?.[0]?.[2] - bubbleMinValue) / nodeSpread) * - (maxBubbleValue * 2) || 0) + MINIMUM_BUBBLE_SIZE; - }); + const [bubbleMinValue, bubbleMaxValue] = extent( + nodes, + x => { + const tmpValue = x.data?.[0]; + const result = isIterable(tmpValue) ? tmpValue[2] : null; + if (typeof result === 'number') { + return result; + } + return null; + }, + ); + if (bubbleMinValue !== undefined && bubbleMaxValue !== undefined) { + const nodeSpread = bubbleMaxValue - bubbleMinValue; + nodes.forEach(node => { + const tmpValue = node.data?.[0]; + const calculated = isIterable(tmpValue) ? tmpValue[2] : null; + if (typeof calculated === 'number') { + // eslint-disable-next-line no-param-reassign + node.symbolSize = + (((calculated - bubbleMinValue) / nodeSpread) * + (maxBubbleValue * 2) || 0) + MINIMUM_BUBBLE_SIZE; + } + }); + } } export function formatTooltip( diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts index 80f2b34e8fc02..7526b820d0783 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts @@ -518,7 +518,9 @@ export default function transformProps( minorTick: { show: minorTicks }, minInterval: xAxisType === AxisType.Time && timeGrainSqla - ? TIMEGRAIN_TO_TIMESTAMP[timeGrainSqla] + ? TIMEGRAIN_TO_TIMESTAMP[ + timeGrainSqla as keyof typeof TIMEGRAIN_TO_TIMESTAMP + ] : 0, ...getMinAndMaxFromBounds( xAxisType, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/formDataSuffix.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/formDataSuffix.ts index c256e6f874270..4fb9a14303834 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/formDataSuffix.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/formDataSuffix.ts @@ -29,7 +29,7 @@ export const retainFormDataSuffix = ( * > removeFormDataSuffix(fd, '_b') * { metrics: ['zee'], limit: 100, ... } * */ - const newFormData = {}; + const newFormData: Record = {}; Object.entries(formData) .sort(([a], [b]) => { @@ -63,7 +63,7 @@ export const removeFormDataSuffix = ( * > removeUnusedFormData(fd, '_b') * { metrics: ['foo', 'bar'], limit: 100, ... } * */ - const newFormData = {}; + const newFormData: Record = {}; Object.entries(formData).forEach(([key, value]) => { if (!key.endsWith(controlSuffix)) { newFormData[key] = value; diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts index bbb6271cc3c29..bc2aa2bd5833b 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformProps.test.ts @@ -469,7 +469,7 @@ describe('Does transformProps transform series correctly', () => { (totals, currentStack) => { const total = Object.keys(currentStack).reduce((stackSum, key) => { if (key === '__timestamp') return stackSum; - return stackSum + currentStack[key]; + return stackSum + currentStack[key as keyof typeof currentStack]; }, 0); totals.push(total); return totals; diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx index cda65f5527815..47d442bed5599 100644 --- a/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx @@ -415,7 +415,12 @@ const config: ControlPanelConfig = { const chartStatus = chart?.chartStatus; const metricColumn = values.map(value => { if (typeof value === 'string') { - return { value, label: verboseMap[value] ?? value }; + return { + value, + label: Array.isArray(verboseMap) + ? value + : verboseMap[value], + }; } return { value: value.label, label: value.label }; }); diff --git a/superset-frontend/plugins/plugin-chart-table/src/DataTable/components/Pagination.tsx b/superset-frontend/plugins/plugin-chart-table/src/DataTable/components/Pagination.tsx index 482300fdd421b..8704054533893 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/DataTable/components/Pagination.tsx +++ b/superset-frontend/plugins/plugin-chart-table/src/DataTable/components/Pagination.tsx @@ -60,11 +60,12 @@ export function generatePageItems( items[i] = i + left; } // replace non-ending items with placeholders - if (items[0] > 0) { + if (typeof items[0] === 'number' && items[0] > 0) { items[0] = 0; items[1] = 'prev-more'; } - if (items[items.length - 1] < total - 1) { + const lastItem = items[items.length - 1]; + if (typeof lastItem === 'number' && lastItem < total - 1) { items[items.length - 1] = total - 1; items[items.length - 2] = 'next-more'; } diff --git a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx index e67bdfcea0c89..c051bdff78759 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx @@ -649,9 +649,11 @@ const config: ControlPanelConfig = { (colname: string, index: number) => coltypes[index] === GenericDataType.Numeric, ) - .map(colname => ({ + .map((colname: string) => ({ value: colname, - label: verboseMap[colname] ?? colname, + label: Array.isArray(verboseMap) + ? colname + : verboseMap[colname], })) : []; const columnOptions = explore?.controls?.time_compare?.value diff --git a/superset-frontend/spec/helpers/Cache.ts b/superset-frontend/spec/helpers/Cache.ts index f79af5f0f96dc..fec6742a9ba08 100644 --- a/superset-frontend/spec/helpers/Cache.ts +++ b/superset-frontend/spec/helpers/Cache.ts @@ -16,10 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -export const caches = {}; +export const caches: Record> = {}; export default class Cache { - cache: object; + cache: Record; constructor(key: string) { caches[key] = caches[key] || {}; diff --git a/superset-frontend/spec/helpers/shim.tsx b/superset-frontend/spec/helpers/shim.tsx index 6f86345a0e18a..894b5233bd0c7 100644 --- a/superset-frontend/spec/helpers/shim.tsx +++ b/superset-frontend/spec/helpers/shim.tsx @@ -40,9 +40,10 @@ const exposedProperties = ['window', 'navigator', 'document']; const { defaultView } = document; if (defaultView != null) { Object.keys(defaultView).forEach(property => { - if (typeof global[property] === 'undefined') { + if (typeof global[property as keyof typeof global] === 'undefined') { exposedProperties.push(property); - global[property] = defaultView[property]; + // @ts-ignore due to string-type index signature doesn't apply for `typeof globalThis`. + global[property] = defaultView[property as keyof typeof defaultView]; } }); } diff --git a/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/KeyboardShortcutButton.test.tsx b/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/KeyboardShortcutButton.test.tsx index b3c16de1075cf..c504bffc4c6f0 100644 --- a/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/KeyboardShortcutButton.test.tsx +++ b/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/KeyboardShortcutButton.test.tsx @@ -17,7 +17,7 @@ * under the License. */ import { fireEvent, render } from 'spec/helpers/testing-library'; -import KeyboardShortcutButton, { KEY_MAP } from '.'; +import KeyboardShortcutButton, { KEY_MAP, KeyboardShortcut } from '.'; test('renders shortcut description', () => { const { getByText, getByRole } = render( @@ -26,7 +26,7 @@ test('renders shortcut description', () => { fireEvent.click(getByRole('button')); expect(getByText('Keyboard shortcuts')).toBeInTheDocument(); Object.keys(KEY_MAP) - .filter(key => Boolean(KEY_MAP[key])) + .filter(key => Boolean(KEY_MAP[key as KeyboardShortcut])) .forEach(key => { expect(getByText(key)).toBeInTheDocument(); }); diff --git a/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/index.tsx b/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/index.tsx index fbc71065bcfcf..05c4ea8ba1b12 100644 --- a/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/index.tsx +++ b/superset-frontend/src/SqlLab/components/KeyboardShortcutButton/index.tsx @@ -42,7 +42,7 @@ export enum KeyboardShortcut { CtrlRight = 'ctrl+]', } -export const KEY_MAP = { +export const KEY_MAP: Record = { [KeyboardShortcut.CtrlR]: t('Run query'), [KeyboardShortcut.CtrlEnter]: t('Run query'), [KeyboardShortcut.AltEnter]: t('Run query'), @@ -62,15 +62,14 @@ export const KEY_MAP = { [KeyboardShortcut.CtrlH]: userOS !== 'MacOS' ? t('Replace') : undefined, }; -const KeyMapByCommand = Object.entries(KEY_MAP).reduce( - (acc, [shortcut, command]) => { - if (command) { - acc[command] = [...(acc[command] || []), shortcut]; - } - return acc; - }, - {} as Record, -); +const KeyMapByCommand = Object.entries(KEY_MAP).reduce< + Record +>((acc, [shortcut, command]) => { + if (command) { + acc[command] = [...(acc[command] || []), shortcut]; + } + return acc; +}, {}); const ShortcutDescription = styled.span` font-size: ${({ theme }) => theme.typography.sizes.m}px; diff --git a/superset-frontend/src/SqlLab/components/QueryTable/index.tsx b/superset-frontend/src/SqlLab/components/QueryTable/index.tsx index 3080b84f958b0..baf0e6f35f585 100644 --- a/superset-frontend/src/SqlLab/components/QueryTable/index.tsx +++ b/superset-frontend/src/SqlLab/components/QueryTable/index.tsx @@ -103,7 +103,9 @@ const QueryTable = ({ columns.map(column => ({ accessor: column, Header: - QUERY_HISTORY_TABLE_HEADERS_LOCALIZED[column] || setHeaders(column), + QUERY_HISTORY_TABLE_HEADERS_LOCALIZED[ + column as keyof typeof QUERY_HISTORY_TABLE_HEADERS_LOCALIZED + ] || setHeaders(column), disableSortBy: true, })), [columns], @@ -221,6 +223,17 @@ const QueryTable = ({ label: t('Unknown Status'), }, }, + started: { + config: { + icon: ( + + ), + label: t('Started'), + }, + }, }; return queries diff --git a/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx b/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx index 7053f5df7be62..b7faf5d714e93 100644 --- a/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx +++ b/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx @@ -68,10 +68,10 @@ const getValidator = () => { const rules: any = getValidationRules(); return (formData: Record, errors: FormValidation) => { rules.forEach((rule: any) => { - const test = validators[rule.name]; + const test = validators[rule.name as keyof typeof validators]; const args = rule.arguments.map((name: string) => formData[name]); const container = rule.container || rule.arguments.slice(-1)[0]; - if (!test(...args)) { + if (!test(args[0], args[1])) { errors[container]?.addError(rule.message); } }); diff --git a/superset-frontend/src/SqlLab/constants.ts b/superset-frontend/src/SqlLab/constants.ts index 5a944663b8a69..263635bb3b46e 100644 --- a/superset-frontend/src/SqlLab/constants.ts +++ b/superset-frontend/src/SqlLab/constants.ts @@ -29,7 +29,7 @@ export const STATE_TYPE_MAP: Record = { success: 'success', }; -export const STATE_TYPE_MAP_LOCALIZED = { +export const STATE_TYPE_MAP_LOCALIZED: Record = { offline: t('offline'), failed: t('failed'), pending: t('pending'), diff --git a/superset-frontend/src/SqlLab/middlewares/persistSqlLabStateEnhancer.js b/superset-frontend/src/SqlLab/middlewares/persistSqlLabStateEnhancer.js index 12c9c3485313a..f3c70b72535f7 100644 --- a/superset-frontend/src/SqlLab/middlewares/persistSqlLabStateEnhancer.js +++ b/superset-frontend/src/SqlLab/middlewares/persistSqlLabStateEnhancer.js @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -// TODO: requires redux-localstorage > 1.0 for typescript support import persistState from 'redux-localstorage'; import { pickBy } from 'lodash'; import { isFeatureEnabled, FeatureFlag } from '@superset-ui/core'; @@ -129,6 +128,8 @@ const sqlLabPersistStateConfig = { }, }; +// TODO: requires redux-localstorage > 1.0 for typescript support +/** @type {any} */ export const persistSqlLabStateEnhancer = persistState( sqlLabPersistStateConfig.paths, sqlLabPersistStateConfig.config, diff --git a/superset-frontend/src/SqlLab/reducers/getInitialState.ts b/superset-frontend/src/SqlLab/reducers/getInitialState.ts index ad0cbd8033079..1f464d4ba6c41 100644 --- a/superset-frontend/src/SqlLab/reducers/getInitialState.ts +++ b/superset-frontend/src/SqlLab/reducers/getInitialState.ts @@ -147,7 +147,7 @@ export default function getInitialState({ }), }; - const destroyedQueryEditors = {}; + const destroyedQueryEditors: Record = {}; /** * If the `SQLLAB_BACKEND_PERSISTENCE` feature flag is off, or if the user diff --git a/superset-frontend/src/SqlLab/utils/emptyQueryResults.test.ts b/superset-frontend/src/SqlLab/utils/emptyQueryResults.test.ts index ca7f60af20da3..fe60edfe1e199 100644 --- a/superset-frontend/src/SqlLab/utils/emptyQueryResults.test.ts +++ b/superset-frontend/src/SqlLab/utils/emptyQueryResults.test.ts @@ -30,7 +30,7 @@ import { import { queries, defaultQueryEditor } from '../fixtures'; describe('reduxStateToLocalStorageHelper', () => { - const queriesObj = {}; + const queriesObj: Record = {}; beforeEach(() => { queries.forEach(q => { queriesObj[q.id] = q; diff --git a/superset-frontend/src/SqlLab/utils/reduxStateToLocalStorageHelper.ts b/superset-frontend/src/SqlLab/utils/reduxStateToLocalStorageHelper.ts index 3061de1f69b07..e8e1e156739cb 100644 --- a/superset-frontend/src/SqlLab/utils/reduxStateToLocalStorageHelper.ts +++ b/superset-frontend/src/SqlLab/utils/reduxStateToLocalStorageHelper.ts @@ -16,8 +16,18 @@ * specific language governing permissions and limitations * under the License. */ -import type { QueryResponse } from '@superset-ui/core'; -import type { QueryEditor, SqlLabRootState, Table } from 'src/SqlLab/types'; +import type { + InnerQueryResults, + Query, + QueryResponse, + QueryResults, +} from '@superset-ui/core'; +import type { + CursorPosition, + QueryEditor, + SqlLabRootState, + Table, +} from 'src/SqlLab/types'; import type { ThunkDispatch } from 'redux-thunk'; import { pick } from 'lodash'; import { tableApiUtil } from 'src/hooks/apiResources/tables'; @@ -71,6 +81,20 @@ export function emptyTablePersistData(tables: Table[]) { .filter(({ queryEditorId }) => Boolean(queryEditorId)); } +type InnerEmptyQueryResults = { + [key in string]: Query & + QueryResults & { + inLocalStorage?: boolean; + }; +}; + +type EmptyQueryResults = Record< + string, + InnerEmptyQueryResults & { + results: InnerQueryResults | {}; + } +>; + export function emptyQueryResults( queries: SqlLabRootState['sqlLab']['queries'], ) { @@ -86,7 +110,7 @@ export function emptyQueryResults( [key]: query, }; return updatedQueries; - }, {}); + }, {} as EmptyQueryResults); } export function clearQueryEditors(queryEditors: QueryEditor[]) { @@ -94,10 +118,15 @@ export function clearQueryEditors(queryEditors: QueryEditor[]) { // only return selected keys Object.keys(editor) .filter(key => PERSISTENT_QUERY_EDITOR_KEYS.has(key)) - .reduce( + .reduce< + Record< + string, + string | number | boolean | CursorPosition | null | undefined + > + >( (accumulator, key) => ({ ...accumulator, - [key]: editor[key], + [key]: editor[key as keyof QueryEditor], }), {}, ), diff --git a/superset-frontend/src/components/AlteredSliceTag/index.tsx b/superset-frontend/src/components/AlteredSliceTag/index.tsx index 46fc58b3ffef8..9af6ae46bd9bb 100644 --- a/superset-frontend/src/components/AlteredSliceTag/index.tsx +++ b/superset-frontend/src/components/AlteredSliceTag/index.tsx @@ -115,7 +115,7 @@ export const formatValueHandler = ( }) .join(', '); } - if (controlsMap[key]?.type === 'BoundsControl') { + if (controlsMap[key]?.type === 'BoundsControl' && Array.isArray(value)) { return `Min: ${value[0]}, Max: ${value[1]}`; } if (controlsMap[key]?.type === 'CollectionControl' && Array.isArray(value)) { diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx index 987003f16d250..d7b2c91ec2f1c 100644 --- a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx +++ b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx @@ -28,6 +28,7 @@ import { t, useTheme, ContextMenuFilters, + AdhocFilter, } from '@superset-ui/core'; import { useDispatch, useSelector } from 'react-redux'; import { Link } from 'react-router-dom'; @@ -224,7 +225,7 @@ export default function DrillByModal({ const getFormDataChangesFromConfigs = useCallback( (configs: DrillByConfigs) => - configs.reduce( + configs.reduce>( (acc, config) => { if (config?.groupbyFieldName && config.column) { acc.formData[config.groupbyFieldName] = getNewGroupby( @@ -246,7 +247,7 @@ export default function DrillByModal({ return acc; }, { - formData: {}, + formData: {} as Record>, overriddenGroupbyFields: new Set(), overriddenAdhocFilterFields: new Set(), }, @@ -256,7 +257,7 @@ export default function DrillByModal({ const getFiltersFromConfigsByFieldName = useCallback( () => - drillByConfigs.reduce((acc, config) => { + drillByConfigs.reduce>((acc, config) => { const adhocFilterFieldName = config.adhocFilterFieldName || DEFAULT_ADHOC_FILTER_FIELD_NAME; acc[adhocFilterFieldName] = [ @@ -295,7 +296,7 @@ export default function DrillByModal({ ...formData, ...overrideFormData, }; - overriddenAdhocFilterFields.forEach(adhocFilterField => ({ + overriddenAdhocFilterFields.forEach((adhocFilterField: string) => ({ ...newFormData, [adhocFilterField]: [ ...formData[adhocFilterField], diff --git a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailPane.tsx b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailPane.tsx index bcab733f48c00..82780e36860ba 100644 --- a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailPane.tsx +++ b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailPane.tsx @@ -90,7 +90,9 @@ export default function DrillDetailPane({ const [resultsPages, setResultsPages] = useState>( new Map(), ); - const [timeFormatting, setTimeFormatting] = useState({}); + const [timeFormatting, setTimeFormatting] = useState< + Record + >({}); const SAMPLES_ROW_LIMIT = useSelector( (state: { common: { conf: JsonObject } }) => @@ -140,7 +142,10 @@ export default function DrillDetailPane({ : TimeFormatting.Formatted } onChange={value => - setTimeFormatting(state => ({ ...state, [column]: value })) + setTimeFormatting(state => ({ + ...state, + [column]: parseInt(value, 10) as TimeFormatting, + })) } /> ) : ( diff --git a/superset-frontend/src/components/Checkbox/Checkbox.stories.tsx b/superset-frontend/src/components/Checkbox/Checkbox.stories.tsx index f5359c46f1747..6387aa411f374 100644 --- a/superset-frontend/src/components/Checkbox/Checkbox.stories.tsx +++ b/superset-frontend/src/components/Checkbox/Checkbox.stories.tsx @@ -34,10 +34,10 @@ export const CheckboxGallery = () =>
{}} - checked={STATUSES[status]} + checked={STATUSES[status as keyof typeof STATUSES]} style={{ marginRight: '8px' }} /> - {`I'm a${STATUSES[status] ? '' : 'n'} ${status} checkbox`} + {`I'm a${STATUSES[status as keyof typeof STATUSES] ? '' : 'n'} ${status} checkbox`}
)); diff --git a/superset-frontend/src/components/Datasource/CollectionTable.test.jsx b/superset-frontend/src/components/Datasource/CollectionTable.test.tsx similarity index 98% rename from superset-frontend/src/components/Datasource/CollectionTable.test.jsx rename to superset-frontend/src/components/Datasource/CollectionTable.test.tsx index ad4d7351ecf12..1ebe7406d9127 100644 --- a/superset-frontend/src/components/Datasource/CollectionTable.test.jsx +++ b/superset-frontend/src/components/Datasource/CollectionTable.test.tsx @@ -24,6 +24,7 @@ import CollectionTable from './CollectionTable'; const props = { collection: mockDatasource['7__table'].columns, tableColumns: ['column_name', 'type', 'groupby'], + sortColumns: [], }; test('renders a table', () => { diff --git a/superset-frontend/src/components/Datasource/CollectionTable.tsx b/superset-frontend/src/components/Datasource/CollectionTable.tsx index 79ffc1f8af762..3a6543b4080c9 100644 --- a/superset-frontend/src/components/Datasource/CollectionTable.tsx +++ b/superset-frontend/src/components/Datasource/CollectionTable.tsx @@ -36,9 +36,9 @@ import { recurseReactClone } from './utils'; interface CRUDCollectionProps { allowAddItem?: boolean; allowDeletes?: boolean; - collection: Array; - columnLabels?: object; - columnLabelTooltips?: object; + collection: Record[]; + columnLabels?: Record; + columnLabelTooltips?: Record; emptyMessage?: ReactNode; expandFieldset?: ReactNode; extraButtons?: ReactNode; @@ -58,8 +58,8 @@ interface CRUDCollectionProps { record: any, ) => ReactNode)[]; onChange?: (arg0: any) => void; - tableColumns: Array; - sortColumns: Array; + tableColumns: any[]; + sortColumns: string[]; stickyHeader?: boolean; } @@ -72,14 +72,14 @@ enum SortOrder { } interface CRUDCollectionState { - collection: object; - collectionArray: Array; - expandedColumns: object; + collection: Record; + collectionArray: Record[]; + expandedColumns: Record; sortColumn: string; sort: SortOrder; } -function createCollectionArray(collection: object) { +function createCollectionArray(collection: Record) { return Object.keys(collection).map(k => collection[k]); } @@ -89,7 +89,7 @@ function createKeyedCollection(arr: Array) { id: o.id || nanoid(), })); - const collection = {}; + const collection: Record = {}; collectionArray.forEach((o: any) => { collection[o.id] = o; }); @@ -301,7 +301,8 @@ export default class CRUDCollection extends PureComponent< // newly ordered collection const sorted = [...this.state.collectionArray].sort( - (a: object, b: object) => compareSort(a[col], b[col]), + (a: Record, b: Record) => + compareSort(a[col], b[col]), ); const newCollection = sort === SortOrder.Asc ? sorted : sorted.reverse(); diff --git a/superset-frontend/src/components/DatePicker/DatePicker.stories.tsx b/superset-frontend/src/components/DatePicker/DatePicker.stories.tsx index 563f48f07f7f6..aff0eed460653 100644 --- a/superset-frontend/src/components/DatePicker/DatePicker.stories.tsx +++ b/superset-frontend/src/components/DatePicker/DatePicker.stories.tsx @@ -74,7 +74,7 @@ const interactiveTypes = { }, }; -export const InteractiveDatePicker = (args: DatePickerProps) => ( +export const InteractiveDatePicker: any = (args: DatePickerProps) => ( ); @@ -87,7 +87,7 @@ InteractiveDatePicker.args = { InteractiveDatePicker.argTypes = interactiveTypes; -export const InteractiveRangePicker = (args: RangePickerProps) => ( +export const InteractiveRangePicker: any = (args: RangePickerProps) => ( ); diff --git a/superset-frontend/src/components/DatePicker/index.tsx b/superset-frontend/src/components/DatePicker/index.tsx index 6538ae9b6a30b..ed79c69e9da20 100644 --- a/superset-frontend/src/components/DatePicker/index.tsx +++ b/superset-frontend/src/components/DatePicker/index.tsx @@ -19,4 +19,8 @@ import { DatePicker as AntdDatePicker } from 'antd-v5'; export const DatePicker = AntdDatePicker; -export const { RangePicker } = AntdDatePicker; + +// Disable ESLint rule to allow tsc to infer proper type for RangePicker. +// eslint-disable-next-line prefer-destructuring +export const RangePicker: typeof AntdDatePicker.RangePicker = + AntdDatePicker.RangePicker; diff --git a/superset-frontend/src/components/EmptyState/index.tsx b/superset-frontend/src/components/EmptyState/index.tsx index a083b6cdcfaa8..0dc9e876c19b3 100644 --- a/superset-frontend/src/components/EmptyState/index.tsx +++ b/superset-frontend/src/components/EmptyState/index.tsx @@ -144,7 +144,10 @@ const ImageContainer = ({ size: EmptyStateSize; }) => { if (!image) return null; - const mappedImage = typeof image === 'string' ? imageMap[image] : image; + const mappedImage = + typeof image === 'string' + ? imageMap[image as keyof typeof imageMap] + : image; return (
css` const extractInvalidValues = (messages: object, payload: object): string[] => { const invalidValues: string[] = []; - const recursiveExtract = (messages: object, payload: object) => { + const recursiveExtract = ( + messages: Record, + payload: Record, + ) => { Object.keys(messages).forEach(key => { const value = payload[key]; const message = messages[key]; @@ -66,7 +69,10 @@ const extractInvalidValues = (messages: object, payload: object): string[] => { } }); }; - recursiveExtract(messages, payload); + recursiveExtract( + messages as Record, + payload as Record, + ); return invalidValues; }; diff --git a/superset-frontend/src/components/FilterableTable/index.tsx b/superset-frontend/src/components/FilterableTable/index.tsx index f77f4aede0eb7..04429ebf845f1 100644 --- a/superset-frontend/src/components/FilterableTable/index.tsx +++ b/superset-frontend/src/components/FilterableTable/index.tsx @@ -95,7 +95,7 @@ const FilterableTable = ({ }: FilterableTableProps) => { const formatTableData = (data: Record[]): Datum[] => data.map(row => { - const newRow = {}; + const newRow: Record = {}; Object.entries(row).forEach(([key, val]) => { if (['string', 'number'].indexOf(typeof val) >= 0) { newRow[key] = val; @@ -116,7 +116,7 @@ const FilterableTable = ({ const getWidthsForColumns = () => { const PADDING = 50; // accounts for cell padding and width of sorting icon - const widthsByColumnKey = {}; + const widthsByColumnKey: Record = {}; const cellContent = ([] as string[]).concat( ...orderedColumnKeys.map(key => { const cellContentList = list.map((data: Datum) => diff --git a/superset-frontend/src/components/FlashProvider/index.tsx b/superset-frontend/src/components/FlashProvider/index.tsx index f2bef9cf57883..b9e8e4a8e2171 100644 --- a/superset-frontend/src/components/FlashProvider/index.tsx +++ b/superset-frontend/src/components/FlashProvider/index.tsx @@ -41,7 +41,7 @@ export default function FlashProvider({ children, messages }: Props) { messages.forEach(message => { const [type, text] = message; const flash = flashObj[type]; - const toast = toasts[flash]; + const toast = toasts[flash as keyof typeof toasts]; if (toast) { toast(text); } diff --git a/superset-frontend/src/components/Icons/AntdEnhanced.tsx b/superset-frontend/src/components/Icons/AntdEnhanced.tsx index 4dcbc494a1414..3032a8a826919 100644 --- a/superset-frontend/src/components/Icons/AntdEnhanced.tsx +++ b/superset-frontend/src/components/Icons/AntdEnhanced.tsx @@ -127,6 +127,8 @@ const AntdEnhancedIcons = Object.keys(AntdIcons) .map(k => ({ [k]: (props: IconType) => { const whatRole = props?.onClick ? 'button' : 'img'; + // @ts-ignore TODO(hainenber): fix the type compatiblity between + // StyledIcon component prop and AntdIcon values return ; }, })) diff --git a/superset-frontend/src/components/Icons/Icons.stories.tsx b/superset-frontend/src/components/Icons/Icons.stories.tsx index 1774176767da5..280b2e768adb9 100644 --- a/superset-frontend/src/components/Icons/Icons.stories.tsx +++ b/superset-frontend/src/components/Icons/Icons.stories.tsx @@ -28,7 +28,7 @@ export default { component: Icon, }; -const palette = { Default: null }; +const palette: Record = { Default: null }; Object.entries(supersetTheme.colors).forEach(([familyName, family]) => { Object.entries(family).forEach(([colorName, colorValue]) => { palette[`${familyName} / ${colorName}`] = colorValue; diff --git a/superset-frontend/src/components/Label/index.tsx b/superset-frontend/src/components/Label/index.tsx index 13766fec86625..72f14218b9e80 100644 --- a/superset-frontend/src/components/Label/index.tsx +++ b/superset-frontend/src/components/Label/index.tsx @@ -123,10 +123,11 @@ export default function Label(props: LabelProps) { borderColor: borderColorHover, opacity: 1, }, + ...(monospace + ? { 'font-family': theme.typography.families.monospace } + : {}), }; - if (monospace) { - css['font-family'] = theme.typography.families.monospace; - } + return ( = {}; Object.keys(filterObj).forEach(id => { const filter: FilterValue = { @@ -300,7 +301,7 @@ export function useListViewState({ useEffect(() => { // From internalFilters, produce a simplified obj - const filterObj = {}; + const filterObj: Record = {}; internalFilters.forEach(filter => { if ( diff --git a/superset-frontend/src/components/Select/AsyncSelect.test.tsx b/superset-frontend/src/components/Select/AsyncSelect.test.tsx index 42db633e01e05..2e9efcbf0b22f 100644 --- a/superset-frontend/src/components/Select/AsyncSelect.test.tsx +++ b/superset-frontend/src/components/Select/AsyncSelect.test.tsx @@ -70,8 +70,10 @@ const loadOptions = async (search: string, page: number, pageSize: number) => { const optionFilterProps = ['label', 'value', 'gender']; const data = OPTIONS.filter(option => optionFilterProps.some(prop => { - const optionProp = option?.[prop] - ? String(option[prop]).trim().toLowerCase() + const optionProp = option?.[prop as keyof typeof option] + ? String(option[prop as keyof typeof option]) + .trim() + .toLowerCase() : ''; return optionProp.includes(searchValue); }), diff --git a/superset-frontend/src/components/Select/utils.tsx b/superset-frontend/src/components/Select/utils.tsx index b4d77204b53cc..454d6757ca00e 100644 --- a/superset-frontend/src/components/Select/utils.tsx +++ b/superset-frontend/src/components/Select/utils.tsx @@ -50,8 +50,8 @@ export function getValue( } export function isEqual(a: V | LabeledValue, b: V | LabeledValue, key: string) { - const actualA = isObject(a) && key in a ? a[key] : a; - const actualB = isObject(b) && key in b ? b[key] : b; + const actualA = isObject(a) && key in a ? a[key as keyof LabeledValue] : a; + const actualB = isObject(b) && key in b ? b[key as keyof LabeledValue] : b; // When comparing the values we use the equality // operator to automatically convert different types // eslint-disable-next-line eqeqeq @@ -84,10 +84,15 @@ export function hasOption( * */ export const propertyComparator = (property: string) => (a: AntdLabeledValue, b: AntdLabeledValue) => { - if (typeof a[property] === 'string' && typeof b[property] === 'string') { - return a[property].localeCompare(b[property]); + const propertyA = a[property as keyof LabeledValue]; + const propertyB = b[property as keyof LabeledValue]; + if (typeof propertyA === 'string' && typeof propertyB === 'string') { + return propertyA.localeCompare(propertyB); } - return (a[property] as number) - (b[property] as number); + if (typeof propertyA === 'number' && typeof propertyB === 'number') { + return propertyA - propertyB; + } + return String(propertyA).localeCompare(String(propertyB)); // fallback to string comparison }; export const sortSelectedFirstHelper = ( diff --git a/superset-frontend/src/components/Steps/Steps.stories.tsx b/superset-frontend/src/components/Steps/Steps.stories.tsx index a2e29d11f93e0..9d50b1b40878f 100644 --- a/superset-frontend/src/components/Steps/Steps.stories.tsx +++ b/superset-frontend/src/components/Steps/Steps.stories.tsx @@ -16,11 +16,12 @@ * specific language governing permissions and limitations * under the License. */ +import { Steps as AntdSteps } from 'antd-v5'; import { Steps, StepsProps } from '.'; export default { title: 'Steps', - component: Steps, + component: Steps as typeof AntdSteps, }; export const InteractiveSteps = (args: StepsProps) => ; diff --git a/superset-frontend/src/components/Table/Table.stories.tsx b/superset-frontend/src/components/Table/Table.stories.tsx index c415cc709ac44..ac45142e6feca 100644 --- a/superset-frontend/src/components/Table/Table.stories.tsx +++ b/superset-frontend/src/components/Table/Table.stories.tsx @@ -46,7 +46,7 @@ export default { argTypes: { onClick: { action: 'clicked' } }, } as Meta; -export interface BasicData { +interface BasicData { name: string; category: string; price: number; @@ -54,7 +54,7 @@ export interface BasicData { key: number; } -export interface RendererData { +interface RendererData { key: number; buttonCell: string; textCell: string; @@ -62,7 +62,7 @@ export interface RendererData { dollarCell: number; } -export interface ExampleData { +interface ExampleData { title: string; name: string; age: number; @@ -71,8 +71,8 @@ export interface ExampleData { key: number; } -function generateValues(amount: number, row = 0): object { - const cells = {}; +function generateValues(amount: number, row = 0): Record { + const cells: Record = {}; for (let i = 0; i < amount; i += 1) { cells[`col-${i}`] = i * row * 0.75; } @@ -94,7 +94,12 @@ function generateColumns(amount: number): ColumnsType[] { locale={LocaleCode.en_US} /> ), - sorter: (a: BasicData, b: BasicData) => numericalSort(`col-${i}`, a, b), + sorter: (a: BasicData, b: BasicData) => + numericalSort( + `col-${i}`, + a as Record, + b as Record, + ), }); } return newCols as ColumnsType[]; @@ -168,19 +173,34 @@ const basicColumns: ColumnsType = [ dataIndex: 'name', key: 'name', width: 100, - sorter: (a: BasicData, b: BasicData) => alphabeticalSort('name', a, b), + sorter: (a: BasicData, b: BasicData) => + alphabeticalSort( + 'name', + a as Record, + b as Record, + ), }, { title: 'Category', dataIndex: 'category', key: 'category', - sorter: (a: BasicData, b: BasicData) => alphabeticalSort('category', a, b), + sorter: (a: BasicData, b: BasicData) => + alphabeticalSort( + 'category', + a as Record, + b as Record, + ), }, { title: 'Price', dataIndex: 'price', key: 'price', - sorter: (a: BasicData, b: BasicData) => numericalSort('price', a, b), + sorter: (a: BasicData, b: BasicData) => + numericalSort( + 'price', + a as Record, + b as Record, + ), width: 100, }, { @@ -201,7 +221,12 @@ const bigColumns: ColumnsType = [ title: 'Age', dataIndex: 'age', key: 'age', - sorter: (a: ExampleData, b: ExampleData) => numericalSort('age', a, b), + sorter: (a: ExampleData, b: ExampleData) => + numericalSort( + 'age', + a as Record, + b as Record, + ), width: 75, }, { @@ -381,7 +406,12 @@ const paginationColumns: ColumnsType = [ locale={LocaleCode.en_US} /> ), - sorter: (a: BasicData, b: BasicData) => numericalSort('price', a, b), + sorter: (a: BasicData, b: BasicData) => + numericalSort( + 'price', + a as Record, + b as Record, + ), }, { title: 'Description', diff --git a/superset-frontend/src/components/Table/cell-renderers/ActionCell/index.tsx b/superset-frontend/src/components/Table/cell-renderers/ActionCell/index.tsx index 675bffe493bc8..1c3127504f1fc 100644 --- a/superset-frontend/src/components/Table/cell-renderers/ActionCell/index.tsx +++ b/superset-frontend/src/components/Table/cell-renderers/ActionCell/index.tsx @@ -95,7 +95,7 @@ function ActionMenu(props: ActionMenuProps) { const { menuOptions, setVisible } = props; const handleClick: MenuProps['onClick'] = ({ key }) => { setVisible?.(false); - const menuItem = menuOptions[key]; + const menuItem = menuOptions[parseInt(key, 10)]; if (menuItem) { menuItem?.onClick?.(menuItem); } diff --git a/superset-frontend/src/components/Table/index.tsx b/superset-frontend/src/components/Table/index.tsx index 11a803893b92c..fb37aa9ce9158 100644 --- a/superset-frontend/src/components/Table/index.tsx +++ b/superset-frontend/src/components/Table/index.tsx @@ -25,6 +25,7 @@ import AntTable, { import { PaginationProps } from 'antd/lib/pagination'; import { t, useTheme, logging, styled } from '@superset-ui/core'; import Loading from 'src/components/Loading'; +import { RowSelectionType } from 'antd/lib/table/interface'; import InteractiveTableUtils from './utils/InteractiveTableUtils'; import VirtualTable from './VirtualTable'; @@ -226,11 +227,12 @@ const defaultLocale = { cancelSort: t('Click to cancel sorting'), }; -const selectionMap = {}; +const selectionMap = { + [SelectionType.Multi]: 'checkbox', + [SelectionType.Single]: 'radio', + [SelectionType.Disabled]: null, +}; const noop = () => {}; -selectionMap[SelectionType.Multi] = 'checkbox'; -selectionMap[SelectionType.Single] = 'radio'; -selectionMap[SelectionType.Disabled] = null; export function Table( props: TableProps, @@ -277,7 +279,7 @@ export function Table( const selectionTypeValue = selectionMap[selectionType]; const rowSelection = { - type: selectionTypeValue, + type: selectionMap[selectionType] as RowSelectionType, selectedRowKeys, onChange: onSelectChange, }; @@ -398,7 +400,7 @@ export function Table( {!virtualize && ( )} diff --git a/superset-frontend/src/components/Table/sorters.test.ts b/superset-frontend/src/components/Table/sorters.test.ts index 91c7583947167..d4f506ac3edd2 100644 --- a/superset-frontend/src/components/Table/sorters.test.ts +++ b/superset-frontend/src/components/Table/sorters.test.ts @@ -45,14 +45,20 @@ const rows = [ * 1 or greater means the first item comes before the second item */ test('alphabeticalSort sorts correctly', () => { + // @ts-ignore expect(alphabeticalSort('name', rows[0], rows[1])).toBe(-1); + // @ts-ignore expect(alphabeticalSort('name', rows[1], rows[0])).toBe(1); + // @ts-ignore expect(alphabeticalSort('category', rows[1], rows[0])).toBe(0); }); test('numericalSort sorts correctly', () => { + // @ts-ignore expect(numericalSort('cost', rows[1], rows[2])).toBe(0); + // @ts-ignore expect(numericalSort('cost', rows[1], rows[0])).toBeLessThan(0); + // @ts-ignore expect(numericalSort('cost', rows[4], rows[1])).toBeGreaterThan(0); }); @@ -73,6 +79,7 @@ test('alphabeticalSort bad inputs no errors', () => { expect( alphabeticalSort( 'name', + // @ts-ignore { name: { title: 'the name attribute should not be an object' } }, { name: 'Doug' }, ), @@ -93,6 +100,7 @@ test('numericalSort bad inputs no errors', () => { expect( numericalSort( 'name', + // @ts-ignore { name: { title: 'the name attribute should not be an object' } }, { name: 'Doug' }, ), diff --git a/superset-frontend/src/components/Table/sorters.ts b/superset-frontend/src/components/Table/sorters.ts index 3f06071aacc69..0471c761ab012 100644 --- a/superset-frontend/src/components/Table/sorters.ts +++ b/superset-frontend/src/components/Table/sorters.ts @@ -23,8 +23,11 @@ * @param b Second row object to compare * @returns number */ -export const alphabeticalSort = (key: string, a: object, b: object): number => - a?.[key]?.localeCompare?.(b?.[key]); +export const alphabeticalSort = ( + key: string, + a: Record, + b: Record, +): number => a?.[key]?.localeCompare?.(b?.[key]); /** * @param key The name of the row's attribute used to compare values for numerical sorting @@ -32,5 +35,8 @@ export const alphabeticalSort = (key: string, a: object, b: object): number => * @param b Second row object to compare * @returns number */ -export const numericalSort = (key: string, a: object, b: object): number => - a?.[key] - b?.[key]; +export const numericalSort = ( + key: string, + a: Record, + b: Record, +): number => a?.[key] - b?.[key]; diff --git a/superset-frontend/src/components/TimezoneSelector/index.tsx b/superset-frontend/src/components/TimezoneSelector/index.tsx index 329ad3461882b..4a3c69ca08c93 100644 --- a/superset-frontend/src/components/TimezoneSelector/index.tsx +++ b/superset-frontend/src/components/TimezoneSelector/index.tsx @@ -70,8 +70,8 @@ export default function TimezoneSelector({ const offsets = getOffsetKey(name); return ( (isDST(currentDate.tz(name), name) - ? offsetsToName[offsets]?.[1] - : offsetsToName[offsets]?.[0]) || name + ? offsetsToName[offsets as keyof typeof offsetsToName]?.[1] + : offsetsToName[offsets as keyof typeof offsetsToName]?.[0]) || name ); }; diff --git a/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx b/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx deleted file mode 100644 index c4204a3c1c971..0000000000000 --- a/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -/* eslint-env browser */ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; -import { getCategoricalSchemeRegistry, t } from '@superset-ui/core'; - -import ColorSchemeControl from 'src/explore/components/controls/ColorSchemeControl'; - -const propTypes = { - onChange: PropTypes.func, - labelMargin: PropTypes.number, - colorScheme: PropTypes.string, - hasCustomLabelsColor: PropTypes.bool, -}; - -const defaultProps = { - hasCustomLabelsColor: false, - colorScheme: undefined, - onChange: () => {}, -}; - -class ColorSchemeControlWrapper extends PureComponent { - constructor(props) { - super(props); - this.state = { hovered: false }; - this.categoricalSchemeRegistry = getCategoricalSchemeRegistry(); - this.choices = this.categoricalSchemeRegistry.keys().map(s => [s, s]); - this.schemes = this.categoricalSchemeRegistry.getMap(); - } - - setHover(hovered) { - this.setState({ hovered }); - } - - render() { - const { colorScheme, labelMargin = 0, hasCustomLabelsColor } = this.props; - return ( - - ); - } -} - -ColorSchemeControlWrapper.propTypes = propTypes; -ColorSchemeControlWrapper.defaultProps = defaultProps; - -export default ColorSchemeControlWrapper; diff --git a/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.tsx b/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.tsx new file mode 100644 index 0000000000000..42e3b0bca5121 --- /dev/null +++ b/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.tsx @@ -0,0 +1,64 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* eslint-env browser */ +import { getCategoricalSchemeRegistry, t } from '@superset-ui/core'; +import { useEffect, useState } from 'react'; +import ColorSchemeControl from 'src/explore/components/controls/ColorSchemeControl'; + +interface ColorSchemeControlWrapperProps { + colorScheme?: string; + hasCustomLabelsColor: boolean; + hovered?: boolean; + onChange: () => void; +} + +const ColorSchemeControlWrapper = ({ + colorScheme, + hasCustomLabelsColor = false, + hovered = false, + onChange = () => {}, +}: ColorSchemeControlWrapperProps) => { + const [choices, setChoices] = useState([]); + const [schemes, setSchemes] = useState({}); + + useEffect(() => { + // Registry initialization + const categoricalSchemeRegistry = getCategoricalSchemeRegistry(); + setChoices(categoricalSchemeRegistry.keys().map(s => [s, s])); + setSchemes(categoricalSchemeRegistry.getMap()); + }, []); // Empty dependency array ensures this runs only once + + return ( + + ); +}; + +export default ColorSchemeControlWrapper; diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx index 929c5af8fdc2d..a336c76392c89 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx @@ -122,7 +122,7 @@ const DashboardContainer: FC = ({ topLevelTabs }) => { const [dashboardLabelsColorInitiated, setDashboardLabelsColorInitiated] = useState(false); const prevRenderedChartIds = useRef([]); - const prevTabIndexRef = useRef(); + const prevTabIndexRef = useRef(); const tabIndex = useMemo(() => { const nextTabIndex = findTabIndexByComponentId({ currentComponent: getRootLevelTabsComponent(dashboardLayout), diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx index 57413ffd23a80..1db359dfbf852 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx +++ b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx @@ -175,10 +175,10 @@ export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => { setDashboardIndicators(indicatorsInitialState); } else if (prevChartStatus !== 'success') { if ( - chart?.queriesResponse?.[0]?.rejected_filters !== - prevChart?.queriesResponse?.[0]?.rejected_filters || - chart?.queriesResponse?.[0]?.applied_filters !== - prevChart?.queriesResponse?.[0]?.applied_filters || + chart?.queriesResponse?.rejected_filters !== + prevChart?.queriesResponse?.rejected_filters || + chart?.queriesResponse?.applied_filters !== + prevChart?.queriesResponse?.applied_filters || dashboardFilters !== prevDashboardFilters || datasources !== prevDatasources ) { @@ -215,10 +215,10 @@ export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => { setNativeIndicators(indicatorsInitialState); } else if (prevChartStatus !== 'success') { if ( - chart?.queriesResponse?.[0]?.rejected_filters !== - prevChart?.queriesResponse?.[0]?.rejected_filters || - chart?.queriesResponse?.[0]?.applied_filters !== - prevChart?.queriesResponse?.[0]?.applied_filters || + chart?.queriesResponse?.rejected_filters !== + prevChart?.queriesResponse?.rejected_filters || + chart?.queriesResponse?.applied_filters !== + prevChart?.queriesResponse?.applied_filters || nativeFilters !== prevNativeFilters || chartLayoutItems !== prevChartLayoutItems || dataMask !== prevDataMask || diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx index 08a1a8bce62b3..4831ece8a5b64 100644 --- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx +++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx @@ -463,7 +463,6 @@ const PropertiesModal = ({ hasCustomLabelsColor={hasCustomLabelsColor} onChange={onColorSchemeChange} colorScheme={colorScheme} - labelMargin={4} /> @@ -532,7 +531,6 @@ const PropertiesModal = ({ hasCustomLabelsColor={hasCustomLabelsColor} onChange={onColorSchemeChange} colorScheme={colorScheme} - labelMargin={4} /> diff --git a/superset-frontend/src/dashboard/components/SyncDashboardState/index.tsx b/superset-frontend/src/dashboard/components/SyncDashboardState/index.tsx index 59199ec278a9d..27f649fb1fdae 100644 --- a/superset-frontend/src/dashboard/components/SyncDashboardState/index.tsx +++ b/superset-frontend/src/dashboard/components/SyncDashboardState/index.tsx @@ -30,6 +30,7 @@ import { import { RootState } from 'src/dashboard/types'; import { getActiveFilters } from 'src/dashboard/util/activeDashboardFilters'; import { enforceSharedLabelsColorsArray } from 'src/utils/colorScheme'; +import { Divider, Filter } from '@superset-ui/core'; type Props = { dashboardPageId: string }; @@ -66,7 +67,9 @@ const selectDashboardContextForExplore = createSelector( (state: RootState) => state.dataMask, ], (metadata, dashboardId, colorScheme, filters, dataMask) => { - const nativeFilters = Object.keys(filters).reduce((acc, key) => { + const nativeFilters = Object.keys(filters).reduce< + Record> + >((acc, key) => { acc[key] = pick(filters[key], ['chartsInScope']); return acc; }, {}); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.tsx index e4530eacec639..40e9d6d395fb2 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.tsx @@ -26,6 +26,7 @@ import { isCrossFilterScopeGlobal, GlobalChartCrossFilterConfig, GLOBAL_SCOPE_POINTER, + ChartCrossFiltersConfig, } from 'src/dashboard/types'; import { getChartIdsInFilterScope } from 'src/dashboard/util/getChartIdsInFilterScope'; import { useChartIds } from 'src/dashboard/util/charts/useChartIds'; @@ -39,7 +40,9 @@ const getUpdatedGloballyScopedChartsInScope = ( configs: ChartConfiguration, globalChartsInScope: number[], ) => - Object.entries(configs).reduce((acc, [id, config]) => { + Object.entries(configs).reduce< + Record + >((acc, [id, config]) => { if (isCrossFilterScopeGlobal(config.crossFilters.scope)) { acc[id] = { id: Number(config.id), diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx index 0b64d37a25169..f92fd8b60c024 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx @@ -191,7 +191,7 @@ const FilterBar: FC = ({ useEffect(() => { if (previousFilters && dashboardId === previousDashboardId) { - const updates = {}; + const updates: Record = {}; Object.values(filters).forEach(currentFilter => { const previousFilter = previousFilters?.[currentFilter.id]; if (!previousFilter) { @@ -208,7 +208,9 @@ const FilterBar: FC = ({ const dataMaskChanged = !isEqual(currentDataMask, previousDataMask); if (typeChanged || targetsChanged || dataMaskChanged) { - updates[currentFilter.id] = getInitialDataMask(currentFilter.id); + updates[currentFilter.id] = getInitialDataMask( + currentFilter.id, + ) as DataMaskWithId; } }); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/utils.ts b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/utils.ts index ff3507f5756ec..09f515b63ee9f 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/utils.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/utils.ts @@ -80,7 +80,9 @@ export const hasTemporalColumns = ( export const doesColumnMatchFilterType = (filterType: string, column: Column) => !column.type_generic || !(filterType in FILTER_SUPPORTED_TYPES) || - FILTER_SUPPORTED_TYPES[filterType]?.includes(column.type_generic); + FILTER_SUPPORTED_TYPES[ + filterType as keyof typeof FILTER_SUPPORTED_TYPES + ]?.includes(column.type_generic); export const mostUsedDataset = ( datasets: DatasourcesState, diff --git a/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts b/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts index 0539a4317a77e..40b362e86c18f 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts @@ -91,7 +91,7 @@ const selectIndicatorValue = ( (columnKey === TIME_FILTER_MAP.time_grain_sqla ? datasource.time_grain_sqla : datasource.granularity) || [] - ).reduce( + ).reduce>( (map, [key, value]) => ({ ...map, [key]: value, @@ -189,8 +189,16 @@ export const getCrossFilterIndicator = ( return filterObject; }; -const cachedIndicatorsForChart = {}; -const cachedDashboardFilterDataForChart = {}; +const cachedIndicatorsForChart: Record = {}; +const cachedDashboardFilterDataForChart: Record< + string, + { + appliedColumns: Set; + rejectedColumns: Set; + matchingFilters: Filter[]; + matchingDatasources: Datasource[]; + } +> = {}; // inspects redux state to find what the filter indicators should be shown for a given chart export const selectIndicatorsForChart = ( chartId: number, @@ -214,10 +222,10 @@ export const selectIndicatorsForChart = ( const cachedFilterData = cachedDashboardFilterDataForChart[chartId]; if ( cachedIndicatorsForChart[chartId] && - areObjectsEqual(cachedFilterData?.appliedColumns, appliedColumns) && - areObjectsEqual(cachedFilterData?.rejectedColumns, rejectedColumns) && - areObjectsEqual(cachedFilterData?.matchingFilters, matchingFilters) && - areObjectsEqual(cachedFilterData?.matchingDatasources, matchingDatasources) + areObjectsEqual(cachedFilterData.appliedColumns, appliedColumns) && + areObjectsEqual(cachedFilterData.rejectedColumns, rejectedColumns) && + areObjectsEqual(cachedFilterData.matchingFilters, matchingFilters) && + areObjectsEqual(cachedFilterData.matchingDatasources, matchingDatasources) ) { return cachedIndicatorsForChart[chartId]; } @@ -286,7 +294,7 @@ export const selectChartCrossFilters = ( rejectedColumns: Set, filterEmitter = false, ): Indicator[] | CrossFilterIndicator[] => { - let crossFilterIndicators: any = []; + let crossFilterIndicators: Indicator[] | CrossFilterIndicator[] = []; crossFilterIndicators = Object.values(chartConfiguration) .filter(chartConfig => { const inScope = @@ -322,8 +330,18 @@ export const selectChartCrossFilters = ( return crossFilterIndicators; }; -const cachedNativeIndicatorsForChart = {}; -const cachedNativeFilterDataForChart: any = {}; +const cachedNativeIndicatorsForChart: Record = {}; +const cachedNativeFilterDataForChart: Record< + number, + { + nativeFilters: Filters; + chartLayoutItems: LayoutItem[]; + chartConfiguration: ChartConfiguration; + dataMask: DataMaskStateWithId; + appliedColumns: Set; + rejectedColumns: Set; + } +> = {}; export const selectNativeIndicatorsForChart = ( nativeFilters: Filters, dataMask: DataMaskStateWithId, @@ -374,7 +392,7 @@ export const selectNativeIndicatorsForChart = ( }; }); - let crossFilterIndicators: any = []; + let crossFilterIndicators: (Indicator | CrossFilterIndicator)[] = []; crossFilterIndicators = selectChartCrossFilters( dataMask, chartId, @@ -383,7 +401,9 @@ export const selectNativeIndicatorsForChart = ( appliedColumns, rejectedColumns, ); - const indicators = crossFilterIndicators.concat(nativeFilterIndicators); + const indicators = crossFilterIndicators.concat( + nativeFilterIndicators as Indicator[], + ); cachedNativeIndicatorsForChart[chartId] = indicators; cachedNativeFilterDataForChart[chartId] = { nativeFilters, diff --git a/superset-frontend/src/dashboard/components/nativeFilters/state.ts b/superset-frontend/src/dashboard/components/nativeFilters/state.ts index a17ebc15d85f3..498fcf9a77c59 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/state.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/state.ts @@ -45,12 +45,12 @@ export function useFilterConfigMap() { const filterConfig = useFilterConfiguration(); return useMemo( () => - filterConfig.reduce( + filterConfig.reduce>( (acc: Record, filter: Filter) => { acc[filter.id] = filter; return acc; }, - {} as Record, + {}, ), [filterConfig], ); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/utils.ts b/superset-frontend/src/dashboard/components/nativeFilters/utils.ts index ef577b354b40a..c883ed040392e 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/utils.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/utils.ts @@ -27,9 +27,13 @@ import { getChartMetadataRegistry, QueryFormData, t, + ExtraFormDataOverride, + TimeGranularity, + ExtraFormDataAppend, } from '@superset-ui/core'; import { LayoutItem } from 'src/dashboard/types'; import extractUrlParams from 'src/dashboard/util/extractUrlParams'; +import { isIterable, OnlyKeyWithType } from 'src/utils/types'; import { TAB_TYPE } from '../../util/componentTypes'; import getBootstrapData from '../../../utils/getBootstrapData'; @@ -103,22 +107,26 @@ export function mergeExtraFormData( ): ExtraFormData { const mergedExtra: ExtraFormData = {}; EXTRA_FORM_DATA_APPEND_KEYS.forEach((key: string) => { + const originalExtraData = originalExtra[key as keyof ExtraFormDataAppend]; + const newExtraData = newExtra[key as keyof ExtraFormDataAppend]; const mergedValues = [ - ...(originalExtra[key] || []), - ...(newExtra[key] || []), + ...(isIterable(originalExtraData) ? originalExtraData : []), + ...(isIterable(newExtraData) ? newExtraData : []), ]; if (mergedValues.length) { - mergedExtra[key] = mergedValues; + mergedExtra[key as OnlyKeyWithType] = mergedValues; } }); EXTRA_FORM_DATA_OVERRIDE_KEYS.forEach((key: string) => { - const originalValue = originalExtra[key]; + const originalValue = originalExtra[key as keyof ExtraFormDataOverride]; if (originalValue !== undefined) { - mergedExtra[key] = originalValue; + mergedExtra[key as OnlyKeyWithType] = + originalValue as TimeGranularity; } - const newValue = newExtra[key]; + const newValue = newExtra[key as keyof ExtraFormDataOverride]; if (newValue !== undefined) { - mergedExtra[key] = newValue; + mergedExtra[key as OnlyKeyWithType] = + newValue as TimeGranularity; } }); return mergedExtra; diff --git a/superset-frontend/src/dashboard/reducers/nativeFilters.ts b/superset-frontend/src/dashboard/reducers/nativeFilters.ts index e551924013b2c..80e8416347262 100644 --- a/superset-frontend/src/dashboard/reducers/nativeFilters.ts +++ b/superset-frontend/src/dashboard/reducers/nativeFilters.ts @@ -27,6 +27,7 @@ import { UPDATE_CASCADE_PARENT_IDS, } from 'src/dashboard/actions/nativeFilters'; import { + Divider, Filter, FilterConfiguration, NativeFiltersState, @@ -41,7 +42,7 @@ export function getInitialState({ state?: NativeFiltersState; }): NativeFiltersState { const state: Partial = {}; - const filters = {}; + const filters: Record = {}; if (filterConfig) { filterConfig.forEach(filter => { const { id } = filter; diff --git a/superset-frontend/src/dashboard/types.ts b/superset-frontend/src/dashboard/types.ts index a643463ddc240..66fcf6418f827 100644 --- a/superset-frontend/src/dashboard/types.ts +++ b/superset-frontend/src/dashboard/types.ts @@ -25,6 +25,7 @@ import { JsonObject, NativeFilterScope, NativeFiltersState, + NativeFilterTarget, } from '@superset-ui/core'; import { Dataset } from '@superset-ui/chart-controls'; import { chart } from 'src/components/Chart/chartReducer'; @@ -183,26 +184,30 @@ export type Charts = { [key: number]: Chart }; type ComponentTypesKeys = keyof typeof componentTypes; export type ComponentType = (typeof componentTypes)[ComponentTypesKeys]; +export type LayoutItemMeta = { + chartId: number; + defaultText?: string; + height: number; + placeholder?: string; + sliceName?: string; + sliceNameOverride?: string; + text?: string; + uuid: string; + width: number; +}; + /** State of dashboardLayout item in redux */ export type LayoutItem = { children: string[]; parents?: string[]; type: ComponentType; id: string; - meta: { - chartId: number; - defaultText?: string; - height: number; - placeholder?: string; - sliceName?: string; - sliceNameOverride?: string; - text?: string; - uuid: string; - width: number; - }; + meta: LayoutItemMeta; }; type ActiveFilter = { + filterType?: string; + targets: number[] | [Partial]; scope: number[]; values: ExtraFormData; }; diff --git a/superset-frontend/src/dashboard/util/activeAllDashboardFilters.ts b/superset-frontend/src/dashboard/util/activeAllDashboardFilters.ts index 3bde1d2e6f856..0d3ae30eb4a04 100644 --- a/superset-frontend/src/dashboard/util/activeAllDashboardFilters.ts +++ b/superset-frontend/src/dashboard/util/activeAllDashboardFilters.ts @@ -20,6 +20,7 @@ import { DataMaskStateWithId, PartialFilters, JsonObject, + DataMaskWithId, } from '@superset-ui/core'; import { ActiveFilters, ChartConfiguration } from '../types'; @@ -28,9 +29,12 @@ export const getRelevantDataMask = ( prop: string, ): JsonObject | DataMaskStateWithId => Object.values(dataMask) - .filter(item => item[prop]) + .filter(item => item[prop as keyof DataMaskWithId]) .reduce( - (prev, next) => ({ ...prev, [next.id]: prop ? next[prop] : next }), + (prev, next) => ({ + ...prev, + [next.id]: prop ? next[prop as keyof DataMaskWithId] : next, + }), {}, ); @@ -45,13 +49,14 @@ export const getAllActiveFilters = ({ nativeFilters: PartialFilters; allSliceIds: number[]; }): ActiveFilters => { - const activeFilters = {}; + const activeFilters: ActiveFilters = {}; // Combine native filters with cross filters, because they have similar logic - Object.values(dataMask).forEach(({ id: filterId, extraFormData }) => { + Object.values(dataMask).forEach(({ id: filterId, extraFormData = {} }) => { const scope = nativeFilters?.[filterId]?.chartsInScope ?? - chartConfiguration?.[filterId]?.crossFilters?.chartsInScope ?? + chartConfiguration?.[parseInt(filterId, 10)]?.crossFilters + ?.chartsInScope ?? allSliceIds ?? []; const filterType = nativeFilters?.[filterId]?.filterType; diff --git a/superset-frontend/src/dashboard/util/charts/getFormDataWithExtraFilters.ts b/superset-frontend/src/dashboard/util/charts/getFormDataWithExtraFilters.ts index 2190318547efb..56cc787b1dca2 100644 --- a/superset-frontend/src/dashboard/util/charts/getFormDataWithExtraFilters.ts +++ b/superset-frontend/src/dashboard/util/charts/getFormDataWithExtraFilters.ts @@ -17,8 +17,10 @@ * under the License. */ import { + DataMask, DataMaskStateWithId, DataRecordFilters, + DataRecordValue, JsonObject, PartialFilters, } from '@superset-ui/core'; @@ -29,10 +31,36 @@ import { isEqual } from 'lodash'; import getEffectiveExtraFilters from './getEffectiveExtraFilters'; import { getAllActiveFilters } from '../activeAllDashboardFilters'; +interface CachedFormData { + extra_form_data?: JsonObject; + extra_filters: { + col: string; + op: string; + val: DataRecordValue[]; + }[]; + own_color_scheme?: string; + color_scheme?: string; + color_namespace?: string; + chart_id: number; + label_colors?: Record; + shared_label_colors?: string[]; + map_label_colors?: Record; +} + +export type CachedFormDataWithExtraControls = CachedFormData & { + [key: string]: any; +}; + // We cache formData objects so that our connected container components don't always trigger // render cascades. we cannot leverage the reselect library because our cache size is >1 -const cachedFiltersByChart = {}; -const cachedFormdataByChart = {}; +const cachedFiltersByChart: Record = {}; +const cachedFormdataByChart: Record< + number, + CachedFormData & { + dataMask: DataMask; + extraControls: Record; + } +> = {}; export interface GetFormDataWithExtraFiltersArguments { chartConfiguration: ChartConfiguration; @@ -113,7 +141,7 @@ export default function getFormDataWithExtraFilters({ }; } - const formData = { + const formData: CachedFormDataWithExtraControls = { ...chart.form_data, chart_id: chart.id, label_colors: labelsColor, diff --git a/superset-frontend/src/dashboard/util/crossFilters.ts b/superset-frontend/src/dashboard/util/crossFilters.ts index 04d3e368fc0c9..559755349d73a 100644 --- a/superset-frontend/src/dashboard/util/crossFilters.ts +++ b/superset-frontend/src/dashboard/util/crossFilters.ts @@ -21,9 +21,11 @@ import { Behavior, getChartMetadataRegistry, isDefined, + NativeFilterScope, } from '@superset-ui/core'; import { getChartIdsInFilterScope } from './getChartIdsInFilterScope'; import { + ChartConfiguration, ChartsState, DashboardInfo, DashboardLayout, @@ -66,7 +68,7 @@ export const getCrossFiltersConfiguration = ( // If user just added cross filter to dashboard it's not saving its scope on server, // so we tweak it until user will update scope and will save it in server - const chartConfiguration = {}; + const chartConfiguration: ChartConfiguration = {}; chartLayoutItems.forEach(layoutItem => { const chartId = layoutItem.meta?.chartId; @@ -92,6 +94,7 @@ export const getCrossFiltersConfiguration = ( id: chartId, crossFilters: { scope: GLOBAL_SCOPE_POINTER, + chartsInScope: [], }, }; } @@ -101,7 +104,8 @@ export const getCrossFiltersConfiguration = ( id => id !== Number(chartId), ) : getChartIdsInFilterScope( - chartConfiguration[chartId].crossFilters.scope, + chartConfiguration[chartId].crossFilters + .scope as NativeFilterScope, Object.values(charts).map(chart => chart.id), chartLayoutItems, ); diff --git a/superset-frontend/src/dashboard/util/findParentId.ts b/superset-frontend/src/dashboard/util/findParentId.ts index d8dbb4dbf2514..277530d11f5de 100644 --- a/superset-frontend/src/dashboard/util/findParentId.ts +++ b/superset-frontend/src/dashboard/util/findParentId.ts @@ -48,15 +48,17 @@ function findParentId(structure: IStructure): string | null { return parentId; } -const cache = {}; +const cache: Record = {}; + export default function findParentIdWithCache( structure: IStructure, ): string | null { let parentId = null; if (structure) { const { childId, layout = {} } = structure; - if (cache[childId]) { - const lastParent = layout?.[cache[childId]] || {}; + const cachedValue = cache[childId]; + if (cachedValue) { + const lastParent = layout?.[cachedValue] || {}; if (lastParent?.children && lastParent?.children?.includes?.(childId)) { return lastParent.id; } diff --git a/superset-frontend/src/dashboard/util/findTabIndexByComponentId.test.js b/superset-frontend/src/dashboard/util/findTabIndexByComponentId.test.ts similarity index 91% rename from superset-frontend/src/dashboard/util/findTabIndexByComponentId.test.js rename to superset-frontend/src/dashboard/util/findTabIndexByComponentId.test.ts index c41ea486335f6..92abdb9ff53ad 100644 --- a/superset-frontend/src/dashboard/util/findTabIndexByComponentId.test.js +++ b/superset-frontend/src/dashboard/util/findTabIndexByComponentId.test.ts @@ -17,16 +17,17 @@ * under the License. */ import findTabIndexByComponentId from 'src/dashboard/util/findTabIndexByComponentId'; +import { LayoutItem, LayoutItemMeta } from '../types'; describe('findTabIndexByComponentId', () => { - const topLevelTabsComponent = { + const topLevelTabsComponent: LayoutItem = { children: ['TAB-0g-5l347I2', 'TAB-qrwN_9VB5'], id: 'TABS-MNQQSW-kyd', - meta: {}, + meta: {} as LayoutItemMeta, parents: ['ROOT_ID'], type: 'TABS', }; - const rowLevelTabsComponent = { + const rowLevelTabsComponent: LayoutItem = { children: [ 'TAB-TwyUUGp2Bg', 'TAB-Zl1BQAUvN', @@ -34,7 +35,7 @@ describe('findTabIndexByComponentId', () => { 'TAB---e53RNei', ], id: 'TABS-Oduxop1L7I', - meta: {}, + meta: {} as LayoutItemMeta, parents: ['ROOT_ID', 'TABS-MNQQSW-kyd', 'TAB-qrwN_9VB5'], type: 'TABS', }; diff --git a/superset-frontend/src/dashboard/util/findTabIndexByComponentId.js b/superset-frontend/src/dashboard/util/findTabIndexByComponentId.ts similarity index 86% rename from superset-frontend/src/dashboard/util/findTabIndexByComponentId.js rename to superset-frontend/src/dashboard/util/findTabIndexByComponentId.ts index ac644e43fcd7e..4fe321b7a7253 100644 --- a/superset-frontend/src/dashboard/util/findTabIndexByComponentId.js +++ b/superset-frontend/src/dashboard/util/findTabIndexByComponentId.ts @@ -16,10 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -export default function findTabIndexByComponentId({ +import { LayoutItem } from '../types'; + +const findTabIndexByComponentId = ({ currentComponent, directPathToChild = [], -}) { +}: { + currentComponent: LayoutItem; + directPathToChild: string[]; +}): number => { if ( !currentComponent || directPathToChild.length === 0 || @@ -38,4 +43,6 @@ export default function findTabIndexByComponentId({ ); } return -1; -} +}; + +export default findTabIndexByComponentId; diff --git a/superset-frontend/src/dashboard/util/getFormDataWithExtraFilters.test.ts b/superset-frontend/src/dashboard/util/getFormDataWithExtraFilters.test.ts index 34cbe4b1bd97a..0f6a5504885ae 100644 --- a/superset-frontend/src/dashboard/util/getFormDataWithExtraFilters.test.ts +++ b/superset-frontend/src/dashboard/util/getFormDataWithExtraFilters.test.ts @@ -17,6 +17,7 @@ * under the License. */ import getFormDataWithExtraFilters, { + CachedFormDataWithExtraControls, GetFormDataWithExtraFiltersArguments, } from 'src/dashboard/util/charts/getFormDataWithExtraFilters'; import { sliceId as chartId } from 'spec/fixtures/mockChartQueries'; @@ -87,7 +88,8 @@ describe('getFormDataWithExtraFilters', () => { }); it('should compose extra control', () => { - const result = getFormDataWithExtraFilters(mockArgs); + const result: CachedFormDataWithExtraControls = + getFormDataWithExtraFilters(mockArgs); expect(result.stack).toEqual('Stacked'); }); }); diff --git a/superset-frontend/src/dashboard/util/isValidChild.test.ts b/superset-frontend/src/dashboard/util/isValidChild.test.ts index 6ff56b85820fc..413cbfc9564cb 100644 --- a/superset-frontend/src/dashboard/util/isValidChild.test.ts +++ b/superset-frontend/src/dashboard/util/isValidChild.test.ts @@ -43,7 +43,7 @@ describe('isValidChild', () => { // every unique parent > child relationship is tested, but because this // test representation WILL result in duplicates, we hash each test // to keep track of which we've run - const didTest = {}; + const didTest: Record = {}; const validExamples = [ [ROOT, GRID, CHART], // chart is valid because it is wrapped in a row [ROOT, GRID, MARKDOWN], // markdown is valid because it is wrapped in a row diff --git a/superset-frontend/src/dashboard/util/isValidChild.ts b/superset-frontend/src/dashboard/util/isValidChild.ts index 9991604320564..457e23217d781 100644 --- a/superset-frontend/src/dashboard/util/isValidChild.ts +++ b/superset-frontend/src/dashboard/util/isValidChild.ts @@ -55,7 +55,7 @@ const depthFour = rootDepth + 4; const depthFive = rootDepth + 5; // when moving components around the depth of child is irrelevant, note these are parent depths -const parentMaxDepthLookup = { +const parentMaxDepthLookup: Record> = { [DASHBOARD_ROOT_TYPE]: { [TABS_TYPE]: rootDepth, [DASHBOARD_GRID_TYPE]: rootDepth, diff --git a/superset-frontend/src/dataMask/reducer.ts b/superset-frontend/src/dataMask/reducer.ts index 6d240b498953e..c13b23fa433d0 100644 --- a/superset-frontend/src/dataMask/reducer.ts +++ b/superset-frontend/src/dataMask/reducer.ts @@ -41,25 +41,15 @@ import { areObjectsEqual } from '../reduxUtils'; export function getInitialDataMask( id?: string | number, - moreProps?: DataMask, -): DataMask; -export function getInitialDataMask( - id: string | number, moreProps: DataMask = {}, -): DataMaskWithId { - let otherProps = {}; - if (id) { - otherProps = { - id, - }; - } +): DataMask | DataMaskWithId { return { - ...otherProps, + ...(id !== undefined ? { id } : {}), extraFormData: {}, filterState: {}, ownState: {}, ...moreProps, - } as DataMaskWithId; + } as DataMask | DataMaskWithId; } function fillNativeFilters( @@ -132,7 +122,7 @@ function updateDataMaskForFilterChanges( const dataMaskReducer = produce( (draft: DataMaskStateWithId, action: AnyDataMaskAction) => { - const cleanState = {}; + const cleanState: DataMaskStateWithId = {}; switch (action.type) { case CLEAR_DATA_MASK_STATE: return cleanState; @@ -151,7 +141,7 @@ const dataMaskReducer = produce( action.data.dashboardInfo?.metadata?.chart_configuration, ).forEach(id => { cleanState[id] = { - ...getInitialDataMask(id), // take initial data + ...(getInitialDataMask(id) as DataMaskWithId), // take initial data }; }); fillNativeFilters( diff --git a/superset-frontend/src/explore/components/Control.tsx b/superset-frontend/src/explore/components/Control.tsx index 40b992796a3f0..80dc4762cec2b 100644 --- a/superset-frontend/src/explore/components/Control.tsx +++ b/superset-frontend/src/explore/components/Control.tsx @@ -102,7 +102,10 @@ export default function Control(props: ControlProps) { if (!type || isVisible === false) return null; - const ControlComponent = typeof type === 'string' ? controlMap[type] : type; + const ControlComponent = + typeof type === 'string' + ? controlMap[type as keyof typeof controlMap] + : type; if (!ControlComponent) { // eslint-disable-next-line no-console console.warn(`Unknown controlType: ${type}`); diff --git a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx index 27a8f2dd6558e..77e5580fd7d67 100644 --- a/superset-frontend/src/explore/components/ControlPanelsContainer.tsx +++ b/superset-frontend/src/explore/components/ControlPanelsContainer.tsx @@ -48,6 +48,7 @@ import { CustomControlItem, Dataset, ExpandedControlItem, + isCustomControlItem, isTemporalColumn, sections, } from '@superset-ui/chart-controls'; @@ -678,8 +679,7 @@ export const ControlPanelsContainer = (props: ControlPanelsContainerProps) => { return controlItem; } if ( - controlItem.name && - controlItem.config && + isCustomControlItem(controlItem) && controlItem.name !== 'datasource' ) { return renderControl(controlItem); diff --git a/superset-frontend/src/explore/components/DataTableControl/index.tsx b/superset-frontend/src/explore/components/DataTableControl/index.tsx index f351ab717c78a..012b89e8da9b6 100644 --- a/superset-frontend/src/explore/components/DataTableControl/index.tsx +++ b/superset-frontend/src/explore/components/DataTableControl/index.tsx @@ -320,7 +320,7 @@ export const useTableColumns = ( return { // react-table requires a non-empty id, therefore we introduce a fallback value in case the key is empty id: key || index, - accessor: row => row[key], + accessor: (row: Record) => row[key], Header: colType === GenericDataType.Temporal && typeof firstValue !== 'string' ? ( diff --git a/superset-frontend/src/explore/components/DatasourcePanel/index.tsx b/superset-frontend/src/explore/components/DatasourcePanel/index.tsx index 2d2c0783df4b2..30c53ffd2d124 100644 --- a/superset-frontend/src/explore/components/DatasourcePanel/index.tsx +++ b/superset-frontend/src/explore/components/DatasourcePanel/index.tsx @@ -277,7 +277,8 @@ export default function DataSourcePanel({ }; const datasourceIsSaveable = - datasource.type && saveableDatasets[datasource.type]; + datasource.type && + saveableDatasets[datasource.type as keyof typeof saveableDatasets]; const mainBody = useMemo( () => ( diff --git a/superset-frontend/src/explore/components/ExploreAlert.tsx b/superset-frontend/src/explore/components/ExploreAlert.tsx index 15c7ad3b4be38..7630d69a2abaa 100644 --- a/superset-frontend/src/explore/components/ExploreAlert.tsx +++ b/superset-frontend/src/explore/components/ExploreAlert.tsx @@ -19,7 +19,7 @@ import { forwardRef, RefObject, MouseEvent } from 'react'; import { css, styled } from '@superset-ui/core'; -import Button from 'src/components/Button'; +import Button, { ButtonStyle } from 'src/components/Button'; interface ControlPanelAlertProps { title: string; @@ -88,6 +88,8 @@ const Title = styled.p` const typeChart = { warning: 'warning', danger: 'danger', + error: 'primary', + info: 'primary', }; export const ExploreAlert = forwardRef( @@ -119,7 +121,7 @@ export const ExploreAlert = forwardRef( )}