feat(fe): upgrade `superset-frontend` to Typescript v5 (#31979)
Signed-off-by: hainenber <dotronghai96@gmail.com> Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
This commit is contained in:
parent
a21f184058
commit
19e8a7049b
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -363,6 +363,13 @@ export type CustomControlItem = {
|
|||
config: BaseControlConfig<any, any, any>;
|
||||
};
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
//---------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -349,15 +349,17 @@ export type Query = {
|
|||
};
|
||||
|
||||
export type QueryResults = {
|
||||
results: {
|
||||
displayLimitReached: boolean;
|
||||
columns: QueryColumn[];
|
||||
data: Record<string, unknown>[];
|
||||
expanded_columns: QueryColumn[];
|
||||
selected_columns: QueryColumn[];
|
||||
query: { limit: number };
|
||||
query_id?: number;
|
||||
};
|
||||
results: InnerQueryResults;
|
||||
};
|
||||
|
||||
export type InnerQueryResults = {
|
||||
displayLimitReached: boolean;
|
||||
columns: QueryColumn[];
|
||||
data: Record<string, unknown>[];
|
||||
expanded_columns: QueryColumn[];
|
||||
selected_columns: QueryColumn[];
|
||||
query: { limit: number };
|
||||
query_id?: number;
|
||||
};
|
||||
|
||||
export type QueryResponse = Query & QueryResults;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 &&
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<any, { color: any; enabled: boolean }> = {};
|
||||
data.forEach(d => {
|
||||
if (d.cat_color != null && !categories.hasOwnProperty(d.cat_color)) {
|
||||
let color;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export type LegendProps = {
|
|||
format: string | null;
|
||||
forceCategorical?: boolean;
|
||||
position?: null | 'tl' | 'tr' | 'bl' | 'br';
|
||||
categories: Record<string, { enabled: boolean; color: number[] }>;
|
||||
categories: Record<string, { enabled: boolean; color: number[] | undefined }>;
|
||||
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 (
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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]));
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export function getExploreLongUrl(
|
|||
formData: JsonObject,
|
||||
endpointType: string,
|
||||
allowOverflow = true,
|
||||
extraSearch = {},
|
||||
extraSearch: Record<string, any> = {},
|
||||
): string | undefined {
|
||||
if (!formData.datasource) {
|
||||
return undefined;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export const selectedChartMutator = (
|
|||
return [];
|
||||
}
|
||||
|
||||
const data: Record<string, any> = [];
|
||||
const data: any[] = [];
|
||||
if (value && typeof value === 'string') {
|
||||
const parsedValue = JSON.parse(value);
|
||||
let itemFound = false;
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ export const stripGeomColumnFromLabelMap = (
|
|||
labelMap: { [key: string]: string[] },
|
||||
geomColumn: string,
|
||||
) => {
|
||||
const newLabelMap = {};
|
||||
const newLabelMap: Record<string, string[]> = {};
|
||||
Object.entries(labelMap).forEach(([key, value]) => {
|
||||
if (key === geomColumn) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const getFontSizeMapping = (
|
|||
proportionValues: number[],
|
||||
actualSizes: number[],
|
||||
) =>
|
||||
proportionValues.reduce((acc, value, index) => {
|
||||
proportionValues.reduce<Record<number, number>>((acc, value, index) => {
|
||||
acc[value] = actualSizes[index] ?? actualSizes[actualSizes.length - 1];
|
||||
return acc;
|
||||
}, {});
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<any> =>
|
||||
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<ScatterSeriesOption, number>(
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export const retainFormDataSuffix = (
|
|||
* > removeFormDataSuffix(fd, '_b')
|
||||
* { metrics: ['zee'], limit: 100, ... }
|
||||
* */
|
||||
const newFormData = {};
|
||||
const newFormData: Record<string, any> = {};
|
||||
|
||||
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<string, any> = {};
|
||||
Object.entries(formData).forEach(([key, value]) => {
|
||||
if (!key.endsWith(controlSuffix)) {
|
||||
newFormData[key] = value;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export const caches = {};
|
||||
export const caches: Record<string, Record<string, Response>> = {};
|
||||
|
||||
export default class Cache {
|
||||
cache: object;
|
||||
cache: Record<string, Response>;
|
||||
|
||||
constructor(key: string) {
|
||||
caches[key] = caches[key] || {};
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export enum KeyboardShortcut {
|
|||
CtrlRight = 'ctrl+]',
|
||||
}
|
||||
|
||||
export const KEY_MAP = {
|
||||
export const KEY_MAP: Record<KeyboardShortcut, string | undefined> = {
|
||||
[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<string, string[]>,
|
||||
);
|
||||
const KeyMapByCommand = Object.entries(KEY_MAP).reduce<
|
||||
Record<string, string[]>
|
||||
>((acc, [shortcut, command]) => {
|
||||
if (command) {
|
||||
acc[command] = [...(acc[command] || []), shortcut];
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const ShortcutDescription = styled.span`
|
||||
font-size: ${({ theme }) => theme.typography.sizes.m}px;
|
||||
|
|
|
|||
|
|
@ -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: (
|
||||
<Icons.LoadingOutlined
|
||||
iconColor={theme.colors.primary.base}
|
||||
iconSize="m"
|
||||
/>
|
||||
),
|
||||
label: t('Started'),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return queries
|
||||
|
|
|
|||
|
|
@ -68,10 +68,10 @@ const getValidator = () => {
|
|||
const rules: any = getValidationRules();
|
||||
return (formData: Record<string, any>, 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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export const STATE_TYPE_MAP: Record<string, Type> = {
|
|||
success: 'success',
|
||||
};
|
||||
|
||||
export const STATE_TYPE_MAP_LOCALIZED = {
|
||||
export const STATE_TYPE_MAP_LOCALIZED: Record<string, string> = {
|
||||
offline: t('offline'),
|
||||
failed: t('failed'),
|
||||
pending: t('pending'),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export default function getInitialState({
|
|||
}),
|
||||
};
|
||||
|
||||
const destroyedQueryEditors = {};
|
||||
const destroyedQueryEditors: Record<string, number> = {};
|
||||
|
||||
/**
|
||||
* If the `SQLLAB_BACKEND_PERSISTENCE` feature flag is off, or if the user
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import {
|
|||
import { queries, defaultQueryEditor } from '../fixtures';
|
||||
|
||||
describe('reduxStateToLocalStorageHelper', () => {
|
||||
const queriesObj = {};
|
||||
const queriesObj: Record<string, any> = {};
|
||||
beforeEach(() => {
|
||||
queries.forEach(q => {
|
||||
queriesObj[q.id] = q;
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
}),
|
||||
{},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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<Record<string, any>>(
|
||||
(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<string, string | string[] | Set<string>>,
|
||||
overriddenGroupbyFields: new Set<string>(),
|
||||
overriddenAdhocFilterFields: new Set<string>(),
|
||||
},
|
||||
|
|
@ -256,7 +257,7 @@ export default function DrillByModal({
|
|||
|
||||
const getFiltersFromConfigsByFieldName = useCallback(
|
||||
() =>
|
||||
drillByConfigs.reduce((acc, config) => {
|
||||
drillByConfigs.reduce<Record<string, AdhocFilter[]>>((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],
|
||||
|
|
|
|||
|
|
@ -90,7 +90,9 @@ export default function DrillDetailPane({
|
|||
const [resultsPages, setResultsPages] = useState<Map<number, ResultsPage>>(
|
||||
new Map(),
|
||||
);
|
||||
const [timeFormatting, setTimeFormatting] = useState({});
|
||||
const [timeFormatting, setTimeFormatting] = useState<
|
||||
Record<string, TimeFormatting>
|
||||
>({});
|
||||
|
||||
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,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -34,10 +34,10 @@ export const CheckboxGallery = () =>
|
|||
<div style={{ marginBottom: '16px' }} key={status}>
|
||||
<Checkbox
|
||||
onChange={() => {}}
|
||||
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`}
|
||||
</div>
|
||||
));
|
||||
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
@ -36,9 +36,9 @@ import { recurseReactClone } from './utils';
|
|||
interface CRUDCollectionProps {
|
||||
allowAddItem?: boolean;
|
||||
allowDeletes?: boolean;
|
||||
collection: Array<object>;
|
||||
columnLabels?: object;
|
||||
columnLabelTooltips?: object;
|
||||
collection: Record<PropertyKey, any>[];
|
||||
columnLabels?: Record<PropertyKey, any>;
|
||||
columnLabelTooltips?: Record<PropertyKey, any>;
|
||||
emptyMessage?: ReactNode;
|
||||
expandFieldset?: ReactNode;
|
||||
extraButtons?: ReactNode;
|
||||
|
|
@ -58,8 +58,8 @@ interface CRUDCollectionProps {
|
|||
record: any,
|
||||
) => ReactNode)[];
|
||||
onChange?: (arg0: any) => void;
|
||||
tableColumns: Array<any>;
|
||||
sortColumns: Array<string>;
|
||||
tableColumns: any[];
|
||||
sortColumns: string[];
|
||||
stickyHeader?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -72,14 +72,14 @@ enum SortOrder {
|
|||
}
|
||||
|
||||
interface CRUDCollectionState {
|
||||
collection: object;
|
||||
collectionArray: Array<object>;
|
||||
expandedColumns: object;
|
||||
collection: Record<PropertyKey, any>;
|
||||
collectionArray: Record<PropertyKey, any>[];
|
||||
expandedColumns: Record<PropertyKey, any>;
|
||||
sortColumn: string;
|
||||
sort: SortOrder;
|
||||
}
|
||||
|
||||
function createCollectionArray(collection: object) {
|
||||
function createCollectionArray(collection: Record<PropertyKey, any>) {
|
||||
return Object.keys(collection).map(k => collection[k]);
|
||||
}
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ function createKeyedCollection(arr: Array<object>) {
|
|||
id: o.id || nanoid(),
|
||||
}));
|
||||
|
||||
const collection = {};
|
||||
const collection: Record<PropertyKey, any> = {};
|
||||
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<PropertyKey, any>, b: Record<PropertyKey, any>) =>
|
||||
compareSort(a[col], b[col]),
|
||||
);
|
||||
const newCollection =
|
||||
sort === SortOrder.Asc ? sorted : sorted.reverse();
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ const interactiveTypes = {
|
|||
},
|
||||
};
|
||||
|
||||
export const InteractiveDatePicker = (args: DatePickerProps) => (
|
||||
export const InteractiveDatePicker: any = (args: DatePickerProps) => (
|
||||
<DatePicker {...args} />
|
||||
);
|
||||
|
||||
|
|
@ -87,7 +87,7 @@ InteractiveDatePicker.args = {
|
|||
|
||||
InteractiveDatePicker.argTypes = interactiveTypes;
|
||||
|
||||
export const InteractiveRangePicker = (args: RangePickerProps) => (
|
||||
export const InteractiveRangePicker: any = (args: RangePickerProps) => (
|
||||
<RangePicker {...args} />
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div role="img" aria-label="empty">
|
||||
<Empty
|
||||
|
|
|
|||
|
|
@ -52,7 +52,10 @@ const collapseStyle = (theme: SupersetTheme) => css`
|
|||
const extractInvalidValues = (messages: object, payload: object): string[] => {
|
||||
const invalidValues: string[] = [];
|
||||
|
||||
const recursiveExtract = (messages: object, payload: object) => {
|
||||
const recursiveExtract = (
|
||||
messages: Record<string, any>,
|
||||
payload: Record<string, any>,
|
||||
) => {
|
||||
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<string, any>,
|
||||
payload as Record<string, any>,
|
||||
);
|
||||
return invalidValues;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ const FilterableTable = ({
|
|||
}: FilterableTableProps) => {
|
||||
const formatTableData = (data: Record<string, unknown>[]): Datum[] =>
|
||||
data.map(row => {
|
||||
const newRow = {};
|
||||
const newRow: Record<string, any> = {};
|
||||
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<string, number> = {};
|
||||
const cellContent = ([] as string[]).concat(
|
||||
...orderedColumnKeys.map(key => {
|
||||
const cellContentList = list.map((data: Datum) =>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <StyledIcon component={AntdIcons[k]} role={whatRole} {...props} />;
|
||||
},
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export default {
|
|||
component: Icon,
|
||||
};
|
||||
|
||||
const palette = { Default: null };
|
||||
const palette: Record<string, string | null> = { Default: null };
|
||||
Object.entries(supersetTheme.colors).forEach(([familyName, family]) => {
|
||||
Object.entries(family).forEach(([colorName, colorValue]) => {
|
||||
palette[`${familyName} / ${colorName}`] = colorValue;
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Tag
|
||||
onClick={onClick}
|
||||
|
|
|
|||
|
|
@ -65,19 +65,21 @@ export type Filters = Filter[];
|
|||
|
||||
export type ViewModeType = 'card' | 'table';
|
||||
|
||||
export type InnerFilterValue =
|
||||
| string
|
||||
| boolean
|
||||
| number
|
||||
| null
|
||||
| undefined
|
||||
| string[]
|
||||
| number[]
|
||||
| { label: string; value: string | number };
|
||||
|
||||
export interface FilterValue {
|
||||
id: string;
|
||||
urlDisplay?: string;
|
||||
operator?: string;
|
||||
value:
|
||||
| string
|
||||
| boolean
|
||||
| number
|
||||
| null
|
||||
| undefined
|
||||
| string[]
|
||||
| number[]
|
||||
| { label: string; value: string | number };
|
||||
value: InnerFilterValue;
|
||||
}
|
||||
|
||||
export interface FetchDataConfig {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import {
|
|||
FetchDataConfig,
|
||||
Filter,
|
||||
FilterValue,
|
||||
InnerFilterValue,
|
||||
InternalFilter,
|
||||
SortColumn,
|
||||
ViewModeType,
|
||||
|
|
@ -138,7 +139,7 @@ export function convertFiltersRison(
|
|||
list: Filter[],
|
||||
): FilterValue[] {
|
||||
const filters: FilterValue[] = [];
|
||||
const refs = {};
|
||||
const refs: Record<string, FilterValue> = {};
|
||||
|
||||
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<string, InnerFilterValue> = {};
|
||||
|
||||
internalFilters.forEach(filter => {
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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 = (
|
||||
|
|
|
|||
|
|
@ -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) => <Steps {...args} />;
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export default {
|
|||
argTypes: { onClick: { action: 'clicked' } },
|
||||
} as Meta<typeof Table>;
|
||||
|
||||
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<string, number> {
|
||||
const cells: Record<string, number> = {};
|
||||
for (let i = 0; i < amount; i += 1) {
|
||||
cells[`col-${i}`] = i * row * 0.75;
|
||||
}
|
||||
|
|
@ -94,7 +94,12 @@ function generateColumns(amount: number): ColumnsType<ExampleData>[] {
|
|||
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<PropertyKey, any>,
|
||||
b as Record<PropertyKey, any>,
|
||||
),
|
||||
});
|
||||
}
|
||||
return newCols as ColumnsType<ExampleData>[];
|
||||
|
|
@ -168,19 +173,34 @@ const basicColumns: ColumnsType<BasicData> = [
|
|||
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<PropertyKey, any>,
|
||||
b as Record<PropertyKey, any>,
|
||||
),
|
||||
},
|
||||
{
|
||||
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<PropertyKey, any>,
|
||||
b as Record<PropertyKey, any>,
|
||||
),
|
||||
},
|
||||
{
|
||||
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<PropertyKey, any>,
|
||||
b as Record<PropertyKey, any>,
|
||||
),
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
|
|
@ -201,7 +221,12 @@ const bigColumns: ColumnsType<ExampleData> = [
|
|||
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<PropertyKey, any>,
|
||||
b as Record<PropertyKey, any>,
|
||||
),
|
||||
width: 75,
|
||||
},
|
||||
{
|
||||
|
|
@ -381,7 +406,12 @@ const paginationColumns: ColumnsType<BasicData> = [
|
|||
locale={LocaleCode.en_US}
|
||||
/>
|
||||
),
|
||||
sorter: (a: BasicData, b: BasicData) => numericalSort('price', a, b),
|
||||
sorter: (a: BasicData, b: BasicData) =>
|
||||
numericalSort(
|
||||
'price',
|
||||
a as Record<PropertyKey, any>,
|
||||
b as Record<PropertyKey, any>,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Description',
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<RecordType extends object>(
|
||||
props: TableProps<RecordType>,
|
||||
|
|
@ -277,7 +279,7 @@ export function Table<RecordType extends object>(
|
|||
|
||||
const selectionTypeValue = selectionMap[selectionType];
|
||||
const rowSelection = {
|
||||
type: selectionTypeValue,
|
||||
type: selectionMap[selectionType] as RowSelectionType,
|
||||
selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
};
|
||||
|
|
@ -398,7 +400,7 @@ export function Table<RecordType extends object>(
|
|||
{!virtualize && (
|
||||
<StyledTable
|
||||
{...sharedProps}
|
||||
rowSelection={selectionTypeValue ? rowSelection : undefined}
|
||||
rowSelection={selectionTypeValue !== null ? rowSelection : undefined}
|
||||
sticky={sticky}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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' },
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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<PropertyKey, string>,
|
||||
b: Record<PropertyKey, string>,
|
||||
): 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<PropertyKey, number>,
|
||||
b: Record<PropertyKey, number>,
|
||||
): number => a?.[key] - b?.[key];
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<ColorSchemeControl
|
||||
description={t(
|
||||
"Any color palette selected here will override the colors applied to this dashboard's individual charts",
|
||||
)}
|
||||
labelMargin={labelMargin}
|
||||
name="color_scheme"
|
||||
onChange={this.props.onChange}
|
||||
value={colorScheme}
|
||||
choices={this.choices}
|
||||
clearable
|
||||
schemes={this.schemes}
|
||||
hovered={this.state.hovered}
|
||||
hasCustomLabelsColor={hasCustomLabelsColor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ColorSchemeControlWrapper.propTypes = propTypes;
|
||||
ColorSchemeControlWrapper.defaultProps = defaultProps;
|
||||
|
||||
export default ColorSchemeControlWrapper;
|
||||
|
|
@ -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<string[][]>([]);
|
||||
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 (
|
||||
<ColorSchemeControl
|
||||
description={t(
|
||||
"Any color palette selected here will override the colors applied to this dashboard's individual charts",
|
||||
)}
|
||||
name="color_scheme"
|
||||
onChange={onChange}
|
||||
value={colorScheme ?? ''}
|
||||
choices={choices}
|
||||
clearable
|
||||
hovered={hovered}
|
||||
schemes={schemes}
|
||||
hasCustomLabelsColor={hasCustomLabelsColor}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColorSchemeControlWrapper;
|
||||
|
|
@ -122,7 +122,7 @@ const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => {
|
|||
const [dashboardLabelsColorInitiated, setDashboardLabelsColorInitiated] =
|
||||
useState(false);
|
||||
const prevRenderedChartIds = useRef<number[]>([]);
|
||||
const prevTabIndexRef = useRef();
|
||||
const prevTabIndexRef = useRef<number>();
|
||||
const tabIndex = useMemo(() => {
|
||||
const nextTabIndex = findTabIndexByComponentId({
|
||||
currentComponent: getRootLevelTabsComponent(dashboardLayout),
|
||||
|
|
|
|||
|
|
@ -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 ||
|
||||
|
|
|
|||
|
|
@ -463,7 +463,6 @@ const PropertiesModal = ({
|
|||
hasCustomLabelsColor={hasCustomLabelsColor}
|
||||
onChange={onColorSchemeChange}
|
||||
colorScheme={colorScheme}
|
||||
labelMargin={4}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
@ -532,7 +531,6 @@ const PropertiesModal = ({
|
|||
hasCustomLabelsColor={hasCustomLabelsColor}
|
||||
onChange={onColorSchemeChange}
|
||||
colorScheme={colorScheme}
|
||||
labelMargin={4}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
|||
|
|
@ -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<string, Pick<Filter | Divider, 'chartsInScope'>>
|
||||
>((acc, key) => {
|
||||
acc[key] = pick(filters[key], ['chartsInScope']);
|
||||
return acc;
|
||||
}, {});
|
||||
|
|
|
|||
|
|
@ -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<string, { id: number; crossFilters: ChartCrossFiltersConfig }>
|
||||
>((acc, [id, config]) => {
|
||||
if (isCrossFilterScopeGlobal(config.crossFilters.scope)) {
|
||||
acc[id] = {
|
||||
id: Number(config.id),
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ const FilterBar: FC<FiltersBarProps> = ({
|
|||
|
||||
useEffect(() => {
|
||||
if (previousFilters && dashboardId === previousDashboardId) {
|
||||
const updates = {};
|
||||
const updates: Record<string, DataMaskWithId> = {};
|
||||
Object.values(filters).forEach(currentFilter => {
|
||||
const previousFilter = previousFilters?.[currentFilter.id];
|
||||
if (!previousFilter) {
|
||||
|
|
@ -208,7 +208,9 @@ const FilterBar: FC<FiltersBarProps> = ({
|
|||
const dataMaskChanged = !isEqual(currentDataMask, previousDataMask);
|
||||
|
||||
if (typeChanged || targetsChanged || dataMaskChanged) {
|
||||
updates[currentFilter.id] = getInitialDataMask(currentFilter.id);
|
||||
updates[currentFilter.id] = getInitialDataMask(
|
||||
currentFilter.id,
|
||||
) as DataMaskWithId;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ const selectIndicatorValue = (
|
|||
(columnKey === TIME_FILTER_MAP.time_grain_sqla
|
||||
? datasource.time_grain_sqla
|
||||
: datasource.granularity) || []
|
||||
).reduce(
|
||||
).reduce<Record<string, string>>(
|
||||
(map, [key, value]) => ({
|
||||
...map,
|
||||
[key]: value,
|
||||
|
|
@ -189,8 +189,16 @@ export const getCrossFilterIndicator = (
|
|||
return filterObject;
|
||||
};
|
||||
|
||||
const cachedIndicatorsForChart = {};
|
||||
const cachedDashboardFilterDataForChart = {};
|
||||
const cachedIndicatorsForChart: Record<number, Indicator[]> = {};
|
||||
const cachedDashboardFilterDataForChart: Record<
|
||||
string,
|
||||
{
|
||||
appliedColumns: Set<string>;
|
||||
rejectedColumns: Set<string>;
|
||||
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<string>,
|
||||
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<number, any> = {};
|
||||
const cachedNativeFilterDataForChart: Record<
|
||||
number,
|
||||
{
|
||||
nativeFilters: Filters;
|
||||
chartLayoutItems: LayoutItem[];
|
||||
chartConfiguration: ChartConfiguration;
|
||||
dataMask: DataMaskStateWithId;
|
||||
appliedColumns: Set<string>;
|
||||
rejectedColumns: Set<string>;
|
||||
}
|
||||
> = {};
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -45,12 +45,12 @@ export function useFilterConfigMap() {
|
|||
const filterConfig = useFilterConfiguration();
|
||||
return useMemo(
|
||||
() =>
|
||||
filterConfig.reduce(
|
||||
filterConfig.reduce<Record<string, Filter | Divider>>(
|
||||
(acc: Record<string, Filter | Divider>, filter: Filter) => {
|
||||
acc[filter.id] = filter;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, Filter | Divider>,
|
||||
{},
|
||||
),
|
||||
[filterConfig],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<ExtraFormData, any[]>] = 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<ExtraFormData, typeof originalValue>] =
|
||||
originalValue as TimeGranularity;
|
||||
}
|
||||
const newValue = newExtra[key];
|
||||
const newValue = newExtra[key as keyof ExtraFormDataOverride];
|
||||
if (newValue !== undefined) {
|
||||
mergedExtra[key] = newValue;
|
||||
mergedExtra[key as OnlyKeyWithType<ExtraFormData, typeof newValue>] =
|
||||
newValue as TimeGranularity;
|
||||
}
|
||||
});
|
||||
return mergedExtra;
|
||||
|
|
|
|||
|
|
@ -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<NativeFiltersState> = {};
|
||||
const filters = {};
|
||||
const filters: Record<string, Filter | Divider> = {};
|
||||
if (filterConfig) {
|
||||
filterConfig.forEach(filter => {
|
||||
const { id } = filter;
|
||||
|
|
|
|||
|
|
@ -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<NativeFilterTarget>];
|
||||
scope: number[];
|
||||
values: ExtraFormData;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<string, string>;
|
||||
shared_label_colors?: string[];
|
||||
map_label_colors?: Record<string, string>;
|
||||
}
|
||||
|
||||
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<number, DataRecordFilters> = {};
|
||||
const cachedFormdataByChart: Record<
|
||||
number,
|
||||
CachedFormData & {
|
||||
dataMask: DataMask;
|
||||
extraControls: Record<string, string | boolean | null>;
|
||||
}
|
||||
> = {};
|
||||
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -48,15 +48,17 @@ function findParentId(structure: IStructure): string | null {
|
|||
return parentId;
|
||||
}
|
||||
|
||||
const cache = {};
|
||||
const cache: Record<string, string | null> = {};
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<string, boolean> = {};
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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<string, Record<string, number>> = {
|
||||
[DASHBOARD_ROOT_TYPE]: {
|
||||
[TABS_TYPE]: rootDepth,
|
||||
[DASHBOARD_GRID_TYPE]: rootDepth,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<string, any>) => row[key],
|
||||
Header:
|
||||
colType === GenericDataType.Temporal &&
|
||||
typeof firstValue !== 'string' ? (
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
() => (
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
</Button>
|
||||
)}
|
||||
<Button
|
||||
buttonStyle={type in typeChart ? typeChart[type] : 'primary'}
|
||||
buttonStyle={typeChart[type] as ButtonStyle}
|
||||
buttonSize="small"
|
||||
onClick={primaryButtonAction}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ const defaultProps = () => ({
|
|||
hasCustomLabelsColor: false,
|
||||
sharedLabelsColors: [],
|
||||
label: 'Color scheme',
|
||||
labelMargin: 0,
|
||||
name: 'color',
|
||||
value: 'supersetDefault',
|
||||
clearable: true,
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ export interface ColorSchemeControlProps {
|
|||
colorNamespace?: string;
|
||||
chartId?: number;
|
||||
dashboardId?: number;
|
||||
label: string;
|
||||
label?: string;
|
||||
name: string;
|
||||
onChange?: (value: string) => void;
|
||||
value: string;
|
||||
|
|
@ -65,7 +65,9 @@ export interface ColorSchemeControlProps {
|
|||
defaultScheme?: string;
|
||||
choices: string[][] | (() => string[][]);
|
||||
schemes: ColorSchemes | (() => ColorSchemes);
|
||||
isLinear: boolean;
|
||||
isLinear?: boolean;
|
||||
description?: string;
|
||||
hovered?: boolean;
|
||||
}
|
||||
|
||||
const StyledAlert = styled(Icons.AlertSolid)`
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue