refactor(monorepo): stage 1 (#17427)

* skip geojson in pre-commit

update prettier

* update package.json

update package.json

u package

pkg

pkg2

* lint main repo 2

lint main repo

lint

* lintrc

lintrc 2

lintrc2

lintrc 3

lintrc

* fix import

* refresh lock file

* fix break line make @ts-ignore invalid

* update rat-excludes

rat-excludes

update rat-excludes

* update eslintrc.js

* lint lint lint
This commit is contained in:
Yongjie Zhao 2021-11-17 07:31:36 +00:00 committed by GitHub
parent 34d7f0a860
commit 9070b6b19c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
119 changed files with 2399 additions and 2222 deletions

View File

@ -33,6 +33,7 @@ repos:
hooks: hooks:
- id: check-docstring-first - id: check-docstring-first
- id: check-added-large-files - id: check-added-large-files
exclude: \.(geojson)$
- id: check-yaml - id: check-yaml
exclude: ^helm/superset/templates/ exclude: ^helm/superset/templates/
- id: debug-statements - id: debug-statements
@ -45,7 +46,7 @@ repos:
- id: black - id: black
language_version: python3 language_version: python3
- repo: https://github.com/pre-commit/mirrors-prettier - repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.2.1 # Use the sha or tag you want to point at rev: v2.4.1 # Use the sha or tag you want to point at
hooks: hooks:
- id: prettier - id: prettier
files: 'superset-frontend' files: 'superset-frontend'

View File

@ -48,3 +48,16 @@ vendor/*
# github configuration # github configuration
.github/* .github/*
.*mdx .*mdx
# skip license check in superset-ui
tmp/*
lib/*
esm/*
tsconfig.tsbuildinfo
.*ipynb
.*yml
.*iml
.esprintrc
.prettierignore
superset-frontend/packages/generator-superset
superset-frontend/temporary_superset_ui

View File

@ -16,6 +16,15 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
const packageConfig = require('./package');
const importCoreModules = [];
Object.entries(packageConfig.dependencies).forEach(([pkg]) => {
if (/@superset-ui/.test(pkg)) {
importCoreModules.push(pkg);
}
});
module.exports = { module.exports = {
extends: [ extends: [
'airbnb', 'airbnb',
@ -33,7 +42,15 @@ module.exports = {
browser: true, browser: true,
}, },
settings: { settings: {
'import/resolver': 'webpack', 'import/resolver': {
webpack: {},
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
},
},
// Allow only core/src and core/test, avoid import modules from lib
'import/internal-regex': /^@superset-ui\/core\/(src|test)\/.*/,
'import/core-modules': importCoreModules,
react: { react: {
version: 'detect', version: 'detect',
}, },
@ -76,11 +93,11 @@ module.exports = {
'@typescript-eslint/no-empty-function': 0, '@typescript-eslint/no-empty-function': 0,
'@typescript-eslint/no-explicit-any': 0, '@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/no-use-before-define': 1, // disabled temporarily '@typescript-eslint/no-use-before-define': 1, // disabled temporarily
'@typescript-eslint/no-non-null-assertion': 0, // disabled temporarily
'@typescript-eslint/explicit-function-return-type': 0, '@typescript-eslint/explicit-function-return-type': 0,
'@typescript-eslint/explicit-module-boundary-types': 0, // re-enable up for discussion '@typescript-eslint/explicit-module-boundary-types': 0, // re-enable up for discussion
camelcase: 0, camelcase: 0,
'class-methods-use-this': 0, 'class-methods-use-this': 0,
curly: 1,
'func-names': 0, 'func-names': 0,
'guard-for-in': 0, 'guard-for-in': 0,
'import/no-cycle': 0, // re-enable up for discussion, might require some major refactors 'import/no-cycle': 0, // re-enable up for discussion, might require some major refactors
@ -170,11 +187,11 @@ module.exports = {
}, },
{ {
files: [ files: [
'src/**/*.test.ts', '*.test.ts',
'src/**/*.test.tsx', '*.test.tsx',
'src/**/*.test.js', '*.test.js',
'src/**/*.test.jsx', '*.test.jsx',
'src/**/fixtures.*', 'fixtures.*',
], ],
plugins: ['jest', 'jest-dom', 'no-only-tests', 'testing-library'], plugins: ['jest', 'jest-dom', 'no-only-tests', 'testing-library'],
env: { env: {
@ -195,9 +212,28 @@ module.exports = {
'error', 'error',
{ devDependencies: true }, { devDependencies: true },
], ],
'jest/consistent-test-it': 'error',
'no-only-tests/no-only-tests': 'error', 'no-only-tests/no-only-tests': 'error',
'max-classes-per-file': 0,
'@typescript-eslint/no-non-null-assertion': 0, '@typescript-eslint/no-non-null-assertion': 0,
// TODO: disabled temporarily, re-enable after monorepo
'jest/consistent-test-it': 'error',
'jest/expect-expect': 0,
'jest/no-test-prefixes': 0,
'jest/valid-expect-in-promise': 0,
'jest/valid-expect': 0,
'jest/valid-title': 0,
'jest-dom/prefer-to-have-attribute': 0,
'jest-dom/prefer-to-have-text-content': 0,
'jest-dom/prefer-to-have-style': 0,
},
},
{
files: './packages/generator-superset/**/*.test.*',
env: {
node: true,
},
rules: {
'jest/expect-expect': 0,
}, },
}, },
], ],
@ -210,7 +246,7 @@ module.exports = {
}, },
], ],
'class-methods-use-this': 0, 'class-methods-use-this': 0,
curly: 1, curly: 2,
'func-names': 0, 'func-names': 0,
'guard-for-in': 0, 'guard-for-in': 0,
'import/extensions': [ 'import/extensions': [

View File

@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
const packageConfig = require('./package.json'); const packageConfig = require('./package');
module.exports = { module.exports = {
sourceMaps: true, sourceMaps: true,

View File

@ -22,7 +22,7 @@
export default function parsePostForm(requestBody: ArrayBuffer) { export default function parsePostForm(requestBody: ArrayBuffer) {
type ParsedFields = Record<string, string[] | string>; type ParsedFields = Record<string, string[] | string>;
if (requestBody.constructor.name !== 'ArrayBuffer') { if (requestBody.constructor.name !== 'ArrayBuffer') {
return (requestBody as unknown) as ParsedFields; return requestBody as unknown as ParsedFields;
} }
const lines = new TextDecoder('utf-8').decode(requestBody).split('\n'); const lines = new TextDecoder('utf-8').decode(requestBody).split('\n');
const fields: ParsedFields = {}; const fields: ParsedFields = {};

File diff suppressed because it is too large Load Diff

View File

@ -2,37 +2,6 @@
"name": "superset", "name": "superset",
"version": "0.0.0dev", "version": "0.0.0dev",
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.", "description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
"license": "Apache-2.0",
"directories": {
"doc": "docs",
"test": "spec"
},
"scripts": {
"tdd": "cross-env NODE_ENV=test jest --watch",
"test": "cross-env NODE_ENV=test jest",
"type": "tsc --noEmit",
"cover": "cross-env NODE_ENV=test jest --coverage",
"dev": "webpack --mode=development --color --watch",
"dev-server": "cross-env NODE_ENV=development BABEL_ENV=development node --max_old_space_size=4096 ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --mode=development",
"prod": "npm run build",
"build-dev": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=development webpack --mode=development --color",
"build-instrumented": "cross-env NODE_ENV=production BABEL_ENV=instrumented webpack --mode=production --color",
"build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=production BABEL_ENV=\"${BABEL_ENV:=production}\" webpack --mode=production --color",
"lint": "eslint --ignore-path=.eslintignore --ext .js,.jsx,.ts,.tsx . && npm run type",
"prettier-check": "prettier --check 'src/**/*.{css,less,sass,scss}'",
"lint-fix": "eslint --fix --ignore-path=.eslintignore --ext .js,.jsx,.ts,tsx . && npm run clean-css && npm run type",
"clean-css": "prettier --write 'src/**/*.{css,less,sass,scss}'",
"format": "prettier --write './{src,spec,cypress-base}/**/*{.js,.jsx,.ts,.tsx,.css,.less,.scss,.sass}'",
"prettier": "npm run format",
"check-translation": "prettier --check ../superset/translations/**/LC_MESSAGES/*.json",
"clean-translation": "prettier --write ../superset/translations/**/LC_MESSAGES/*.json",
"storybook": "cross-env NODE_ENV=development BABEL_ENV=development start-storybook -s ./src/assets/images -p 6006",
"build-storybook": "build-storybook"
},
"repository": {
"type": "git",
"url": "git+https://github.com/apache/superset.git"
},
"keywords": [ "keywords": [
"big", "big",
"data", "data",
@ -45,21 +14,66 @@
"database", "database",
"flask" "flask"
], ],
"author": "Apache", "homepage": "https://superset.apache.org/",
"bugs": { "bugs": {
"url": "https://github.com/apache/superset/issues" "url": "https://github.com/apache/superset/issues"
}, },
"repository": {
"type": "git",
"url": "git+https://github.com/apache/superset.git"
},
"license": "Apache-2.0",
"author": "Apache",
"directories": {
"doc": "docs",
"test": "spec"
},
"scripts": {
"build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=production BABEL_ENV=\"${BABEL_ENV:=production}\" webpack --mode=production --color",
"build-dev": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=development webpack --mode=development --color",
"build-instrumented": "cross-env NODE_ENV=production BABEL_ENV=instrumented webpack --mode=production --color",
"build-storybook": "build-storybook",
"check-translation": "prettier --check ../superset/translations/**/LC_MESSAGES/*.json",
"clean-css": "prettier --write 'src/**/*.{css,less,sass,scss}'",
"clean-translation": "prettier --write ../superset/translations/**/LC_MESSAGES/*.json",
"cover": "cross-env NODE_ENV=test jest --coverage",
"dev": "webpack --mode=development --color --watch",
"dev-server": "cross-env NODE_ENV=development BABEL_ENV=development node --max_old_space_size=4096 ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --mode=development",
"format": "prettier --write './{src,spec,cypress-base,plugins,packages}/**/*{.js,.jsx,.ts,.tsx,.css,.less,.scss,.sass}'",
"lint": "eslint --ignore-path=.eslintignore --ext .js,.jsx,.ts,.tsx . && npm run type",
"lint-fix": "eslint --fix --ignore-path=.eslintignore --ext .js,.jsx,.ts,tsx . && npm run clean-css && npm run type",
"prettier": "npm run format",
"prettier-check": "prettier --check 'src/**/*.{css,less,sass,scss}'",
"prod": "npm run build",
"prune": "rm -rf ./{packages,plugins}/*/{lib,esm,tsconfig.tsbuildinfo,package-lock.json}",
"storybook": "cross-env NODE_ENV=development BABEL_ENV=development start-storybook -s ./src/assets/images -p 6006",
"tdd": "cross-env NODE_ENV=test jest --watch",
"test": "cross-env NODE_ENV=test jest",
"type": "tsc --noEmit"
},
"browserslist": [ "browserslist": [
"last 3 chrome versions", "last 3 chrome versions",
"last 3 firefox versions", "last 3 firefox versions",
"last 3 safari versions", "last 3 safari versions",
"last 3 edge versions" "last 3 edge versions"
], ],
"engines": { "stylelint": {
"node": "^16.9.1", "rules": {
"npm": "^7.5.4" "block-opening-brace-space-before": "always",
"no-missing-end-of-source-newline": "never",
"rule-empty-line-before": [
"always",
{
"except": [
"first-nested"
],
"ignore": [
"after-comment"
]
}
]
}
}, },
"homepage": "https://superset.apache.org/",
"dependencies": { "dependencies": {
"@ant-design/icons": "^4.2.2", "@ant-design/icons": "^4.2.2",
"@babel/runtime-corejs3": "^7.12.5", "@babel/runtime-corejs3": "^7.12.5",
@ -178,12 +192,15 @@
"redux-thunk": "^2.1.0", "redux-thunk": "^2.1.0",
"redux-undo": "^1.0.0-beta9-9-7", "redux-undo": "^1.0.0-beta9-9-7",
"regenerator-runtime": "^0.13.5", "regenerator-runtime": "^0.13.5",
"rimraf": "^3.0.2",
"rison": "^0.1.1", "rison": "^0.1.1",
"scroll-into-view-if-needed": "^2.2.28", "scroll-into-view-if-needed": "^2.2.28",
"shortid": "^2.2.6", "shortid": "^2.2.6",
"src": "file:./src",
"urijs": "^1.19.6", "urijs": "^1.19.6",
"use-immer": "^0.6.0", "use-immer": "^0.6.0",
"use-query-params": "^1.1.9" "use-query-params": "^1.1.9",
"yargs": "^15.4.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.15.7", "@babel/cli": "^7.15.7",
@ -258,7 +275,7 @@
"css-minimizer-webpack-plugin": "^3.0.2", "css-minimizer-webpack-plugin": "^3.0.2",
"enzyme": "^3.10.0", "enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0", "enzyme-adapter-react-16": "^1.14.0",
"eslint": "^7.17.0", "eslint": "^7.32.0",
"eslint-config-airbnb": "^18.2.1", "eslint-config-airbnb": "^18.2.1",
"eslint-config-prettier": "^7.1.0", "eslint-config-prettier": "^7.1.0",
"eslint-import-resolver-typescript": "^2.5.0", "eslint-import-resolver-typescript": "^2.5.0",
@ -284,17 +301,18 @@
"jest-environment-enzyme": "^7.1.2", "jest-environment-enzyme": "^7.1.2",
"jest-enzyme": "^7.1.2", "jest-enzyme": "^7.1.2",
"jest-websocket-mock": "^2.2.0", "jest-websocket-mock": "^2.2.0",
"lerna": "^3.22.1",
"jsdom": "^16.4.0", "jsdom": "^16.4.0",
"lerna": "^3.22.1",
"less": "^3.12.2", "less": "^3.12.2",
"less-loader": "^5.0.0", "less-loader": "^5.0.0",
"mini-css-extract-plugin": "^2.3.0", "mini-css-extract-plugin": "^2.3.0",
"mock-socket": "^9.0.3", "mock-socket": "^9.0.3",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"prettier": "^2.2.1", "prettier": "^2.4.1",
"prettier-plugin-packagejson": "^2.2.15",
"process": "^0.11.10", "process": "^0.11.10",
"react-test-renderer": "^16.9.0",
"react-resizable": "^3.0.4", "react-resizable": "^3.0.4",
"react-test-renderer": "^16.9.0",
"redux-mock-store": "^1.5.4", "redux-mock-store": "^1.5.4",
"sinon": "^9.0.2", "sinon": "^9.0.2",
"source-map-support": "^0.5.16", "source-map-support": "^0.5.16",
@ -312,24 +330,10 @@
"webpack-cli": "^4.8.0", "webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.2.0", "webpack-dev-server": "^4.2.0",
"webpack-manifest-plugin": "^4.0.2", "webpack-manifest-plugin": "^4.0.2",
"webpack-sources": "^3.2.0", "webpack-sources": "^3.2.0"
"yargs": "^15.4.1"
}, },
"stylelint": { "engines": {
"rules": { "node": "^16.9.1",
"block-opening-brace-space-before": "always", "npm": "^7.5.4"
"no-missing-end-of-source-newline": "never",
"rule-empty-line-before": [
"always",
{
"except": [
"first-nested"
],
"ignore": [
"after-comment"
]
}
]
}
} }
} }

View File

@ -65,7 +65,8 @@ export const getMockStoreWithChartsInTabsAndRoot = () =>
); );
export const mockStoreWithTabs = getMockStoreWithTabs(); export const mockStoreWithTabs = getMockStoreWithTabs();
export const mockStoreWithChartsInTabsAndRoot = getMockStoreWithChartsInTabsAndRoot(); export const mockStoreWithChartsInTabsAndRoot =
getMockStoreWithChartsInTabsAndRoot();
export const sliceIdWithAppliedFilter = sliceId + 1; export const sliceIdWithAppliedFilter = sliceId + 1;
export const sliceIdWithRejectedFilter = sliceId + 2; export const sliceIdWithRejectedFilter = sliceId + 2;

View File

@ -93,9 +93,8 @@ describe('dashboardState actions', () => {
// mock redux work: dispatch an event, cause modify redux state // mock redux work: dispatch an event, cause modify redux state
const mockParentsList = ['ROOT_ID']; const mockParentsList = ['ROOT_ID'];
dispatch.callsFake(() => { dispatch.callsFake(() => {
mockState.dashboardLayout.present[ mockState.dashboardLayout.present[DASHBOARD_GRID_ID].parents =
DASHBOARD_GRID_ID mockParentsList;
].parents = mockParentsList;
}); });
// call saveDashboardRequest, it should post dashboard data with updated // call saveDashboardRequest, it should post dashboard data with updated

View File

@ -183,7 +183,8 @@ describe('Tabs', () => {
expect(wrapper.state('tabIndex')).toBe(0); expect(wrapper.state('tabIndex')).toBe(0);
// display child in directPathToChild list // display child in directPathToChild list
const directPathToChild = dashboardLayoutWithTabs.present.ROW_ID2.parents.slice(); const directPathToChild =
dashboardLayoutWithTabs.present.ROW_ID2.parents.slice();
const directLinkProps = { const directLinkProps = {
...props, ...props,
directPathToChild, directPathToChild,

View File

@ -65,13 +65,13 @@ describe('getFormDataWithExtraFilters', () => {
nativeFilters: { nativeFilters: {
filterSets: {}, filterSets: {},
filters: { filters: {
[filterId]: ({ [filterId]: {
id: filterId, id: filterId,
scope: { scope: {
rootPath: [DASHBOARD_ROOT_ID], rootPath: [DASHBOARD_ROOT_ID],
excluded: [], excluded: [],
}, },
} as unknown) as Filter, } as unknown as Filter,
}, },
}, },
dataMask: { dataMask: {
@ -82,7 +82,7 @@ describe('getFormDataWithExtraFilters', () => {
ownState: {}, ownState: {},
}, },
}, },
layout: (dashboardLayout.present as unknown) as { layout: dashboardLayout.present as unknown as {
[key: string]: LayoutItem; [key: string]: LayoutItem;
}, },
}; };

View File

@ -29,9 +29,8 @@ describe('getLeafComponentIdFromPath', () => {
}); });
it('should not return label component', () => { it('should not return label component', () => {
const updatedPath = dashboardFilters[filterId].directPathToFilter.concat( const updatedPath =
'LABEL-test123', dashboardFilters[filterId].directPathToFilter.concat('LABEL-test123');
);
expect(getLeafComponentIdFromPath(updatedPath)).toBe(leaf); expect(getLeafComponentIdFromPath(updatedPath)).toBe(leaf);
}); });
}); });

View File

@ -43,10 +43,10 @@ const getKnownControlState = (...args: Parameters<typeof getControlState>) =>
describe('controlUtils', () => { describe('controlUtils', () => {
const state: ControlPanelState = { const state: ControlPanelState = {
datasource: ({ datasource: {
columns: [{ column_name: 'a' }], columns: [{ column_name: 'a' }],
metrics: [{ metric_name: 'first' }, { metric_name: 'second' }], metrics: [{ metric_name: 'first' }, { metric_name: 'second' }],
} as unknown) as DatasourceMeta, } as unknown as DatasourceMeta,
controls: {}, controls: {},
form_data: { datasource: '1__table', viz_type: 'table' }, form_data: { datasource: '1__table', viz_type: 'table' },
}; };

View File

@ -68,39 +68,41 @@ export const controlPanelSectionsChartOptions: ControlPanelSectionConfig[] = [
}, },
]; ];
export const controlPanelSectionsChartOptionsOnlyColorScheme: ControlPanelSectionConfig[] = [ export const controlPanelSectionsChartOptionsOnlyColorScheme: ControlPanelSectionConfig[] =
{ [
label: t('Chart Options'), {
expanded: true, label: t('Chart Options'),
controlSetRows: [['color_scheme']], expanded: true,
}, controlSetRows: [['color_scheme']],
]; },
];
export const controlPanelSectionsChartOptionsTable: ControlPanelSectionConfig[] = [ export const controlPanelSectionsChartOptionsTable: ControlPanelSectionConfig[] =
{ [
label: t('Chart Options'), {
expanded: true, label: t('Chart Options'),
controlSetRows: [ expanded: true,
[ controlSetRows: [
'metric', [
'metrics', 'metric',
{ 'metrics',
name: 'all_columns', {
config: { name: 'all_columns',
type: 'SelectControl', config: {
multi: true, type: 'SelectControl',
label: t('Columns'), multi: true,
default: [], label: t('Columns'),
description: t('Columns to display'), default: [],
optionRenderer: c => <ColumnOption column={c} showType />, description: t('Columns to display'),
valueKey: 'column_name', optionRenderer: c => <ColumnOption column={c} showType />,
mapStateToProps: stateRef => ({ valueKey: 'column_name',
options: stateRef.datasource ? stateRef.datasource.columns : [], mapStateToProps: stateRef => ({
}), options: stateRef.datasource ? stateRef.datasource.columns : [],
freeForm: true, }),
} as ControlConfig<'SelectControl', ColumnMeta>, freeForm: true,
}, } as ControlConfig<'SelectControl', ColumnMeta>,
},
],
], ],
], },
}, ];
];

View File

@ -252,9 +252,9 @@ export default class CRUDCollection extends React.PureComponent<
} }
// newly ordered collection // newly ordered collection
const sorted = [ const sorted = [...this.state.collectionArray].sort(
...this.state.collectionArray, (a: object, b: object) => compareSort(a[col], b[col]),
].sort((a: object, b: object) => compareSort(a[col], b[col])); );
const newCollection = const newCollection =
sort === SortOrder.asc ? sorted : sorted.reverse(); sort === SortOrder.asc ? sorted : sorted.reverse();
@ -280,12 +280,8 @@ export default class CRUDCollection extends React.PureComponent<
renderHeaderRow() { renderHeaderRow() {
const cols = this.effectiveTableColumns(); const cols = this.effectiveTableColumns();
const { const { allowDeletes, expandFieldset, extraButtons, sortColumns } =
allowDeletes, this.props;
expandFieldset,
extraButtons,
sortColumns,
} = this.props;
return ( return (
<thead> <thead>
<tr> <tr>
@ -322,12 +318,8 @@ export default class CRUDCollection extends React.PureComponent<
} }
renderItem(record: any) { renderItem(record: any) {
const { const { allowAddItem, allowDeletes, expandFieldset, tableColumns } =
allowAddItem, this.props;
allowDeletes,
expandFieldset,
tableColumns,
} = this.props;
/* eslint-disable no-underscore-dangle */ /* eslint-disable no-underscore-dangle */
const isExpanded = const isExpanded =
!!this.state.expandedColumns[record.id] || record.__expanded; !!this.state.expandedColumns[record.id] || record.__expanded;

View File

@ -44,9 +44,8 @@ class ExploreResultsButton extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.getInvalidColumns = this.getInvalidColumns.bind(this); this.getInvalidColumns = this.getInvalidColumns.bind(this);
this.renderInvalidColumnMessage = this.renderInvalidColumnMessage.bind( this.renderInvalidColumnMessage =
this, this.renderInvalidColumnMessage.bind(this);
);
} }
getColumns() { getColumns() {

View File

@ -221,7 +221,7 @@ function QuerySearch({ actions, displayLimit }: QuerySearchProps) {
value: xt, value: xt,
label: xt, label: xt,
}))} }))}
value={(from as unknown) as undefined} value={from as unknown as undefined}
autosize={false} autosize={false}
onChange={(selected: any) => setFrom(selected?.value)} onChange={(selected: any) => setFrom(selected?.value)}
/> />
@ -230,7 +230,7 @@ function QuerySearch({ actions, displayLimit }: QuerySearchProps) {
name="select-to" name="select-to"
placeholder={t('[To]-')} placeholder={t('[To]-')}
options={TIME_OPTIONS.map(xt => ({ value: xt, label: xt }))} options={TIME_OPTIONS.map(xt => ({ value: xt, label: xt }))}
value={(to as unknown) as undefined} value={to as unknown as undefined}
autosize={false} autosize={false}
onChange={(selected: any) => setTo(selected?.value)} onChange={(selected: any) => setTo(selected?.value)}
/> />
@ -242,7 +242,7 @@ function QuerySearch({ actions, displayLimit }: QuerySearchProps) {
value: s, value: s,
label: s, label: s,
}))} }))}
value={(status as unknown) as undefined} value={status as unknown as undefined}
isLoading={false} isLoading={false}
autosize={false} autosize={false}
onChange={(selected: any) => setStatus(selected?.value)} onChange={(selected: any) => setStatus(selected?.value)}

View File

@ -203,30 +203,25 @@ export default class ResultSet extends React.PureComponent<
this.fetchResults = this.fetchResults.bind(this); this.fetchResults = this.fetchResults.bind(this);
this.popSelectStar = this.popSelectStar.bind(this); this.popSelectStar = this.popSelectStar.bind(this);
this.reFetchQueryResults = this.reFetchQueryResults.bind(this); this.reFetchQueryResults = this.reFetchQueryResults.bind(this);
this.toggleExploreResultsButton = this.toggleExploreResultsButton.bind( this.toggleExploreResultsButton =
this, this.toggleExploreResultsButton.bind(this);
);
this.handleSaveInDataset = this.handleSaveInDataset.bind(this); this.handleSaveInDataset = this.handleSaveInDataset.bind(this);
this.handleHideSaveModal = this.handleHideSaveModal.bind(this); this.handleHideSaveModal = this.handleHideSaveModal.bind(this);
this.handleDatasetNameChange = this.handleDatasetNameChange.bind(this); this.handleDatasetNameChange = this.handleDatasetNameChange.bind(this);
this.handleSaveDatasetRadioBtnState = this.handleSaveDatasetRadioBtnState.bind( this.handleSaveDatasetRadioBtnState =
this, this.handleSaveDatasetRadioBtnState.bind(this);
);
this.handleOverwriteCancel = this.handleOverwriteCancel.bind(this); this.handleOverwriteCancel = this.handleOverwriteCancel.bind(this);
this.handleOverwriteDataset = this.handleOverwriteDataset.bind(this); this.handleOverwriteDataset = this.handleOverwriteDataset.bind(this);
this.handleOverwriteDatasetOption = this.handleOverwriteDatasetOption.bind( this.handleOverwriteDatasetOption =
this, this.handleOverwriteDatasetOption.bind(this);
);
this.handleSaveDatasetModalSearch = debounce( this.handleSaveDatasetModalSearch = debounce(
this.handleSaveDatasetModalSearch.bind(this), this.handleSaveDatasetModalSearch.bind(this),
1000, 1000,
); );
this.handleFilterAutocompleteOption = this.handleFilterAutocompleteOption.bind( this.handleFilterAutocompleteOption =
this, this.handleFilterAutocompleteOption.bind(this);
); this.handleOnChangeAutoComplete =
this.handleOnChangeAutoComplete = this.handleOnChangeAutoComplete.bind( this.handleOnChangeAutoComplete.bind(this);
this,
);
this.handleExploreBtnClick = this.handleExploreBtnClick.bind(this); this.handleExploreBtnClick = this.handleExploreBtnClick.bind(this);
} }

View File

@ -184,9 +184,8 @@ class SqlEditor extends React.PureComponent {
); );
this.queryPane = this.queryPane.bind(this); this.queryPane = this.queryPane.bind(this);
this.renderQueryLimit = this.renderQueryLimit.bind(this); this.renderQueryLimit = this.renderQueryLimit.bind(this);
this.getAceEditorAndSouthPaneHeights = this.getAceEditorAndSouthPaneHeights.bind( this.getAceEditorAndSouthPaneHeights =
this, this.getAceEditorAndSouthPaneHeights.bind(this);
);
this.getSqlEditorHeight = this.getSqlEditorHeight.bind(this); this.getSqlEditorHeight = this.getSqlEditorHeight.bind(this);
this.requestValidation = debounce( this.requestValidation = debounce(
this.requestValidation.bind(this), this.requestValidation.bind(this),
@ -456,14 +455,12 @@ class SqlEditor extends React.PureComponent {
queryPane() { queryPane() {
const hotkeys = this.getHotkeyConfig(); const hotkeys = this.getHotkeyConfig();
const { const { aceEditorHeight, southPaneHeight } =
aceEditorHeight, this.getAceEditorAndSouthPaneHeights(
southPaneHeight, this.state.height,
} = this.getAceEditorAndSouthPaneHeights( this.state.northPercent,
this.state.height, this.state.southPercent,
this.state.northPercent, );
this.state.southPercent,
);
return ( return (
<Split <Split
expandToMin expandToMin

View File

@ -84,9 +84,8 @@ class TabbedSqlEditors extends React.PureComponent {
this.removeQueryEditor = this.removeQueryEditor.bind(this); this.removeQueryEditor = this.removeQueryEditor.bind(this);
this.renameTab = this.renameTab.bind(this); this.renameTab = this.renameTab.bind(this);
this.toggleLeftBar = this.toggleLeftBar.bind(this); this.toggleLeftBar = this.toggleLeftBar.bind(this);
this.removeAllOtherQueryEditors = this.removeAllOtherQueryEditors.bind( this.removeAllOtherQueryEditors =
this, this.removeAllOtherQueryEditors.bind(this);
);
this.duplicateQueryEditor = this.duplicateQueryEditor.bind(this); this.duplicateQueryEditor = this.duplicateQueryEditor.bind(this);
this.handleSelect = this.handleSelect.bind(this); this.handleSelect = this.handleSelect.bind(this);
this.handleEdit = this.handleEdit.bind(this); this.handleEdit = this.handleEdit.bind(this);

View File

@ -110,9 +110,8 @@ const RefreshOverlayWrapper = styled.div`
class Chart extends React.PureComponent { class Chart extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.handleRenderContainerFailure = this.handleRenderContainerFailure.bind( this.handleRenderContainerFailure =
this, this.handleRenderContainerFailure.bind(this);
);
} }
componentDidMount() { componentDidMount() {

View File

@ -162,13 +162,8 @@ class ChartRenderer extends React.Component {
} }
render() { render() {
const { const { chartAlert, chartStatus, vizType, chartId, refreshOverlayVisible } =
chartAlert, this.props;
chartStatus,
vizType,
chartId,
refreshOverlayVisible,
} = this.props;
// Skip chart rendering // Skip chart rendering
if ( if (

View File

@ -574,8 +574,8 @@ export function redirectSQLLab(formData) {
export function refreshChart(chartKey, force, dashboardId) { export function refreshChart(chartKey, force, dashboardId) {
return (dispatch, getState) => { return (dispatch, getState) => {
const chart = (getState().charts || {})[chartKey]; const chart = (getState().charts || {})[chartKey];
const timeout = getState().dashboardInfo.common.conf const timeout =
.SUPERSET_WEBSERVER_TIMEOUT; getState().dashboardInfo.common.conf.SUPERSET_WEBSERVER_TIMEOUT;
if ( if (
!chart.latestQueryFormData || !chart.latestQueryFormData ||

View File

@ -70,12 +70,8 @@ class AnchorLink extends React.PureComponent {
} }
render() { render() {
const { const { anchorLinkId, filters, showShortLinkButton, placement } =
anchorLinkId, this.props;
filters,
showShortLinkButton,
placement,
} = this.props;
return ( return (
<span className="anchor-link-container" id={anchorLinkId}> <span className="anchor-link-container" id={anchorLinkId}>
{showShortLinkButton && ( {showShortLinkButton && (

View File

@ -53,7 +53,7 @@ function DefaultPlaceholder({
*/ */
export default function AsyncEsmComponent< export default function AsyncEsmComponent<
P = PlaceholderProps, P = PlaceholderProps,
M = React.ComponentType<P> | { default: React.ComponentType<P> } M = React.ComponentType<P> | { default: React.ComponentType<P> },
>( >(
/** /**
* A promise generator that returns the React component to render. * A promise generator that returns the React component to render.

View File

@ -25,10 +25,12 @@ export interface BadgeProps extends AntdBadgeProps {
textColor?: string; textColor?: string;
} }
const Badge = styled(( const Badge = styled(
// eslint-disable-next-line @typescript-eslint/no-unused-vars (
{ textColor, ...props }: BadgeProps, // eslint-disable-next-line @typescript-eslint/no-unused-vars
) => <AntdBadge {...props} />)` { textColor, ...props }: BadgeProps,
) => <AntdBadge {...props} />,
)`
& > sup { & > sup {
padding: 0 ${({ theme }) => theme.gridUnit * 2}px; padding: 0 ${({ theme }) => theme.gridUnit * 2}px;
background: ${({ theme, color }) => color || theme.colors.primary.base}; background: ${({ theme, color }) => color || theme.colors.primary.base};

View File

@ -93,8 +93,8 @@ test('renders with custom properties', () => {
}); });
const header = document.getElementsByClassName('ant-collapse-header')[0]; const header = document.getElementsByClassName('ant-collapse-header')[0];
const arrow = document.getElementsByClassName('ant-collapse-arrow')[0] const arrow =
.children[0]; document.getElementsByClassName('ant-collapse-arrow')[0].children[0];
const headerStyle = window.getComputedStyle(header); const headerStyle = window.getComputedStyle(header);
const arrowStyle = window.getComputedStyle(arrow); const arrowStyle = window.getComputedStyle(arrow);

View File

@ -146,61 +146,62 @@ export default function DatabaseSelector({
const [refresh, setRefresh] = useState(0); const [refresh, setRefresh] = useState(0);
const loadDatabases = useMemo( const loadDatabases = useMemo(
() => async ( () =>
search: string, async (
page: number, search: string,
pageSize: number, page: number,
): Promise<{ pageSize: number,
data: DatabaseValue[]; ): Promise<{
totalCount: number; data: DatabaseValue[];
}> => { totalCount: number;
const queryParams = rison.encode({ }> => {
order_columns: 'database_name', const queryParams = rison.encode({
order_direction: 'asc', order_columns: 'database_name',
page, order_direction: 'asc',
page_size: pageSize, page,
...(formMode || !sqlLabMode page_size: pageSize,
? { filters: [{ col: 'database_name', opr: 'ct', value: search }] } ...(formMode || !sqlLabMode
: { ? { filters: [{ col: 'database_name', opr: 'ct', value: search }] }
filters: [ : {
{ col: 'database_name', opr: 'ct', value: search }, filters: [
{ { col: 'database_name', opr: 'ct', value: search },
col: 'expose_in_sqllab', {
opr: 'eq', col: 'expose_in_sqllab',
value: true, opr: 'eq',
}, value: true,
], },
}), ],
}); }),
const endpoint = `/api/v1/database/?q=${queryParams}`; });
return SupersetClient.get({ endpoint }).then(({ json }) => { const endpoint = `/api/v1/database/?q=${queryParams}`;
const { result } = json; return SupersetClient.get({ endpoint }).then(({ json }) => {
if (getDbList) { const { result } = json;
getDbList(result); if (getDbList) {
} getDbList(result);
if (result.length === 0) { }
handleError(t("It seems you don't have access to any database")); if (result.length === 0) {
} handleError(t("It seems you don't have access to any database"));
const options = result.map((row: DatabaseObject) => ({ }
label: ( const options = result.map((row: DatabaseObject) => ({
<SelectLabel label: (
backend={row.backend} <SelectLabel
databaseName={row.database_name} backend={row.backend}
/> databaseName={row.database_name}
), />
value: row.id, ),
id: row.id, value: row.id,
database_name: row.database_name, id: row.id,
backend: row.backend, database_name: row.database_name,
allow_multi_schema_metadata_fetch: backend: row.backend,
row.allow_multi_schema_metadata_fetch, allow_multi_schema_metadata_fetch:
})); row.allow_multi_schema_metadata_fetch,
return { }));
data: options, return {
totalCount: options.length, data: options,
}; totalCount: options.length,
}); };
}, });
},
[formMode, getDbList, handleError, sqlLabMode], [formMode, getDbList, handleError, sqlLabMode],
); );

View File

@ -452,9 +452,8 @@ class DatasourceEditor extends React.PureComponent {
this.onChangeEditMode = this.onChangeEditMode.bind(this); this.onChangeEditMode = this.onChangeEditMode.bind(this);
this.onDatasourcePropChange = this.onDatasourcePropChange.bind(this); this.onDatasourcePropChange = this.onDatasourcePropChange.bind(this);
this.onDatasourceChange = this.onDatasourceChange.bind(this); this.onDatasourceChange = this.onDatasourceChange.bind(this);
this.tableChangeAndSyncMetadata = this.tableChangeAndSyncMetadata.bind( this.tableChangeAndSyncMetadata =
this, this.tableChangeAndSyncMetadata.bind(this);
);
this.syncMetadata = this.syncMetadata.bind(this); this.syncMetadata = this.syncMetadata.bind(this);
this.setColumns = this.setColumns.bind(this); this.setColumns = this.setColumns.bind(this);
this.validateAndChange = this.validateAndChange.bind(this); this.validateAndChange = this.validateAndChange.bind(this);

View File

@ -52,10 +52,8 @@ export default function EditableTitle({
const [isEditing, setIsEditing] = useState(editing); const [isEditing, setIsEditing] = useState(editing);
const [currentTitle, setCurrentTitle] = useState(title); const [currentTitle, setCurrentTitle] = useState(title);
const [lastTitle, setLastTitle] = useState(title); const [lastTitle, setLastTitle] = useState(title);
const [ const [contentBoundingRect, setContentBoundingRect] =
contentBoundingRect, useState<DOMRect | null>(null);
setContentBoundingRect,
] = useState<DOMRect | null>(null);
// Used so we can access the DOM element if a user clicks on this component. // Used so we can access the DOM element if a user clicks on this component.
const contentRef = useRef<any | HTMLInputElement | HTMLTextAreaElement>(); const contentRef = useRef<any | HTMLInputElement | HTMLTextAreaElement>();

View File

@ -38,10 +38,9 @@ function TimeoutErrorMessage({
}: ErrorMessageComponentProps<TimeoutErrorExtra>) { }: ErrorMessageComponentProps<TimeoutErrorExtra>) {
const { extra, level } = error; const { extra, level } = error;
const isVisualization = (['dashboard', 'explore'] as ( const isVisualization = (
| string ['dashboard', 'explore'] as (string | undefined)[]
| undefined ).includes(source);
)[]).includes(source);
const subtitle = isVisualization const subtitle = isVisualization
? tn( ? tn(

View File

@ -96,12 +96,12 @@ export type SupersetError<ExtraType = Record<string, any> | null> = {
message: string; message: string;
}; };
export type ErrorMessageComponentProps< export type ErrorMessageComponentProps<ExtraType = Record<string, any> | null> =
ExtraType = Record<string, any> | null {
> = { error: SupersetError<ExtraType>;
error: SupersetError<ExtraType>; source?: ErrorSource;
source?: ErrorSource; subtitle?: React.ReactNode;
subtitle?: React.ReactNode; };
};
export type ErrorMessageComponent = React.ComponentType<ErrorMessageComponentProps>; export type ErrorMessageComponent =
React.ComponentType<ErrorMessageComponentProps>;

View File

@ -126,9 +126,8 @@ const ImportModelsModal: FunctionComponent<ImportModelsModalProps> = ({
}) => { }) => {
const [isHidden, setIsHidden] = useState<boolean>(true); const [isHidden, setIsHidden] = useState<boolean>(true);
const [passwords, setPasswords] = useState<Record<string, string>>({}); const [passwords, setPasswords] = useState<Record<string, string>>({});
const [needsOverwriteConfirm, setNeedsOverwriteConfirm] = useState<boolean>( const [needsOverwriteConfirm, setNeedsOverwriteConfirm] =
false, useState<boolean>(false);
);
const [confirmedOverwrite, setConfirmedOverwrite] = useState<boolean>(false); const [confirmedOverwrite, setConfirmedOverwrite] = useState<boolean>(false);
const [fileList, setFileList] = useState<UploadFile[]>([]); const [fileList, setFileList] = useState<UploadFile[]>([]);
const [importingModel, setImportingModel] = useState<boolean>(false); const [importingModel, setImportingModel] = useState<boolean>(false);

View File

@ -45,15 +45,8 @@ export default function Label(props: LabelProps) {
const theme = useTheme(); const theme = useTheme();
const { colors, transitionTiming } = theme; const { colors, transitionTiming } = theme;
const { type, onClick, children, ...rest } = props; const { type, onClick, children, ...rest } = props;
const { const { primary, secondary, grayscale, success, warning, error, info } =
primary, colors;
secondary,
grayscale,
success,
warning,
error,
info,
} = colors;
let backgroundColor = grayscale.light3; let backgroundColor = grayscale.light3;
let backgroundColorHover = onClick ? primary.light2 : grayscale.light3; let backgroundColorHover = onClick ? primary.light2 : grayscale.light3;

View File

@ -41,19 +41,18 @@ export const InteractivePopoverDropdown = (props: Props) => {
const { value, buttonType, optionType, ...rest } = props; const { value, buttonType, optionType, ...rest } = props;
const [currentValue, setCurrentValue] = useState(value); const [currentValue, setCurrentValue] = useState(value);
const newElementHandler = (type: ElementType) => ({ const newElementHandler =
label, (type: ElementType) =>
value, ({ label, value }: OptionProps) => {
}: OptionProps) => { if (type === 'button') {
if (type === 'button') { return (
return ( <button type="button" key={value}>
<button type="button" key={value}> {label}
{label} </button>
</button> );
); }
} return <span>{label}</span>;
return <span>{label}</span>; };
};
return ( return (
<PopoverDropdown <PopoverDropdown

View File

@ -43,10 +43,8 @@ export default function HeaderReportActionsDropDown({
const reports = useSelector<any, AlertObject>(state => state.reports); const reports = useSelector<any, AlertObject>(state => state.reports);
const reportsIds = Object.keys(reports); const reportsIds = Object.keys(reports);
const report = reports[reportsIds[0]]; const report = reports[reportsIds[0]];
const [ const [currentReportDeleting, setCurrentReportDeleting] =
currentReportDeleting, useState<AlertObject | null>(null);
setCurrentReportDeleting,
] = useState<AlertObject | null>(null);
const theme = useTheme(); const theme = useTheme();
const toggleActiveKey = async (data: AlertObject, checked: boolean) => { const toggleActiveKey = async (data: AlertObject, checked: boolean) => {

View File

@ -71,7 +71,7 @@ type AnyReactSelect<OptionType extends OptionTypeBase> =
export type SupersetStyledSelectProps< export type SupersetStyledSelectProps<
OptionType extends OptionTypeBase, OptionType extends OptionTypeBase,
T extends WindowedSelectProps<OptionType> = WindowedSelectProps<OptionType> T extends WindowedSelectProps<OptionType> = WindowedSelectProps<OptionType>,
> = T & { > = T & {
// additional props for easier usage or backward compatibility // additional props for easier usage or backward compatibility
labelKey?: string; labelKey?: string;
@ -103,7 +103,7 @@ function styled<
| WindowedSelectComponentType<OptionType> | WindowedSelectComponentType<OptionType>
| ComponentType< | ComponentType<
SelectProps<OptionType> SelectProps<OptionType>
> = WindowedSelectComponentType<OptionType> > = WindowedSelectComponentType<OptionType>,
>(SelectComponent: SelectComponentType) { >(SelectComponent: SelectComponentType) {
type SelectProps = SupersetStyledSelectProps<OptionType>; type SelectProps = SupersetStyledSelectProps<OptionType>;
type Components = SelectComponents<OptionType>; type Components = SelectComponents<OptionType>;
@ -113,7 +113,8 @@ function styled<
}); });
// default components for the given OptionType // default components for the given OptionType
const supersetDefaultComponents: SelectComponentsConfig<OptionType> = DEFAULT_COMPONENTS; const supersetDefaultComponents: SelectComponentsConfig<OptionType> =
DEFAULT_COMPONENTS;
const getSortableMultiValue = (MultiValue: Components['MultiValue']) => const getSortableMultiValue = (MultiValue: Components['MultiValue']) =>
SortableElement((props: MultiValueProps<OptionType>) => { SortableElement((props: MultiValueProps<OptionType>) => {

View File

@ -246,15 +246,13 @@ const defaultSortComparator = (a: AntdLabeledValue, b: AntdLabeledValue) => {
* It creates a comparator to check for a specific property. * It creates a comparator to check for a specific property.
* Can be used with string and number property values. * Can be used with string and number property values.
* */ * */
export const propertyComparator = (property: string) => ( export const propertyComparator =
a: AntdLabeledValue, (property: string) => (a: AntdLabeledValue, b: AntdLabeledValue) => {
b: AntdLabeledValue, if (typeof a[property] === 'string' && typeof b[property] === 'string') {
) => { return a[property].localeCompare(b[property]);
if (typeof a[property] === 'string' && typeof b[property] === 'string') { }
return a[property].localeCompare(b[property]); return (a[property] as number) - (b[property] as number);
} };
return (a[property] as number) - (b[property] as number);
};
/** /**
* This component is a customized version of the Antdesign 4.X Select component * This component is a customized version of the Antdesign 4.X Select component
@ -300,9 +298,8 @@ const Select = ({
const shouldShowSearch = isAsync || allowNewOptions ? true : showSearch; const shouldShowSearch = isAsync || allowNewOptions ? true : showSearch;
const initialOptions = const initialOptions =
options && Array.isArray(options) ? options : EMPTY_OPTIONS; options && Array.isArray(options) ? options : EMPTY_OPTIONS;
const [selectOptions, setSelectOptions] = useState<OptionsType>( const [selectOptions, setSelectOptions] =
initialOptions, useState<OptionsType>(initialOptions);
);
const shouldUseChildrenOptions = !!selectOptions.find( const shouldUseChildrenOptions = !!selectOptions.find(
opt => opt?.customLabel, opt => opt?.customLabel,
); );

View File

@ -59,15 +59,14 @@ type MenuListPropsChildren<OptionType> =
| Component<OptionProps<OptionType>>[] | Component<OptionProps<OptionType>>[]
| ReactElement[]; | ReactElement[];
export type MenuListProps< export type MenuListProps<OptionType extends OptionTypeBase> =
OptionType extends OptionTypeBase MenuListComponentProps<OptionType> & {
> = MenuListComponentProps<OptionType> & { children: MenuListPropsChildren<OptionType>;
children: MenuListPropsChildren<OptionType>; // theme is not present with built-in @types/react-select, but is actually
// theme is not present with built-in @types/react-select, but is actually // available via CommonProps.
// available via CommonProps. theme?: ThemeConfig;
theme?: ThemeConfig; className?: string;
className?: string; } & WindowedMenuListProps;
} & WindowedMenuListProps;
const DEFAULT_OPTION_HEIGHT = 30; const DEFAULT_OPTION_HEIGHT = 30;

View File

@ -29,15 +29,13 @@ const { MenuList: DefaultMenuList } = defaultComponents;
export const DEFAULT_WINDOW_THRESHOLD = 100; export const DEFAULT_WINDOW_THRESHOLD = 100;
export type WindowedSelectProps< export type WindowedSelectProps<OptionType extends OptionTypeBase> =
OptionType extends OptionTypeBase SelectProps<OptionType> & {
> = SelectProps<OptionType> & { windowThreshold?: number;
windowThreshold?: number; } & WindowedMenuListProps['selectProps'];
} & WindowedMenuListProps['selectProps'];
export type WindowedSelectComponentType< export type WindowedSelectComponentType<OptionType extends OptionTypeBase> =
OptionType extends OptionTypeBase FunctionComponent<WindowedSelectProps<OptionType>>;
> = FunctionComponent<WindowedSelectProps<OptionType>>;
export function MenuList<OptionType extends OptionTypeBase>({ export function MenuList<OptionType extends OptionTypeBase>({
children, children,

View File

@ -71,12 +71,11 @@ export type ThemeConfig = {
colors: { colors: {
// add known colors // add known colors
[key in keyof typeof reactSelectColors]: string; [key in keyof typeof reactSelectColors]: string;
} & } & {
{ [key in keyof ReturnType<typeof colors>]: string;
[key in keyof ReturnType<typeof colors>]: string; } & {
} & { [key: string]: string; // any other colors
[key: string]: string; // any other colors };
};
spacing: Theme['spacing'] & { spacing: Theme['spacing'] & {
// line height and font size must be pixels for easier computation // line height and font size must be pixels for easier computation
// of option item height in WindowedMenuList // of option item height in WindowedMenuList
@ -89,21 +88,20 @@ export type ThemeConfig = {
export type PartialThemeConfig = RecursivePartial<ThemeConfig>; export type PartialThemeConfig = RecursivePartial<ThemeConfig>;
export const defaultTheme: ( export const defaultTheme: (theme: SupersetTheme) => PartialThemeConfig =
theme: SupersetTheme, theme => ({
) => PartialThemeConfig = theme => ({ borderRadius: theme.borderRadius,
borderRadius: theme.borderRadius, zIndex: 11,
zIndex: 11, colors: colors(theme),
colors: colors(theme), spacing: {
spacing: { baseUnit: 3,
baseUnit: 3, menuGutter: 0,
menuGutter: 0, controlHeight: 34,
controlHeight: 34, lineHeight: 19,
lineHeight: 19, fontSize: 14,
fontSize: 14, minWidth: '6.5em',
minWidth: '6.5em', },
}, });
});
// let styles accept serialized CSS, too // let styles accept serialized CSS, too
type CSSStyles = CSSProperties | SerializedStyles; type CSSStyles = CSSProperties | SerializedStyles;
@ -314,13 +312,8 @@ export type InputProps = ReactSelectInputProps & {
inputStyle?: object; inputStyle?: object;
}; };
const { const { ClearIndicator, DropdownIndicator, Option, Input, SelectContainer } =
ClearIndicator, defaultComponents as Required<DeepNonNullable<SelectComponentsType>>;
DropdownIndicator,
Option,
Input,
SelectContainer,
} = defaultComponents as Required<DeepNonNullable<SelectComponentsType>>;
export const DEFAULT_COMPONENTS: SelectComponentsType = { export const DEFAULT_COMPONENTS: SelectComponentsType = {
SelectContainer: ({ children, ...props }) => { SelectContainer: ({ children, ...props }) => {

View File

@ -60,41 +60,41 @@ export interface SetChartConfigFail {
type: typeof SET_CHART_CONFIG_FAIL; type: typeof SET_CHART_CONFIG_FAIL;
chartConfiguration: ChartConfiguration; chartConfiguration: ChartConfiguration;
} }
export const setChartConfiguration = ( export const setChartConfiguration =
chartConfiguration: ChartConfiguration, (chartConfiguration: ChartConfiguration) =>
) => async (dispatch: Dispatch, getState: () => any) => { async (dispatch: Dispatch, getState: () => any) => {
dispatch({
type: SET_CHART_CONFIG_BEGIN,
chartConfiguration,
});
const { id, metadata } = getState().dashboardInfo;
// TODO extract this out when makeApi supports url parameters
const updateDashboard = makeApi<
Partial<DashboardInfo>,
{ result: DashboardInfo }
>({
method: 'PUT',
endpoint: `/api/v1/dashboard/${id}`,
});
try {
const response = await updateDashboard({
json_metadata: JSON.stringify({
...metadata,
chart_configuration: chartConfiguration,
}),
});
dispatch(
dashboardInfoChanged({
metadata: JSON.parse(response.result.json_metadata),
}),
);
dispatch({ dispatch({
type: SET_CHART_CONFIG_COMPLETE, type: SET_CHART_CONFIG_BEGIN,
chartConfiguration, chartConfiguration,
}); });
} catch (err) { const { id, metadata } = getState().dashboardInfo;
dispatch({ type: SET_CHART_CONFIG_FAIL, chartConfiguration });
} // TODO extract this out when makeApi supports url parameters
}; const updateDashboard = makeApi<
Partial<DashboardInfo>,
{ result: DashboardInfo }
>({
method: 'PUT',
endpoint: `/api/v1/dashboard/${id}`,
});
try {
const response = await updateDashboard({
json_metadata: JSON.stringify({
...metadata,
chart_configuration: chartConfiguration,
}),
});
dispatch(
dashboardInfoChanged({
metadata: JSON.parse(response.result.json_metadata),
}),
);
dispatch({
type: SET_CHART_CONFIG_COMPLETE,
chartConfiguration,
});
} catch (err) {
dispatch({ type: SET_CHART_CONFIG_FAIL, chartConfiguration });
}
};

View File

@ -38,28 +38,29 @@ export const UPDATE_COMPONENTS = 'UPDATE_COMPONENTS';
// an additional setUnsavedChanges(true) action after the dispatch in the case // an additional setUnsavedChanges(true) action after the dispatch in the case
// that dashboardState.hasUnsavedChanges is false. // that dashboardState.hasUnsavedChanges is false.
function setUnsavedChangesAfterAction(action) { function setUnsavedChangesAfterAction(action) {
return (...args) => (dispatch, getState) => { return (...args) =>
const result = action(...args); (dispatch, getState) => {
if (typeof result === 'function') { const result = action(...args);
dispatch(result(dispatch, getState)); if (typeof result === 'function') {
} else { dispatch(result(dispatch, getState));
dispatch(result); } else {
} dispatch(result);
}
const isComponentLevelEvent = const isComponentLevelEvent =
result.type === UPDATE_COMPONENTS && result.type === UPDATE_COMPONENTS &&
result.payload && result.payload &&
result.payload.nextComponents; result.payload.nextComponents;
// trigger dashboardFilters state update if dashboard layout is changed. // trigger dashboardFilters state update if dashboard layout is changed.
if (!isComponentLevelEvent) { if (!isComponentLevelEvent) {
const components = getState().dashboardLayout.present; const components = getState().dashboardLayout.present;
dispatch(updateLayoutComponents(components)); dispatch(updateLayoutComponents(components));
} }
if (!getState().dashboardState.hasUnsavedChanges) { if (!getState().dashboardState.hasUnsavedChanges) {
dispatch(setUnsavedChanges(true)); dispatch(setUnsavedChanges(true));
} }
}; };
} }
export const updateComponents = setUnsavedChangesAfterAction( export const updateComponents = setUnsavedChangesAfterAction(

View File

@ -62,347 +62,352 @@ import getNativeFilterConfig from '../util/filterboxMigrationHelper';
export const HYDRATE_DASHBOARD = 'HYDRATE_DASHBOARD'; export const HYDRATE_DASHBOARD = 'HYDRATE_DASHBOARD';
export const hydrateDashboard = ( export const hydrateDashboard =
dashboardData, (
chartData, dashboardData,
filterboxMigrationState = FILTER_BOX_MIGRATION_STATES.NOOP, chartData,
) => (dispatch, getState) => { filterboxMigrationState = FILTER_BOX_MIGRATION_STATES.NOOP,
const { user, common } = getState(); ) =>
(dispatch, getState) => {
const { user, common } = getState();
const { metadata } = dashboardData; const { metadata } = dashboardData;
const regularUrlParams = extractUrlParams('regular'); const regularUrlParams = extractUrlParams('regular');
const reservedUrlParams = extractUrlParams('reserved'); const reservedUrlParams = extractUrlParams('reserved');
const editMode = reservedUrlParams.edit === 'true'; const editMode = reservedUrlParams.edit === 'true';
let preselectFilters = {}; let preselectFilters = {};
chartData.forEach(chart => { chartData.forEach(chart => {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
chart.slice_id = chart.form_data.slice_id; chart.slice_id = chart.form_data.slice_id;
});
try {
// allow request parameter overwrite dashboard metadata
preselectFilters =
getUrlParam(URL_PARAMS.preselectFilters) ||
JSON.parse(metadata.default_filters);
} catch (e) {
//
}
// Priming the color palette with user's label-color mapping provided in
// the dashboard's JSON metadata
if (metadata?.label_colors) {
const namespace = metadata.color_namespace;
const colorMap = isString(metadata.label_colors)
? JSON.parse(metadata.label_colors)
: metadata.label_colors;
const categoricalNamespace = CategoricalColorNamespace.getNamespace(
namespace,
);
Object.keys(colorMap).forEach(label => {
categoricalNamespace.setColor(label, colorMap[label]);
}); });
} try {
// allow request parameter overwrite dashboard metadata
// dashboard layout preselectFilters =
const { position_data } = dashboardData; getUrlParam(URL_PARAMS.preselectFilters) ||
// new dash: position_json could be {} or null JSON.parse(metadata.default_filters);
const layout = } catch (e) {
position_data && Object.keys(position_data).length > 0 //
? position_data
: getEmptyLayout();
// create a lookup to sync layout names with slice names
const chartIdToLayoutId = {};
Object.values(layout).forEach(layoutComponent => {
if (layoutComponent.type === CHART_TYPE) {
chartIdToLayoutId[layoutComponent.meta.chartId] = layoutComponent.id;
} }
});
// find root level chart container node for newly-added slices // Priming the color palette with user's label-color mapping provided in
const parentId = findFirstParentContainerId(layout); // the dashboard's JSON metadata
const parent = layout[parentId]; if (metadata?.label_colors) {
let newSlicesContainer; const namespace = metadata.color_namespace;
let newSlicesContainerWidth = 0; const colorMap = isString(metadata.label_colors)
? JSON.parse(metadata.label_colors)
: metadata.label_colors;
const categoricalNamespace =
CategoricalColorNamespace.getNamespace(namespace);
const filterScopes = metadata?.filter_scopes || {}; Object.keys(colorMap).forEach(label => {
categoricalNamespace.setColor(label, colorMap[label]);
});
}
const chartQueries = {}; // dashboard layout
const dashboardFilters = {}; const { position_data } = dashboardData;
const slices = {}; // new dash: position_json could be {} or null
const sliceIds = new Set(); const layout =
chartData.forEach(slice => { position_data && Object.keys(position_data).length > 0
const key = slice.slice_id; ? position_data
const form_data = { : getEmptyLayout();
...slice.form_data,
url_params: {
...slice.form_data.url_params,
...regularUrlParams,
},
};
chartQueries[key] = {
...chart,
id: key,
form_data,
formData: applyDefaultFormData(form_data),
};
slices[key] = { // create a lookup to sync layout names with slice names
slice_id: key, const chartIdToLayoutId = {};
slice_url: slice.slice_url, Object.values(layout).forEach(layoutComponent => {
slice_name: slice.slice_name, if (layoutComponent.type === CHART_TYPE) {
form_data: slice.form_data, chartIdToLayoutId[layoutComponent.meta.chartId] = layoutComponent.id;
viz_type: slice.form_data.viz_type, }
datasource: slice.form_data.datasource, });
description: slice.description,
description_markeddown: slice.description_markeddown,
owners: slice.owners,
modified: slice.modified,
changed_on: new Date(slice.changed_on).getTime(),
};
sliceIds.add(key); // find root level chart container node for newly-added slices
const parentId = findFirstParentContainerId(layout);
const parent = layout[parentId];
let newSlicesContainer;
let newSlicesContainerWidth = 0;
// if there are newly added slices from explore view, fill slices into 1 or more rows const filterScopes = metadata?.filter_scopes || {};
if (!chartIdToLayoutId[key] && layout[parentId]) {
if ( const chartQueries = {};
newSlicesContainerWidth === 0 || const dashboardFilters = {};
newSlicesContainerWidth + GRID_DEFAULT_CHART_WIDTH > GRID_COLUMN_COUNT const slices = {};
) { const sliceIds = new Set();
newSlicesContainer = newComponentFactory( chartData.forEach(slice => {
ROW_TYPE, const key = slice.slice_id;
(parent.parents || []).slice(), const form_data = {
...slice.form_data,
url_params: {
...slice.form_data.url_params,
...regularUrlParams,
},
};
chartQueries[key] = {
...chart,
id: key,
form_data,
formData: applyDefaultFormData(form_data),
};
slices[key] = {
slice_id: key,
slice_url: slice.slice_url,
slice_name: slice.slice_name,
form_data: slice.form_data,
viz_type: slice.form_data.viz_type,
datasource: slice.form_data.datasource,
description: slice.description,
description_markeddown: slice.description_markeddown,
owners: slice.owners,
modified: slice.modified,
changed_on: new Date(slice.changed_on).getTime(),
};
sliceIds.add(key);
// if there are newly added slices from explore view, fill slices into 1 or more rows
if (!chartIdToLayoutId[key] && layout[parentId]) {
if (
newSlicesContainerWidth === 0 ||
newSlicesContainerWidth + GRID_DEFAULT_CHART_WIDTH > GRID_COLUMN_COUNT
) {
newSlicesContainer = newComponentFactory(
ROW_TYPE,
(parent.parents || []).slice(),
);
layout[newSlicesContainer.id] = newSlicesContainer;
parent.children.push(newSlicesContainer.id);
newSlicesContainerWidth = 0;
}
const chartHolder = newComponentFactory(
CHART_TYPE,
{
chartId: slice.slice_id,
},
(newSlicesContainer.parents || []).slice(),
); );
layout[newSlicesContainer.id] = newSlicesContainer;
parent.children.push(newSlicesContainer.id); layout[chartHolder.id] = chartHolder;
newSlicesContainerWidth = 0; newSlicesContainer.children.push(chartHolder.id);
chartIdToLayoutId[chartHolder.meta.chartId] = chartHolder.id;
newSlicesContainerWidth += GRID_DEFAULT_CHART_WIDTH;
} }
const chartHolder = newComponentFactory( // build DashboardFilters for interactive filter features
CHART_TYPE, if (slice.form_data.viz_type === 'filter_box') {
{ const configs = getFilterConfigsFromFormdata(slice.form_data);
chartId: slice.slice_id, let { columns } = configs;
}, const { labels } = configs;
(newSlicesContainer.parents || []).slice(), if (preselectFilters[key]) {
); Object.keys(columns).forEach(col => {
if (preselectFilters[key][col]) {
columns = {
...columns,
[col]: preselectFilters[key][col],
};
}
});
}
layout[chartHolder.id] = chartHolder; const scopesByChartId = Object.keys(columns).reduce((map, column) => {
newSlicesContainer.children.push(chartHolder.id); const scopeSettings = {
chartIdToLayoutId[chartHolder.meta.chartId] = chartHolder.id; ...filterScopes[key],
newSlicesContainerWidth += GRID_DEFAULT_CHART_WIDTH; };
} const { scope, immune } = {
...DASHBOARD_FILTER_SCOPE_GLOBAL,
...scopeSettings[column],
};
// build DashboardFilters for interactive filter features return {
if (slice.form_data.viz_type === 'filter_box') { ...map,
const configs = getFilterConfigsFromFormdata(slice.form_data); [column]: {
let { columns } = configs; scope,
const { labels } = configs; immune,
if (preselectFilters[key]) {
Object.keys(columns).forEach(col => {
if (preselectFilters[key][col]) {
columns = {
...columns,
[col]: preselectFilters[key][col],
};
}
});
}
const scopesByChartId = Object.keys(columns).reduce((map, column) => {
const scopeSettings = {
...filterScopes[key],
};
const { scope, immune } = {
...DASHBOARD_FILTER_SCOPE_GLOBAL,
...scopeSettings[column],
};
return {
...map,
[column]: {
scope,
immune,
},
};
}, {});
const componentId = chartIdToLayoutId[key];
const directPathToFilter = (layout[componentId].parents || []).slice();
directPathToFilter.push(componentId);
if (
[
FILTER_BOX_MIGRATION_STATES.NOOP,
FILTER_BOX_MIGRATION_STATES.SNOOZED,
].includes(filterboxMigrationState)
) {
dashboardFilters[key] = {
...dashboardFilter,
chartId: key,
componentId,
datasourceId: slice.form_data.datasource,
filterName: slice.slice_name,
directPathToFilter,
columns,
labels,
scopes: scopesByChartId,
isDateFilter: Object.keys(columns).includes(TIME_RANGE),
};
}
}
// sync layout names with current slice names in case a slice was edited
// in explore since the layout was updated. name updates go through layout for undo/redo
// functionality and python updates slice names based on layout upon dashboard save
const layoutId = chartIdToLayoutId[key];
if (layoutId && layout[layoutId]) {
layout[layoutId].meta.sliceName = slice.slice_name;
}
});
buildActiveFilters({
dashboardFilters,
components: layout,
});
// store the header as a layout component so we can undo/redo changes
layout[DASHBOARD_HEADER_ID] = {
id: DASHBOARD_HEADER_ID,
type: DASHBOARD_HEADER_TYPE,
meta: {
text: dashboardData.dashboard_title,
},
};
const dashboardLayout = {
past: [],
present: layout,
future: [],
};
// find direct link component and path from root
const directLinkComponentId = getLocationHash();
let directPathToChild = [];
if (layout[directLinkComponentId]) {
directPathToChild = (layout[directLinkComponentId].parents || []).slice();
directPathToChild.push(directLinkComponentId);
}
// should convert filter_box to filter component?
let filterConfig = metadata?.native_filter_configuration || [];
if (filterboxMigrationState === FILTER_BOX_MIGRATION_STATES.REVIEWING) {
filterConfig = getNativeFilterConfig(
chartData,
filterScopes,
preselectFilters,
);
metadata.native_filter_configuration = filterConfig;
metadata.show_native_filters = true;
}
const nativeFilters = getInitialNativeFilterState({
filterConfig,
});
metadata.show_native_filters =
dashboardData?.metadata?.show_native_filters ??
(isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS) &&
[
FILTER_BOX_MIGRATION_STATES.CONVERTED,
FILTER_BOX_MIGRATION_STATES.REVIEWING,
FILTER_BOX_MIGRATION_STATES.NOOP,
].includes(filterboxMigrationState));
if (isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS)) {
// If user just added cross filter to dashboard it's not saving it scope on server,
// so we tweak it until user will update scope and will save it in server
Object.values(dashboardLayout.present).forEach(layoutItem => {
const chartId = layoutItem.meta?.chartId;
const behaviors =
(
getChartMetadataRegistry().get(
chartQueries[chartId]?.formData?.viz_type,
) ?? {}
)?.behaviors ?? [];
if (!metadata.chart_configuration) {
metadata.chart_configuration = {};
}
if (
behaviors.includes(Behavior.INTERACTIVE_CHART) &&
!metadata.chart_configuration[chartId]
) {
metadata.chart_configuration[chartId] = {
id: chartId,
crossFilters: {
scope: {
rootPath: [DASHBOARD_ROOT_ID],
excluded: [chartId], // By default it doesn't affects itself
}, },
}, };
}; }, {});
const componentId = chartIdToLayoutId[key];
const directPathToFilter = (layout[componentId].parents || []).slice();
directPathToFilter.push(componentId);
if (
[
FILTER_BOX_MIGRATION_STATES.NOOP,
FILTER_BOX_MIGRATION_STATES.SNOOZED,
].includes(filterboxMigrationState)
) {
dashboardFilters[key] = {
...dashboardFilter,
chartId: key,
componentId,
datasourceId: slice.form_data.datasource,
filterName: slice.slice_name,
directPathToFilter,
columns,
labels,
scopes: scopesByChartId,
isDateFilter: Object.keys(columns).includes(TIME_RANGE),
};
}
}
// sync layout names with current slice names in case a slice was edited
// in explore since the layout was updated. name updates go through layout for undo/redo
// functionality and python updates slice names based on layout upon dashboard save
const layoutId = chartIdToLayoutId[key];
if (layoutId && layout[layoutId]) {
layout[layoutId].meta.sliceName = slice.slice_name;
} }
}); });
} buildActiveFilters({
const { roles } = user;
const canEdit = canUserEditDashboard(dashboardData, user);
return dispatch({
type: HYDRATE_DASHBOARD,
data: {
sliceEntities: { ...initSliceEntities, slices, isLoading: false },
charts: chartQueries,
// read-only data
dashboardInfo: {
...dashboardData,
metadata,
userId: user.userId ? String(user.userId) : null, // legacy, please use state.user instead
dash_edit_perm: canEdit,
dash_save_perm: findPermission('can_save_dash', 'Superset', roles),
dash_share_perm: findPermission(
'can_share_dashboard',
'Superset',
roles,
),
superset_can_explore: findPermission('can_explore', 'Superset', roles),
superset_can_share: findPermission(
'can_share_chart',
'Superset',
roles,
),
superset_can_csv: findPermission('can_csv', 'Superset', roles),
slice_can_edit: findPermission('can_slice', 'Superset', roles),
common: {
// legacy, please use state.common instead
flash_messages: common.flash_messages,
conf: common.conf,
},
},
dashboardFilters, dashboardFilters,
nativeFilters, components: layout,
dashboardState: { });
preselectNativeFilters: getUrlParam(URL_PARAMS.nativeFilters),
sliceIds: Array.from(sliceIds), // store the header as a layout component so we can undo/redo changes
directPathToChild, layout[DASHBOARD_HEADER_ID] = {
directPathLastUpdated: Date.now(), id: DASHBOARD_HEADER_ID,
focusedFilterField: null, type: DASHBOARD_HEADER_TYPE,
expandedSlices: metadata?.expanded_slices || {}, meta: {
refreshFrequency: metadata?.refresh_frequency || 0, text: dashboardData.dashboard_title,
// dashboard viewers can set refresh frequency for the current visit,
// only persistent refreshFrequency will be saved to backend
shouldPersistRefreshFrequency: false,
css: dashboardData.css || '',
colorNamespace: metadata?.color_namespace || null,
colorScheme: metadata?.color_scheme || null,
editMode: canEdit && editMode,
isPublished: dashboardData.published,
hasUnsavedChanges: false,
maxUndoHistoryExceeded: false,
lastModifiedTime: dashboardData.changed_on,
isRefreshing: false,
activeTabs: [],
filterboxMigrationState,
}, },
dashboardLayout, };
},
}); const dashboardLayout = {
}; past: [],
present: layout,
future: [],
};
// find direct link component and path from root
const directLinkComponentId = getLocationHash();
let directPathToChild = [];
if (layout[directLinkComponentId]) {
directPathToChild = (layout[directLinkComponentId].parents || []).slice();
directPathToChild.push(directLinkComponentId);
}
// should convert filter_box to filter component?
let filterConfig = metadata?.native_filter_configuration || [];
if (filterboxMigrationState === FILTER_BOX_MIGRATION_STATES.REVIEWING) {
filterConfig = getNativeFilterConfig(
chartData,
filterScopes,
preselectFilters,
);
metadata.native_filter_configuration = filterConfig;
metadata.show_native_filters = true;
}
const nativeFilters = getInitialNativeFilterState({
filterConfig,
});
metadata.show_native_filters =
dashboardData?.metadata?.show_native_filters ??
(isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS) &&
[
FILTER_BOX_MIGRATION_STATES.CONVERTED,
FILTER_BOX_MIGRATION_STATES.REVIEWING,
FILTER_BOX_MIGRATION_STATES.NOOP,
].includes(filterboxMigrationState));
if (isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS)) {
// If user just added cross filter to dashboard it's not saving it scope on server,
// so we tweak it until user will update scope and will save it in server
Object.values(dashboardLayout.present).forEach(layoutItem => {
const chartId = layoutItem.meta?.chartId;
const behaviors =
(
getChartMetadataRegistry().get(
chartQueries[chartId]?.formData?.viz_type,
) ?? {}
)?.behaviors ?? [];
if (!metadata.chart_configuration) {
metadata.chart_configuration = {};
}
if (
behaviors.includes(Behavior.INTERACTIVE_CHART) &&
!metadata.chart_configuration[chartId]
) {
metadata.chart_configuration[chartId] = {
id: chartId,
crossFilters: {
scope: {
rootPath: [DASHBOARD_ROOT_ID],
excluded: [chartId], // By default it doesn't affects itself
},
},
};
}
});
}
const { roles } = user;
const canEdit = canUserEditDashboard(dashboardData, user);
return dispatch({
type: HYDRATE_DASHBOARD,
data: {
sliceEntities: { ...initSliceEntities, slices, isLoading: false },
charts: chartQueries,
// read-only data
dashboardInfo: {
...dashboardData,
metadata,
userId: user.userId ? String(user.userId) : null, // legacy, please use state.user instead
dash_edit_perm: canEdit,
dash_save_perm: findPermission('can_save_dash', 'Superset', roles),
dash_share_perm: findPermission(
'can_share_dashboard',
'Superset',
roles,
),
superset_can_explore: findPermission(
'can_explore',
'Superset',
roles,
),
superset_can_share: findPermission(
'can_share_chart',
'Superset',
roles,
),
superset_can_csv: findPermission('can_csv', 'Superset', roles),
slice_can_edit: findPermission('can_slice', 'Superset', roles),
common: {
// legacy, please use state.common instead
flash_messages: common.flash_messages,
conf: common.conf,
},
},
dashboardFilters,
nativeFilters,
dashboardState: {
preselectNativeFilters: getUrlParam(URL_PARAMS.nativeFilters),
sliceIds: Array.from(sliceIds),
directPathToChild,
directPathLastUpdated: Date.now(),
focusedFilterField: null,
expandedSlices: metadata?.expanded_slices || {},
refreshFrequency: metadata?.refresh_frequency || 0,
// dashboard viewers can set refresh frequency for the current visit,
// only persistent refreshFrequency will be saved to backend
shouldPersistRefreshFrequency: false,
css: dashboardData.css || '',
colorNamespace: metadata?.color_namespace || null,
colorScheme: metadata?.color_scheme || null,
editMode: canEdit && editMode,
isPublished: dashboardData.published,
hasUnsavedChanges: false,
maxUndoHistoryExceeded: false,
lastModifiedTime: dashboardData.changed_on,
isRefreshing: false,
activeTabs: [],
filterboxMigrationState,
},
dashboardLayout,
},
});
};

View File

@ -111,82 +111,84 @@ export interface UpdateFilterSetFail {
type: typeof UPDATE_FILTER_SET_FAIL; type: typeof UPDATE_FILTER_SET_FAIL;
} }
export const setFilterConfiguration = ( export const setFilterConfiguration =
filterConfig: FilterConfiguration, (filterConfig: FilterConfiguration) =>
) => async (dispatch: Dispatch, getState: () => any) => { async (dispatch: Dispatch, getState: () => any) => {
dispatch({ dispatch({
type: SET_FILTER_CONFIG_BEGIN, type: SET_FILTER_CONFIG_BEGIN,
filterConfig, filterConfig,
}); });
const { id, metadata } = getState().dashboardInfo; const { id, metadata } = getState().dashboardInfo;
const oldFilters = getState().nativeFilters?.filters; const oldFilters = getState().nativeFilters?.filters;
// TODO extract this out when makeApi supports url parameters // TODO extract this out when makeApi supports url parameters
const updateDashboard = makeApi< const updateDashboard = makeApi<
Partial<DashboardInfo>, Partial<DashboardInfo>,
{ result: DashboardInfo } { result: DashboardInfo }
>({ >({
method: 'PUT', method: 'PUT',
endpoint: `/api/v1/dashboard/${id}`, endpoint: `/api/v1/dashboard/${id}`,
}); });
const mergedFilterConfig = filterConfig.map(filter => { const mergedFilterConfig = filterConfig.map(filter => {
const oldFilter = oldFilters[filter.id]; const oldFilter = oldFilters[filter.id];
if (!oldFilter) { if (!oldFilter) {
return filter; return filter;
}
return { ...oldFilter, ...filter };
});
try {
const response = await updateDashboard({
json_metadata: JSON.stringify({
...metadata,
native_filter_configuration: mergedFilterConfig,
}),
});
dispatch(
dashboardInfoChanged({
metadata: JSON.parse(response.result.json_metadata),
}),
);
dispatch({
type: SET_FILTER_CONFIG_COMPLETE,
filterConfig: mergedFilterConfig,
});
dispatch(
setDataMaskForFilterConfigComplete(mergedFilterConfig, oldFilters),
);
} catch (err) {
dispatch({
type: SET_FILTER_CONFIG_FAIL,
filterConfig: mergedFilterConfig,
});
dispatch({
type: SET_DATA_MASK_FOR_FILTER_CONFIG_FAIL,
filterConfig: mergedFilterConfig,
});
} }
return { ...oldFilter, ...filter }; };
});
try { export const setInScopeStatusOfFilters =
const response = await updateDashboard({ (
json_metadata: JSON.stringify({ filterScopes: {
...metadata, filterId: string;
native_filter_configuration: mergedFilterConfig, chartsInScope: number[];
}), tabsInScope: string[];
}); }[],
dispatch( ) =>
dashboardInfoChanged({ async (dispatch: Dispatch, getState: () => any) => {
metadata: JSON.parse(response.result.json_metadata), const filters = getState().nativeFilters?.filters;
}), const filtersWithScopes = filterScopes.map(scope => ({
); ...filters[scope.filterId],
chartsInScope: scope.chartsInScope,
tabsInScope: scope.tabsInScope,
}));
dispatch({ dispatch({
type: SET_FILTER_CONFIG_COMPLETE, type: SET_IN_SCOPE_STATUS_OF_FILTERS,
filterConfig: mergedFilterConfig, filterConfig: filtersWithScopes,
}); });
dispatch( };
setDataMaskForFilterConfigComplete(mergedFilterConfig, oldFilters),
);
} catch (err) {
dispatch({
type: SET_FILTER_CONFIG_FAIL,
filterConfig: mergedFilterConfig,
});
dispatch({
type: SET_DATA_MASK_FOR_FILTER_CONFIG_FAIL,
filterConfig: mergedFilterConfig,
});
}
};
export const setInScopeStatusOfFilters = (
filterScopes: {
filterId: string;
chartsInScope: number[];
tabsInScope: string[];
}[],
) => async (dispatch: Dispatch, getState: () => any) => {
const filters = getState().nativeFilters?.filters;
const filtersWithScopes = filterScopes.map(scope => ({
...filters[scope.filterId],
chartsInScope: scope.chartsInScope,
tabsInScope: scope.tabsInScope,
}));
dispatch({
type: SET_IN_SCOPE_STATUS_OF_FILTERS,
filterConfig: filtersWithScopes,
});
};
type BootstrapData = { type BootstrapData = {
nativeFilters: { nativeFilters: {
@ -201,138 +203,134 @@ export interface SetBootstrapData {
data: BootstrapData; data: BootstrapData;
} }
export const getFilterSets = () => async ( export const getFilterSets =
dispatch: Dispatch, () => async (dispatch: Dispatch, getState: () => RootState) => {
getState: () => RootState, const dashboardId = getState().dashboardInfo.id;
) => { const fetchFilterSets = makeApi<
const dashboardId = getState().dashboardInfo.id; null,
const fetchFilterSets = makeApi< {
null, count: number;
{ ids: number[];
count: number; result: FilterSetFullData[];
ids: number[]; }
result: FilterSetFullData[]; >({
} method: 'GET',
>({ endpoint: `/api/v1/dashboard/${dashboardId}/filtersets`,
method: 'GET', });
endpoint: `/api/v1/dashboard/${dashboardId}/filtersets`,
});
dispatch({ dispatch({
type: SET_FILTER_SETS_BEGIN, type: SET_FILTER_SETS_BEGIN,
}); });
const response = await fetchFilterSets(null); const response = await fetchFilterSets(null);
dispatch({ dispatch({
type: SET_FILTER_SETS_COMPLETE, type: SET_FILTER_SETS_COMPLETE,
filterSets: response.ids.map((id, i) => ({ filterSets: response.ids.map((id, i) => ({
...response.result[i].params, ...response.result[i].params,
id, id,
name: response.result[i].name, name: response.result[i].name,
})), })),
}); });
};
export const createFilterSet = (filterSet: Omit<FilterSet, 'id'>) => async (
dispatch: Function,
getState: () => RootState,
) => {
const dashboardId = getState().dashboardInfo.id;
const postFilterSets = makeApi<
Partial<FilterSetFullData & { json_metadata: any }>,
{
count: number;
ids: number[];
result: FilterSetFullData[];
}
>({
method: 'POST',
endpoint: `/api/v1/dashboard/${dashboardId}/filtersets`,
});
dispatch({
type: CREATE_FILTER_SET_BEGIN,
});
const serverFilterSet: Omit<FilterSet, 'id' | 'name'> & { name?: string } = {
...filterSet,
}; };
delete serverFilterSet.name; export const createFilterSet =
(filterSet: Omit<FilterSet, 'id'>) =>
async (dispatch: Function, getState: () => RootState) => {
const dashboardId = getState().dashboardInfo.id;
const postFilterSets = makeApi<
Partial<FilterSetFullData & { json_metadata: any }>,
{
count: number;
ids: number[];
result: FilterSetFullData[];
}
>({
method: 'POST',
endpoint: `/api/v1/dashboard/${dashboardId}/filtersets`,
});
await postFilterSets({ dispatch({
name: filterSet.name, type: CREATE_FILTER_SET_BEGIN,
owner_type: 'Dashboard', });
owner_id: dashboardId,
json_metadata: JSON.stringify(serverFilterSet),
});
dispatch({ const serverFilterSet: Omit<FilterSet, 'id' | 'name'> & { name?: string } =
type: CREATE_FILTER_SET_COMPLETE, {
}); ...filterSet,
dispatch(getFilterSets()); };
};
export const updateFilterSet = (filterSet: FilterSet) => async ( delete serverFilterSet.name;
dispatch: Function,
getState: () => RootState,
) => {
const dashboardId = getState().dashboardInfo.id;
const postFilterSets = makeApi<
Partial<FilterSetFullData & { json_metadata: any }>,
{}
>({
method: 'PUT',
endpoint: `/api/v1/dashboard/${dashboardId}/filtersets/${filterSet.id}`,
});
dispatch({ await postFilterSets({
type: UPDATE_FILTER_SET_BEGIN, name: filterSet.name,
}); owner_type: 'Dashboard',
owner_id: dashboardId,
json_metadata: JSON.stringify(serverFilterSet),
});
const serverFilterSet: Omit<FilterSet, 'id' | 'name'> & { dispatch({
name?: string; type: CREATE_FILTER_SET_COMPLETE,
id?: number; });
} = { dispatch(getFilterSets());
...filterSet,
}; };
delete serverFilterSet.id; export const updateFilterSet =
delete serverFilterSet.name; (filterSet: FilterSet) =>
async (dispatch: Function, getState: () => RootState) => {
const dashboardId = getState().dashboardInfo.id;
const postFilterSets = makeApi<
Partial<FilterSetFullData & { json_metadata: any }>,
{}
>({
method: 'PUT',
endpoint: `/api/v1/dashboard/${dashboardId}/filtersets/${filterSet.id}`,
});
await postFilterSets({ dispatch({
name: filterSet.name, type: UPDATE_FILTER_SET_BEGIN,
json_metadata: JSON.stringify(serverFilterSet), });
});
dispatch({ const serverFilterSet: Omit<FilterSet, 'id' | 'name'> & {
type: UPDATE_FILTER_SET_COMPLETE, name?: string;
}); id?: number;
dispatch(getFilterSets()); } = {
}; ...filterSet,
};
export const deleteFilterSet = (filterSetId: number) => async ( delete serverFilterSet.id;
dispatch: Function, delete serverFilterSet.name;
getState: () => RootState,
) => {
const dashboardId = getState().dashboardInfo.id;
const deleteFilterSets = makeApi<{}, {}>({
method: 'DELETE',
endpoint: `/api/v1/dashboard/${dashboardId}/filtersets/${filterSetId}`,
});
dispatch({ await postFilterSets({
type: DELETE_FILTER_SET_BEGIN, name: filterSet.name,
}); json_metadata: JSON.stringify(serverFilterSet),
});
await deleteFilterSets({}); dispatch({
type: UPDATE_FILTER_SET_COMPLETE,
});
dispatch(getFilterSets());
};
dispatch({ export const deleteFilterSet =
type: DELETE_FILTER_SET_COMPLETE, (filterSetId: number) =>
}); async (dispatch: Function, getState: () => RootState) => {
dispatch(getFilterSets()); const dashboardId = getState().dashboardInfo.id;
}; const deleteFilterSets = makeApi<{}, {}>({
method: 'DELETE',
endpoint: `/api/v1/dashboard/${dashboardId}/filtersets/${filterSetId}`,
});
dispatch({
type: DELETE_FILTER_SET_BEGIN,
});
await deleteFilterSets({});
dispatch({
type: DELETE_FILTER_SET_COMPLETE,
});
dispatch(getFilterSets());
};
export const SET_FOCUSED_NATIVE_FILTER = 'SET_FOCUSED_NATIVE_FILTER'; export const SET_FOCUSED_NATIVE_FILTER = 'SET_FOCUSED_NATIVE_FILTER';
export interface SetFocusedNativeFilter { export interface SetFocusedNativeFilter {

View File

@ -56,45 +56,40 @@ interface FilterBoxMigrationModalProps {
hideFooter: boolean; hideFooter: boolean;
} }
const FilterBoxMigrationModal: FunctionComponent<FilterBoxMigrationModalProps> = ({ const FilterBoxMigrationModal: FunctionComponent<FilterBoxMigrationModalProps> =
onClickReview, ({ onClickReview, onClickSnooze, onHide, show, hideFooter = false }) => (
onClickSnooze, <StyledFilterBoxMigrationModal
onHide, show={show}
show, onHide={onHide}
hideFooter = false, title={t('Ready to review filters in this dashboard?')}
}) => ( hideFooter={hideFooter}
<StyledFilterBoxMigrationModal footer={
show={show} <>
onHide={onHide} <Button buttonSize="small" onClick={onClickSnooze}>
title={t('Ready to review filters in this dashboard?')} {t('Remind me in 24 hours')}
hideFooter={hideFooter} </Button>
footer={ <Button buttonSize="small" onClick={onHide}>
<> {t('Cancel')}
<Button buttonSize="small" onClick={onClickSnooze}> </Button>
{t('Remind me in 24 hours')} <Button
</Button> buttonSize="small"
<Button buttonSize="small" onClick={onHide}> buttonStyle="primary"
{t('Cancel')} onClick={onClickReview}
</Button> >
<Button {t('Start Review')}
buttonSize="small" </Button>
buttonStyle="primary" </>
onClick={onClickReview} }
> responsive
{t('Start Review')} >
</Button> <div>
</> {t(
} 'filter_box will be deprecated ' +
responsive 'in a future version of Superset. ' +
> 'Please replace filter_box by dashboard filter components.',
<div> )}
{t( </div>
'filter_box will be deprecated ' + </StyledFilterBoxMigrationModal>
'in a future version of Superset. ' + );
'Please replace filter_box by dashboard filter components.',
)}
</div>
</StyledFilterBoxMigrationModal>
);
export default FilterBoxMigrationModal; export default FilterBoxMigrationModal;

View File

@ -611,10 +611,8 @@ class Header extends React.PureComponent {
onHide={this.hidePropertiesModal} onHide={this.hidePropertiesModal}
colorScheme={this.props.colorScheme} colorScheme={this.props.colorScheme}
onSubmit={updates => { onSubmit={updates => {
const { const { dashboardInfoChanged, dashboardTitleChanged } =
dashboardInfoChanged, this.props;
dashboardTitleChanged,
} = this.props;
dashboardInfoChanged({ dashboardInfoChanged({
slug: updates.slug, slug: updates.slug,
metadata: JSON.parse(updates.jsonMetadata), metadata: JSON.parse(updates.jsonMetadata),

View File

@ -85,24 +85,26 @@ const handleErrorResponse = async response => {
}); });
}; };
const loadAccessOptions = accessType => (input = '') => { const loadAccessOptions =
const query = rison.encode({ filter: input }); accessType =>
return SupersetClient.get({ (input = '') => {
endpoint: `/api/v1/dashboard/related/${accessType}?q=${query}`, const query = rison.encode({ filter: input });
}).then( return SupersetClient.get({
response => ({ endpoint: `/api/v1/dashboard/related/${accessType}?q=${query}`,
data: response.json.result.map(item => ({ }).then(
value: item.value, response => ({
label: item.text, data: response.json.result.map(item => ({
})), value: item.value,
totalCount: response.json.count, label: item.text,
}), })),
badResponse => { totalCount: response.json.count,
handleErrorResponse(badResponse); }),
return []; badResponse => {
}, handleErrorResponse(badResponse);
); return [];
}; },
);
};
const loadOwners = loadAccessOptions('owners'); const loadOwners = loadAccessOptions('owners');
const loadRoles = loadAccessOptions('roles'); const loadRoles = loadAccessOptions('roles');

View File

@ -30,7 +30,8 @@ const defaultProps = {
}; };
test('renders with unpublished status and readonly permissions', async () => { test('renders with unpublished status and readonly permissions', async () => {
const tooltip = /This dashboard is not published which means it will not show up in the list of dashboards/; const tooltip =
/This dashboard is not published which means it will not show up in the list of dashboards/;
render(<PublishedStatus {...defaultProps} />); render(<PublishedStatus {...defaultProps} />);
expect(screen.getByText('Draft')).toBeInTheDocument(); expect(screen.getByText('Draft')).toBeInTheDocument();
userEvent.hover(screen.getByText('Draft')); userEvent.hover(screen.getByText('Draft'));
@ -38,7 +39,8 @@ test('renders with unpublished status and readonly permissions', async () => {
}); });
test('renders with unpublished status and write permissions', async () => { test('renders with unpublished status and write permissions', async () => {
const tooltip = /This dashboard is not published, it will not show up in the list of dashboards/; const tooltip =
/This dashboard is not published, it will not show up in the list of dashboards/;
const savePublished = jest.fn(); const savePublished = jest.fn();
render( render(
<PublishedStatus <PublishedStatus

View File

@ -179,11 +179,8 @@ export default class FilterScopeSelector extends React.PureComponent {
} }
onCheckFilterScope(checked = []) { onCheckFilterScope(checked = []) {
const { const { activeFilterField, filterScopeMap, checkedFilterFields } =
activeFilterField, this.state;
filterScopeMap,
checkedFilterFields,
} = this.state;
const key = getKeyForFilterScopeTree({ const key = getKeyForFilterScopeTree({
activeFilterField, activeFilterField,
@ -213,11 +210,8 @@ export default class FilterScopeSelector extends React.PureComponent {
} }
onExpandFilterScope(expanded = []) { onExpandFilterScope(expanded = []) {
const { const { activeFilterField, checkedFilterFields, filterScopeMap } =
activeFilterField, this.state;
checkedFilterFields,
filterScopeMap,
} = this.state;
const key = getKeyForFilterScopeTree({ const key = getKeyForFilterScopeTree({
activeFilterField, activeFilterField,
checkedFilterFields, checkedFilterFields,
@ -347,11 +341,8 @@ export default class FilterScopeSelector extends React.PureComponent {
// Reset nodes back to unfiltered state // Reset nodes back to unfiltered state
if (!this.state.searchText) { if (!this.state.searchText) {
this.setState(prevState => { this.setState(prevState => {
const { const { activeFilterField, checkedFilterFields, filterScopeMap } =
activeFilterField, prevState;
checkedFilterFields,
filterScopeMap,
} = prevState;
const key = getKeyForFilterScopeTree({ const key = getKeyForFilterScopeTree({
activeFilterField, activeFilterField,
checkedFilterFields, checkedFilterFields,
@ -370,11 +361,8 @@ export default class FilterScopeSelector extends React.PureComponent {
}); });
} else { } else {
const updater = prevState => { const updater = prevState => {
const { const { activeFilterField, checkedFilterFields, filterScopeMap } =
activeFilterField, prevState;
checkedFilterFields,
filterScopeMap,
} = prevState;
const key = getKeyForFilterScopeTree({ const key = getKeyForFilterScopeTree({
activeFilterField, activeFilterField,
checkedFilterFields, checkedFilterFields,

View File

@ -167,10 +167,8 @@ class ChartHolder extends React.Component {
static getDerivedStateFromProps(props, state) { static getDerivedStateFromProps(props, state) {
const { component, directPathToChild, directPathLastUpdated } = props; const { component, directPathToChild, directPathLastUpdated } = props;
const { const { label: columnName, chart: chartComponentId } =
label: columnName, getChartAndLabelComponentIdFromPath(directPathToChild);
chart: chartComponentId,
} = getChartAndLabelComponentIdFromPath(directPathToChild);
if ( if (
directPathLastUpdated !== state.directPathLastUpdated && directPathLastUpdated !== state.directPathLastUpdated &&

View File

@ -83,8 +83,9 @@ describe('ChartHolder', () => {
it('should render full size', async () => { it('should render full size', async () => {
renderWrapper(); renderWrapper();
const chart = (screen.getByTestId('slice-container') const chart = (
.firstChild as HTMLElement).style; screen.getByTestId('slice-container').firstChild as HTMLElement
).style;
await waitFor(() => expect(chart?.width).toBe('992px')); await waitFor(() => expect(chart?.width).toBe('992px'));
expect(chart?.height).toBe('714px'); expect(chart?.height).toBe('714px');

View File

@ -110,13 +110,8 @@ class Markdown extends React.PureComponent {
} }
static getDerivedStateFromProps(nextProps, state) { static getDerivedStateFromProps(nextProps, state) {
const { const { hasError, editorMode, markdownSource, undoLength, redoLength } =
hasError, state;
editorMode,
markdownSource,
undoLength,
redoLength,
} = state;
const { const {
component: nextComponent, component: nextComponent,
undoLength: nextUndoLength, undoLength: nextUndoLength,

View File

@ -135,10 +135,10 @@ const CascadePopover: React.FC<CascadePopoverProps> = ({
}; };
const allFilters = getAllFilters(filter); const allFilters = getAllFilters(filter);
const activeFilters = useMemo(() => getActiveChildren(filter) || [filter], [ const activeFilters = useMemo(
filter, () => getActiveChildren(filter) || [filter],
getActiveChildren, [filter, getActiveChildren],
]); );
useEffect(() => { useEffect(() => {
const focusedFilterId = currentPathToChild?.[0]; const focusedFilterId = currentPathToChild?.[0];

View File

@ -73,9 +73,8 @@ const FilterControls: FC<FilterControlsProps> = ({
}, [filterValues, dataMaskSelected]); }, [filterValues, dataMaskSelected]);
const cascadeFilterIds = new Set(cascadeFilters.map(item => item.id)); const cascadeFilterIds = new Set(cascadeFilters.map(item => item.id));
const [filtersInScope, filtersOutOfScope] = useSelectFiltersInScope( const [filtersInScope, filtersOutOfScope] =
cascadeFilters, useSelectFiltersInScope(cascadeFilters);
);
const dashboardHasTabs = useDashboardHasTabs(); const dashboardHasTabs = useDashboardHasTabs();
const showCollapsePanel = dashboardHasTabs && cascadeFilters.length > 0; const showCollapsePanel = dashboardHasTabs && cascadeFilters.length > 0;

View File

@ -213,9 +213,10 @@ const FilterValue: React.FC<FilterProps> = ({
() => dispatchFocusAction(dispatch, id), () => dispatchFocusAction(dispatch, id),
[dispatch, id], [dispatch, id],
); );
const unsetFocusedFilter = useCallback(() => dispatchFocusAction(dispatch), [ const unsetFocusedFilter = useCallback(
dispatch, () => dispatchFocusAction(dispatch),
]); [dispatch],
);
const hooks = useMemo( const hooks = useMemo(
() => ({ setDataMask, setFocusedFilter, unsetFocusedFilter }), () => ({ setDataMask, setFocusedFilter, unsetFocusedFilter }),

View File

@ -151,9 +151,8 @@ const FilterBar: React.FC<FiltersBarProps> = ({
const history = useHistory(); const history = useHistory();
const dataMaskApplied: DataMaskStateWithId = useNativeFiltersDataMask(); const dataMaskApplied: DataMaskStateWithId = useNativeFiltersDataMask();
const [editFilterSetId, setEditFilterSetId] = useState<number | null>(null); const [editFilterSetId, setEditFilterSetId] = useState<number | null>(null);
const [dataMaskSelected, setDataMaskSelected] = useImmer<DataMaskStateWithId>( const [dataMaskSelected, setDataMaskSelected] =
dataMaskApplied, useImmer<DataMaskStateWithId>(dataMaskApplied);
);
const dispatch = useDispatch(); const dispatch = useDispatch();
const filterSets = useFilterSets(); const filterSets = useFilterSets();
const filterSetFilterValues = Object.values(filterSets); const filterSetFilterValues = Object.values(filterSets);
@ -267,9 +266,10 @@ const FilterBar: React.FC<FiltersBarProps> = ({
}); });
}, [dataMaskSelected, dispatch]); }, [dataMaskSelected, dispatch]);
const openFiltersBar = useCallback(() => toggleFiltersBar(true), [ const openFiltersBar = useCallback(
toggleFiltersBar, () => toggleFiltersBar(true),
]); [toggleFiltersBar],
);
useFilterUpdates(dataMaskSelected, setDataMaskSelected); useFilterUpdates(dataMaskSelected, setDataMaskSelected);
const isApplyDisabled = checkIsApplyDisabled( const isApplyDisabled = checkIsApplyDisabled(

View File

@ -27,9 +27,9 @@ export enum TabIds {
FilterSets = 'filterSets', FilterSets = 'filterSets',
} }
export function mapParentFiltersToChildren( export function mapParentFiltersToChildren(filters: Filter[]): {
filters: Filter[], [id: string]: Filter[];
): { [id: string]: Filter[] } { } {
const cascadeChildren = {}; const cascadeChildren = {};
filters.forEach(filter => { filters.forEach(filter => {
const [parentId] = filter.cascadeParentIds || []; const [parentId] = filter.cascadeParentIds || [];

View File

@ -104,9 +104,8 @@ const FilterTitlePane: React.FC<Props> = ({
setTimeout(() => { setTimeout(() => {
const element = document.getElementById('native-filters-tabs'); const element = document.getElementById('native-filters-tabs');
if (element) { if (element) {
const navList = element.getElementsByClassName( const navList =
'ant-tabs-nav-list', element.getElementsByClassName('ant-tabs-nav-list')[0];
)[0];
navList.scrollTop = navList.scrollHeight; navList.scrollTop = navList.scrollHeight;
} }
}, 0); }, 0);

View File

@ -77,8 +77,8 @@ export function ColumnSelect({
[columns, filterValues], [columns, filterValues],
); );
const currentFilterType = form.getFieldValue('filters')?.[filterId] const currentFilterType =
.filterType; form.getFieldValue('filters')?.[filterId].filterType;
const currentColumn = useMemo( const currentColumn = useMemo(
() => columns?.find(column => column.column_name === value), () => columns?.find(column => column.column_name === value),
[columns, value], [columns, value],

View File

@ -390,9 +390,9 @@ const FiltersConfigForm = (
return currentDataset ? hasTemporalColumns(currentDataset) : true; return currentDataset ? hasTemporalColumns(currentDataset) : true;
}, [formFilter?.dataset?.value, loadedDatasets]); }, [formFilter?.dataset?.value, loadedDatasets]);
// @ts-ignore const hasDataset =
const hasDataset = !!nativeFilterItems[formFilter?.filterType]?.value // @ts-ignore
?.datasourceCount; !!nativeFilterItems[formFilter?.filterType]?.value?.datasourceCount;
const datasetId = const datasetId =
formFilter?.dataset?.value ?? formFilter?.dataset?.value ??
@ -514,12 +514,8 @@ const FiltersConfigForm = (
...formFilter, ...formFilter,
}); });
const [ const [hasDefaultValue, isRequired, defaultValueTooltip, setHasDefaultValue] =
hasDefaultValue, useDefaultValue(formFilter, filterToEdit);
isRequired,
defaultValueTooltip,
setHasDefaultValue,
] = useDefaultValue(formFilter, filterToEdit);
const showDataset = const showDataset =
!datasetId || datasetDetails || formFilter?.dataset?.label; !datasetId || datasetDetails || formFilter?.dataset?.label;

View File

@ -118,53 +118,55 @@ export const validateForm = async (
} }
}; };
export const createHandleSave = ( export const createHandleSave =
filterConfigMap: Record<string, Filter>, (
filterIds: string[], filterConfigMap: Record<string, Filter>,
removedFilters: Record<string, FilterRemoval>, filterIds: string[],
saveForm: Function, removedFilters: Record<string, FilterRemoval>,
values: NativeFiltersForm, saveForm: Function,
) => async () => { values: NativeFiltersForm,
const newFilterConfig: FilterConfiguration = filterIds ) =>
.filter(id => !removedFilters[id]) async () => {
.map(id => { const newFilterConfig: FilterConfiguration = filterIds
// create a filter config object from the form inputs .filter(id => !removedFilters[id])
const formInputs = values.filters?.[id]; .map(id => {
// if user didn't open a filter, return the original config // create a filter config object from the form inputs
if (!formInputs) return filterConfigMap[id]; const formInputs = values.filters?.[id];
const target: Partial<Target> = {}; // if user didn't open a filter, return the original config
if (formInputs.dataset) { if (!formInputs) return filterConfigMap[id];
target.datasetId = formInputs.dataset.value; const target: Partial<Target> = {};
} if (formInputs.dataset) {
if (formInputs.dataset && formInputs.column) { target.datasetId = formInputs.dataset.value;
target.column = { name: formInputs.column }; }
} if (formInputs.dataset && formInputs.column) {
return { target.column = { name: formInputs.column };
id, }
adhoc_filters: formInputs.adhoc_filters, return {
time_range: formInputs.time_range, id,
controlValues: formInputs.controlValues ?? {}, adhoc_filters: formInputs.adhoc_filters,
granularity_sqla: formInputs.granularity_sqla, time_range: formInputs.time_range,
requiredFirst: Object.values(formInputs.requiredFirst ?? {}).find( controlValues: formInputs.controlValues ?? {},
rf => rf, granularity_sqla: formInputs.granularity_sqla,
), requiredFirst: Object.values(formInputs.requiredFirst ?? {}).find(
name: formInputs.name, rf => rf,
filterType: formInputs.filterType, ),
// for now there will only ever be one target name: formInputs.name,
targets: [target], filterType: formInputs.filterType,
defaultDataMask: formInputs.defaultDataMask ?? getInitialDataMask(), // for now there will only ever be one target
cascadeParentIds: formInputs.parentFilter targets: [target],
? [formInputs.parentFilter.value] defaultDataMask: formInputs.defaultDataMask ?? getInitialDataMask(),
: [], cascadeParentIds: formInputs.parentFilter
scope: formInputs.scope, ? [formInputs.parentFilter.value]
sortMetric: formInputs.sortMetric, : [],
type: formInputs.type, scope: formInputs.scope,
description: (formInputs.description || '').trim(), sortMetric: formInputs.sortMetric,
}; type: formInputs.type,
}); description: (formInputs.description || '').trim(),
};
});
await saveForm(newFilterConfig); await saveForm(newFilterConfig);
}; };
export function buildFilterGroup(nodes: FilterHierarchyNode[]) { export function buildFilterGroup(nodes: FilterHierarchyNode[]) {
const buildGroup = ( const buildGroup = (
elementId: string, elementId: string,
@ -208,84 +210,88 @@ export function buildFilterGroup(nodes: FilterHierarchyNode[]) {
} }
return group; return group;
} }
export const createHandleTabEdit = ( export const createHandleTabEdit =
setRemovedFilters: ( (
value: setRemovedFilters: (
| (( value:
prevState: Record<string, FilterRemoval>, | ((
) => Record<string, FilterRemoval>) prevState: Record<string, FilterRemoval>,
| Record<string, FilterRemoval>, ) => Record<string, FilterRemoval>)
) => void, | Record<string, FilterRemoval>,
setSaveAlertVisible: Function, ) => void,
setOrderedFilters: ( setSaveAlertVisible: Function,
val: string[][] | ((prevState: string[][]) => string[][]), setOrderedFilters: (
) => void, val: string[][] | ((prevState: string[][]) => string[][]),
setFilterHierarchy: ( ) => void,
state: FilterHierarchy | ((prevState: FilterHierarchy) => FilterHierarchy), setFilterHierarchy: (
) => void, state:
addFilter: Function, | FilterHierarchy
filterHierarchy: FilterHierarchy, | ((prevState: FilterHierarchy) => FilterHierarchy),
) => (filterId: string, action: 'add' | 'remove') => { ) => void,
const completeFilterRemoval = (filterId: string) => { addFilter: Function,
const buildNewFilterHierarchy = (hierarchy: FilterHierarchy) => filterHierarchy: FilterHierarchy,
hierarchy ) =>
.filter(nativeFilter => nativeFilter.id !== filterId) (filterId: string, action: 'add' | 'remove') => {
.map(nativeFilter => { const completeFilterRemoval = (filterId: string) => {
const didRemoveParent = nativeFilter.parentId === filterId; const buildNewFilterHierarchy = (hierarchy: FilterHierarchy) =>
return didRemoveParent hierarchy
? { ...nativeFilter, parentId: null } .filter(nativeFilter => nativeFilter.id !== filterId)
: nativeFilter; .map(nativeFilter => {
}); const didRemoveParent = nativeFilter.parentId === filterId;
// the filter state will actually stick around in the form, return didRemoveParent
// and the filterConfig/newFilterIds, but we use removedFilters ? { ...nativeFilter, parentId: null }
// to mark it as removed. : nativeFilter;
setRemovedFilters(removedFilters => ({ });
...removedFilters, // the filter state will actually stick around in the form,
[filterId]: { isPending: false }, // and the filterConfig/newFilterIds, but we use removedFilters
})); // to mark it as removed.
// Remove the filter from the side tab and de-associate children setRemovedFilters(removedFilters => ({
// in case we removed a parent. ...removedFilters,
setFilterHierarchy(prevFilterHierarchy => [filterId]: { isPending: false },
buildNewFilterHierarchy(prevFilterHierarchy), }));
); // Remove the filter from the side tab and de-associate children
setOrderedFilters((orderedFilters: string[][]) => { // in case we removed a parent.
const newOrder = []; setFilterHierarchy(prevFilterHierarchy =>
for (let index = 0; index < orderedFilters.length; index += 1) { buildNewFilterHierarchy(prevFilterHierarchy),
const doesGroupContainDeletedFilter = );
orderedFilters[index].findIndex(id => id === filterId) >= 0; setOrderedFilters((orderedFilters: string[][]) => {
// Rebuild just the group that contains deleted filter ID. const newOrder = [];
if (doesGroupContainDeletedFilter) { for (let index = 0; index < orderedFilters.length; index += 1) {
const newGroups = buildFilterGroup( const doesGroupContainDeletedFilter =
buildNewFilterHierarchy( orderedFilters[index].findIndex(id => id === filterId) >= 0;
filterHierarchy.filter(filter => // Rebuild just the group that contains deleted filter ID.
orderedFilters[index].includes(filter.id), if (doesGroupContainDeletedFilter) {
const newGroups = buildFilterGroup(
buildNewFilterHierarchy(
filterHierarchy.filter(filter =>
orderedFilters[index].includes(filter.id),
),
), ),
), );
); newGroups.forEach(group => newOrder.push(group));
newGroups.forEach(group => newOrder.push(group)); } else {
} else { newOrder.push(orderedFilters[index]);
newOrder.push(orderedFilters[index]); }
} }
} return newOrder;
return newOrder; });
}); };
};
if (action === 'remove') { if (action === 'remove') {
// first set up the timer to completely remove it // first set up the timer to completely remove it
const timerId = window.setTimeout(() => { const timerId = window.setTimeout(() => {
completeFilterRemoval(filterId); completeFilterRemoval(filterId);
}, REMOVAL_DELAY_SECS * 1000); }, REMOVAL_DELAY_SECS * 1000);
// mark the filter state as "removal in progress" // mark the filter state as "removal in progress"
setRemovedFilters(removedFilters => ({ setRemovedFilters(removedFilters => ({
...removedFilters, ...removedFilters,
[filterId]: { isPending: true, timerId }, [filterId]: { isPending: true, timerId },
})); }));
setSaveAlertVisible(false); setSaveAlertVisible(false);
} else if (action === 'add') { } else if (action === 'add') {
addFilter(); addFilter();
} }
}; };
export const NATIVE_FILTER_PREFIX = 'NATIVE_FILTER-'; export const NATIVE_FILTER_PREFIX = 'NATIVE_FILTER-';
export const generateFilterId = () => export const generateFilterId = () =>

View File

@ -81,7 +81,8 @@ function mapStateToProps({
).text, ).text,
expandedSlices: dashboardState.expandedSlices, expandedSlices: dashboardState.expandedSlices,
refreshFrequency: dashboardState.refreshFrequency, refreshFrequency: dashboardState.refreshFrequency,
shouldPersistRefreshFrequency: !!dashboardState.shouldPersistRefreshFrequency, shouldPersistRefreshFrequency:
!!dashboardState.shouldPersistRefreshFrequency,
customCss: dashboardState.css, customCss: dashboardState.css,
colorNamespace: dashboardState.colorNamespace, colorNamespace: dashboardState.colorNamespace,
colorScheme: dashboardState.colorScheme, colorScheme: dashboardState.colorScheme,

View File

@ -72,15 +72,12 @@ const DashboardPage: FC = () => {
); );
const { addDangerToast } = useToasts(); const { addDangerToast } = useToasts();
const { idOrSlug } = useParams<{ idOrSlug: string }>(); const { idOrSlug } = useParams<{ idOrSlug: string }>();
const { result: dashboard, error: dashboardApiError } = useDashboard( const { result: dashboard, error: dashboardApiError } =
idOrSlug, useDashboard(idOrSlug);
); const { result: charts, error: chartsApiError } =
const { result: charts, error: chartsApiError } = useDashboardCharts( useDashboardCharts(idOrSlug);
idOrSlug, const { result: datasets, error: datasetsApiError } =
); useDashboardDatasets(idOrSlug);
const { result: datasets, error: datasetsApiError } = useDashboardDatasets(
idOrSlug,
);
const isDashboardHydrated = useRef(false); const isDashboardHydrated = useRef(false);
const error = dashboardApiError || chartsApiError; const error = dashboardApiError || chartsApiError;

View File

@ -49,9 +49,9 @@ export function isFilterBox(chartId) {
export function getAppliedFilterValues(chartId) { export function getAppliedFilterValues(chartId) {
// use cached data if possible // use cached data if possible
if (!(chartId in appliedFilterValuesByChart)) { if (!(chartId in appliedFilterValuesByChart)) {
const applicableFilters = Object.entries( const applicableFilters = Object.entries(activeFilters).filter(
activeFilters, ([, { scope: chartIds }]) => chartIds.includes(chartId),
).filter(([, { scope: chartIds }]) => chartIds.includes(chartId)); );
appliedFilterValuesByChart[chartId] = flow( appliedFilterValuesByChart[chartId] = flow(
keyBy( keyBy(
([filterKey]) => getChartIdAndColumnFromFilterKey(filterKey).column, ([filterKey]) => getChartIdAndColumnFromFilterKey(filterKey).column,

View File

@ -103,15 +103,18 @@ enum FILTER_COMPONENT_FILTER_TYPES {
FILTER_RANGE = 'filter_range', FILTER_RANGE = 'filter_range',
} }
const getPreselectedValuesFromDashboard = ( const getPreselectedValuesFromDashboard =
preselectedFilters: PreselectedFiltersMeatadata, (preselectedFilters: PreselectedFiltersMeatadata) =>
) => (filterKey: string, column: string) => { (filterKey: string, column: string) => {
if (preselectedFilters[filterKey] && preselectedFilters[filterKey][column]) { if (
// overwrite default values by dashboard default_filters preselectedFilters[filterKey] &&
return preselectedFilters[filterKey][column]; preselectedFilters[filterKey][column]
} ) {
return null; // overwrite default values by dashboard default_filters
}; return preselectedFilters[filterKey][column];
}
return null;
};
const getFilterBoxDefaultValues = (config: FilterConfig) => { const getFilterBoxDefaultValues = (config: FilterConfig) => {
let defaultValues = config[FILTER_CONFIG_ATTRIBUTES.DEFAULT_VALUE]; let defaultValues = config[FILTER_CONFIG_ATTRIBUTES.DEFAULT_VALUE];
@ -218,9 +221,8 @@ export default function getNativeFilterConfig(
time_range, time_range,
} = slice.form_data; } = slice.form_data;
const getDashboardDefaultValues = getPreselectedValuesFromDashboard( const getDashboardDefaultValues =
preselectFilters, getPreselectedValuesFromDashboard(preselectFilters);
);
if (date_filter) { if (date_filter) {
const { scope, immune }: FilterScopeType = const { scope, immune }: FilterScopeType =
@ -488,9 +490,8 @@ export default function getNativeFilterConfig(
} }
}); });
const dependencies: FilterBoxDependencyMap = getFilterboxDependencies( const dependencies: FilterBoxDependencyMap =
filterScopes, getFilterboxDependencies(filterScopes);
);
Object.entries(dependencies).forEach(([key, filterFields]) => { Object.entries(dependencies).forEach(([key, filterFields]) => {
Object.entries(filterFields).forEach(([field, childrenChartIds]) => { Object.entries(filterFields).forEach(([field, childrenChartIds]) => {
const parentComponentId = filterBoxToFilterComponentMap[key][field]; const parentComponentId = filterBoxToFilterComponentMap[key][field];

View File

@ -37,35 +37,29 @@ export default function getComponentWidthFromDrop({
return component.meta.width; return component.meta.width;
} }
const { const { width: draggingWidth, minimumWidth: minDraggingWidth } =
width: draggingWidth, getDetailedComponentWidth({
minimumWidth: minDraggingWidth, component,
} = getDetailedComponentWidth({ components,
component, });
components,
});
const { const { width: destinationWidth, occupiedWidth: draggingOccupiedWidth } =
width: destinationWidth, getDetailedComponentWidth({
occupiedWidth: draggingOccupiedWidth, id: destination.id,
} = getDetailedComponentWidth({ components,
id: destination.id, });
components,
});
let destinationCapacity = Number(destinationWidth - draggingOccupiedWidth); let destinationCapacity = Number(destinationWidth - draggingOccupiedWidth);
if (Number.isNaN(destinationCapacity)) { if (Number.isNaN(destinationCapacity)) {
const { const { width: grandparentWidth, occupiedWidth: grandparentOccupiedWidth } =
width: grandparentWidth, getDetailedComponentWidth({
occupiedWidth: grandparentOccupiedWidth, id: findParentId({
} = getDetailedComponentWidth({ childId: destination.id,
id: findParentId({ layout: components,
childId: destination.id, }),
layout: components, components,
}), });
components,
});
destinationCapacity = Number(grandparentWidth - grandparentOccupiedWidth); destinationCapacity = Number(grandparentWidth - grandparentOccupiedWidth);
} }

View File

@ -57,12 +57,11 @@ function getTabChildrenScope({
)) ))
) { ) {
// get all charts from tabChildren that is not in scope // get all charts from tabChildren that is not in scope
const immuneChartIdsFromTabsNotInScope = getImmuneChartIdsFromTabsNotInScope( const immuneChartIdsFromTabsNotInScope =
{ getImmuneChartIdsFromTabsNotInScope({
tabs: tabChildren, tabs: tabChildren,
tabsInScope: flatMap(tabScopes, ({ scope }) => scope), tabsInScope: flatMap(tabScopes, ({ scope }) => scope),
}, });
);
const immuneChartIdsFromTabsInScope = flatMap( const immuneChartIdsFromTabsInScope = flatMap(
Object.values(tabScopes), Object.values(tabScopes),
({ immune }) => immune, ({ immune }) => immune,

View File

@ -40,7 +40,7 @@ export default function injectCustomCss(css: string) {
document.querySelector(`.${className}`) || createStyleElement(className); document.querySelector(`.${className}`) || createStyleElement(className);
if ('styleSheet' in style) { if ('styleSheet' in style) {
((style as unknown) as MysteryStyleElement).styleSheet.cssText = css; (style as unknown as MysteryStyleElement).styleSheet.cssText = css;
} else { } else {
style.innerHTML = css; style.innerHTML = css;
} }

View File

@ -49,9 +49,8 @@ export type ControlProps = {
/** /**
* *
*/ */
export type ControlComponentProps< export type ControlComponentProps<ValueType extends JsonValue = JsonValue> =
ValueType extends JsonValue = JsonValue Omit<ControlProps, 'value'> & BaseControlComponentProps<ValueType>;
> = Omit<ControlProps, 'value'> & BaseControlComponentProps<ValueType>;
export default function Control(props: ControlProps) { export default function Control(props: ControlProps) {
const { const {

View File

@ -49,14 +49,8 @@ type ExploreActionButtonsProps = {
}; };
const ActionButton = (props: ActionButtonProps) => { const ActionButton = (props: ActionButtonProps) => {
const { const { icon, text, tooltip, className, onTooltipVisibilityChange, ...rest } =
icon, props;
text,
tooltip,
className,
onTooltipVisibilityChange,
...rest
} = props;
return ( return (
<Tooltip <Tooltip
id={`${icon}-tooltip`} id={`${icon}-tooltip`}

View File

@ -48,7 +48,7 @@ const createProps = () => ({
}, },
chartStatus: 'rendered', chartStatus: 'rendered',
}, },
slice: ({ slice: {
cache_timeout: null, cache_timeout: null,
changed_on: '2021-03-19T16:30:56.750230', changed_on: '2021-03-19T16:30:56.750230',
changed_on_humanized: '7 days ago', changed_on_humanized: '7 days ago',
@ -85,7 +85,7 @@ const createProps = () => ({
slice_id: 318, slice_id: 318,
slice_name: 'Age distribution of respondents', slice_name: 'Age distribution of respondents',
slice_url: '/superset/explore/?form_data=%7B%22slice_id%22%3A%20318%7D', slice_url: '/superset/explore/?form_data=%7B%22slice_id%22%3A%20318%7D',
} as unknown) as Slice, } as unknown as Slice,
slice_name: 'Age distribution of respondents', slice_name: 'Age distribution of respondents',
actions: { actions: {
postChartFormData: () => null, postChartFormData: () => null,

View File

@ -25,7 +25,7 @@ import userEvent from '@testing-library/user-event';
import PropertiesModal from '.'; import PropertiesModal from '.';
const createProps = () => ({ const createProps = () => ({
slice: ({ slice: {
cache_timeout: null, cache_timeout: null,
changed_on: '2021-03-19T16:30:56.750230', changed_on: '2021-03-19T16:30:56.750230',
changed_on_humanized: '7 days ago', changed_on_humanized: '7 days ago',
@ -62,7 +62,7 @@ const createProps = () => ({
slice_id: 318, slice_id: 318,
slice_name: 'Age distribution of respondents', slice_name: 'Age distribution of respondents',
slice_url: '/superset/explore/?form_data=%7B%22slice_id%22%3A%20318%7D', slice_url: '/superset/explore/?form_data=%7B%22slice_id%22%3A%20318%7D',
} as unknown) as Slice, } as unknown as Slice,
show: true, show: true,
onHide: jest.fn(), onHide: jest.fn(),
onSave: jest.fn(), onSave: jest.fn(),

View File

@ -88,20 +88,25 @@ export default function PropertiesModal({
); );
const loadOptions = useMemo( const loadOptions = useMemo(
() => (input = '', page: number, pageSize: number) => { () =>
const query = rison.encode({ filter: input, page, page_size: pageSize }); (input = '', page: number, pageSize: number) => {
return SupersetClient.get({ const query = rison.encode({
endpoint: `/api/v1/chart/related/owners?q=${query}`, filter: input,
}).then(response => ({ page,
data: response.json.result.map( page_size: pageSize,
(item: { value: number; text: string }) => ({ });
value: item.value, return SupersetClient.get({
label: item.text, endpoint: `/api/v1/chart/related/owners?q=${query}`,
}), }).then(response => ({
), data: response.json.result.map(
totalCount: response.json.count, (item: { value: number; text: string }) => ({
})); value: item.value,
}, label: item.text,
}),
),
totalCount: response.json.count,
}));
},
[], [],
); );
@ -115,10 +120,12 @@ export default function PropertiesModal({
cache_timeout: cacheTimeout || null, cache_timeout: cacheTimeout || null,
}; };
if (selectedOwners) { if (selectedOwners) {
payload.owners = (selectedOwners as { payload.owners = (
value: number; selectedOwners as {
label: string; value: number;
}[]).map(o => o.value); label: string;
}[]
).map(o => o.value);
} }
try { try {
const res = await SupersetClient.put({ const res = await SupersetClient.put({

View File

@ -164,9 +164,8 @@ export default class AnnotationLayer extends React.PureComponent {
this.applyAnnotation = this.applyAnnotation.bind(this); this.applyAnnotation = this.applyAnnotation.bind(this);
this.fetchOptions = this.fetchOptions.bind(this); this.fetchOptions = this.fetchOptions.bind(this);
this.handleAnnotationType = this.handleAnnotationType.bind(this); this.handleAnnotationType = this.handleAnnotationType.bind(this);
this.handleAnnotationSourceType = this.handleAnnotationSourceType.bind( this.handleAnnotationSourceType =
this, this.handleAnnotationSourceType.bind(this);
);
this.handleValue = this.handleValue.bind(this); this.handleValue = this.handleValue.bind(this);
this.isValidForm = this.isValidForm.bind(this); this.isValidForm = this.isValidForm.bind(this);
} }

View File

@ -74,10 +74,8 @@ const ConditionalFormattingControl = ({
...props ...props
}: ConditionalFormattingControlProps) => { }: ConditionalFormattingControlProps) => {
const theme = useTheme(); const theme = useTheme();
const [ const [conditionalFormattingConfigs, setConditionalFormattingConfigs] =
conditionalFormattingConfigs, useState<ConditionalFormattingConfig[]>(value ?? []);
setConditionalFormattingConfigs,
] = useState<ConditionalFormattingConfig[]>(value ?? []);
useEffect(() => { useEffect(() => {
if (onChange) { if (onChange) {

View File

@ -57,22 +57,22 @@ const operatorOptions = [
{ value: COMPARATOR.BETWEEN_OR_RIGHT_EQUAL, label: '< x ≤', order: 10 }, { value: COMPARATOR.BETWEEN_OR_RIGHT_EQUAL, label: '< x ≤', order: 10 },
]; ];
const targetValueValidator = ( const targetValueValidator =
compare: (targetValue: number, compareValue: number) => boolean, (
rejectMessage: string, compare: (targetValue: number, compareValue: number) => boolean,
) => (targetValue: number | string) => ( rejectMessage: string,
_: any, ) =>
compareValue: number | string, (targetValue: number | string) =>
) => { (_: any, compareValue: number | string) => {
if ( if (
!targetValue || !targetValue ||
!compareValue || !compareValue ||
compare(Number(targetValue), Number(compareValue)) compare(Number(targetValue), Number(compareValue))
) { ) {
return Promise.resolve(); return Promise.resolve();
} }
return Promise.reject(new Error(rejectMessage)); return Promise.reject(new Error(rejectMessage));
}; };
const targetValueLeftValidator = targetValueValidator( const targetValueLeftValidator = targetValueValidator(
(target: number, val: number) => target > val, (target: number, val: number) => target > val,

View File

@ -116,9 +116,8 @@ class DatasourceControl extends React.PureComponent {
showChangeDatasourceModal: false, showChangeDatasourceModal: false,
}; };
this.onDatasourceSave = this.onDatasourceSave.bind(this); this.onDatasourceSave = this.onDatasourceSave.bind(this);
this.toggleChangeDatasourceModal = this.toggleChangeDatasourceModal.bind( this.toggleChangeDatasourceModal =
this, this.toggleChangeDatasourceModal.bind(this);
);
this.toggleEditDatasourceModal = this.toggleEditDatasourceModal.bind(this); this.toggleEditDatasourceModal = this.toggleEditDatasourceModal.bind(this);
this.toggleShowDatasource = this.toggleShowDatasource.bind(this); this.toggleShowDatasource = this.toggleShowDatasource.bind(this);
this.handleMenuItemClick = this.handleMenuItemClick.bind(this); this.handleMenuItemClick = this.handleMenuItemClick.bind(this);

View File

@ -93,7 +93,8 @@ export const SINCE_MODE_OPTIONS: SelectOptionType[] = [
{ value: 'today', label: t('Midnight'), order: 3 }, { value: 'today', label: t('Midnight'), order: 3 },
]; ];
export const UNTIL_MODE_OPTIONS: SelectOptionType[] = SINCE_MODE_OPTIONS.slice(); export const UNTIL_MODE_OPTIONS: SelectOptionType[] =
SINCE_MODE_OPTIONS.slice();
export const COMMON_RANGE_SET: Set<CommonRangeType> = new Set([ export const COMMON_RANGE_SET: Set<CommonRangeType> = new Set([
'Last day', 'Last day',

View File

@ -32,9 +32,9 @@ export const formatTimeRange = (
) => { ) => {
const splitDateRange = timeRange.split(SEPARATOR); const splitDateRange = timeRange.split(SEPARATOR);
if (splitDateRange.length === 1) return timeRange; if (splitDateRange.length === 1) return timeRange;
const formattedEndpoints = ( const formattedEndpoints = (endpoints || ['unknown', 'unknown']).map(
endpoints || ['unknown', 'unknown'] (endpoint: string) => (endpoint === 'inclusive' ? '≤' : '<'),
).map((endpoint: string) => (endpoint === 'inclusive' ? '≤' : '<')); );
return `${formatDateEndpoint(splitDateRange[0], true)} ${ return `${formatDateEndpoint(splitDateRange[0], true)} ${
formattedEndpoints[0] formattedEndpoints[0]

View File

@ -84,12 +84,12 @@ export const customTimeRangeDecode = (
// specific : specific // specific : specific
if (ISO8601_AND_CONSTANT.test(since) && ISO8601_AND_CONSTANT.test(until)) { if (ISO8601_AND_CONSTANT.test(since) && ISO8601_AND_CONSTANT.test(until)) {
const sinceMode = (DATETIME_CONSTANT.includes(since) const sinceMode = (
? since DATETIME_CONSTANT.includes(since) ? since : 'specific'
: 'specific') as DateTimeModeType; ) as DateTimeModeType;
const untilMode = (DATETIME_CONSTANT.includes(until) const untilMode = (
? until DATETIME_CONSTANT.includes(until) ? until : 'specific'
: 'specific') as DateTimeModeType; ) as DateTimeModeType;
return { return {
customRange: { customRange: {
...defaultCustomRange, ...defaultCustomRange,
@ -110,9 +110,9 @@ export const customTimeRangeDecode = (
since.includes(until) since.includes(until)
) { ) {
const [dttm, grainValue, grain] = sinceCapturedGroup.slice(1); const [dttm, grainValue, grain] = sinceCapturedGroup.slice(1);
const untilMode = (DATETIME_CONSTANT.includes(until) const untilMode = (
? until DATETIME_CONSTANT.includes(until) ? until : 'specific'
: 'specific') as DateTimeModeType; ) as DateTimeModeType;
return { return {
customRange: { customRange: {
...defaultCustomRange, ...defaultCustomRange,
@ -135,9 +135,9 @@ export const customTimeRangeDecode = (
until.includes(since) until.includes(since)
) { ) {
const [dttm, grainValue, grain] = [...untilCapturedGroup.slice(1)]; const [dttm, grainValue, grain] = [...untilCapturedGroup.slice(1)];
const sinceMode = (DATETIME_CONSTANT.includes(since) const sinceMode = (
? since DATETIME_CONSTANT.includes(since) ? since : 'specific'
: 'specific') as DateTimeModeType; ) as DateTimeModeType;
return { return {
customRange: { customRange: {
...defaultCustomRange, ...defaultCustomRange,

View File

@ -88,11 +88,8 @@ const ColumnSelectPopover = ({
isAdhocColumnsEnabled, isAdhocColumnsEnabled,
}: ColumnSelectPopoverProps) => { }: ColumnSelectPopoverProps) => {
const [initialLabel] = useState(label); const [initialLabel] = useState(label);
const [ const [initialAdhocColumn, initialCalculatedColumn, initialSimpleColumn] =
initialAdhocColumn, getInitialColumnValues(editedColumn);
initialCalculatedColumn,
initialSimpleColumn,
] = getInitialColumnValues(editedColumn);
const [adhocColumn, setAdhocColumn] = useState<AdhocColumn | undefined>( const [adhocColumn, setAdhocColumn] = useState<AdhocColumn | undefined>(
initialAdhocColumn, initialAdhocColumn,

View File

@ -81,21 +81,18 @@ const ColumnSelectPopoverTrigger = ({
setPopoverVisible(false); setPopoverVisible(false);
}, []); }, []);
const { const { visible, handleTogglePopover, handleClosePopover } =
visible, isControlledComponent
handleTogglePopover, ? {
handleClosePopover, visible: props.visible,
} = isControlledComponent handleTogglePopover: props.togglePopover!,
? { handleClosePopover: props.closePopover!,
visible: props.visible, }
handleTogglePopover: props.togglePopover!, : {
handleClosePopover: props.closePopover!, visible: popoverVisible,
} handleTogglePopover: togglePopover,
: { handleClosePopover: closePopover,
visible: popoverVisible, };
handleTogglePopover: togglePopover,
handleClosePopover: closePopover,
};
const getCurrentTab = useCallback((tab: string) => { const getCurrentTab = useCallback((tab: string) => {
setIsTitleEditDisabled(tab !== editableTitleTab); setIsTitleEditDisabled(tab !== editableTitleTab);

View File

@ -28,7 +28,7 @@ import {
DndFilterSelectProps, DndFilterSelectProps,
} from 'src/explore/components/controls/DndColumnSelectControl/DndFilterSelect'; } from 'src/explore/components/controls/DndColumnSelectControl/DndFilterSelect';
import { PLACEHOLDER_DATASOURCE } from 'src/dashboard/constants'; import { PLACEHOLDER_DATASOURCE } from 'src/dashboard/constants';
import { DEFAULT_FORM_DATA } from '@superset-ui/plugin-chart-echarts/lib/Timeseries/types'; import { TimeseriesDefaultFormData } from '@superset-ui/plugin-chart-echarts';
const defaultProps: DndFilterSelectProps = { const defaultProps: DndFilterSelectProps = {
type: 'DndFilterSelect', type: 'DndFilterSelect',
@ -70,7 +70,7 @@ test('renders options with saved metric', () => {
{...defaultProps} {...defaultProps}
formData={{ formData={{
...baseFormData, ...baseFormData,
...DEFAULT_FORM_DATA, ...TimeseriesDefaultFormData,
metrics: ['saved_metric'], metrics: ['saved_metric'],
}} }}
/>, />,
@ -111,7 +111,7 @@ test('renders options with adhoc metric', () => {
{...defaultProps} {...defaultProps}
formData={{ formData={{
...baseFormData, ...baseFormData,
...DEFAULT_FORM_DATA, ...TimeseriesDefaultFormData,
metrics: [adhocMetric], metrics: [adhocMetric],
}} }}
/>, />,

View File

@ -41,13 +41,12 @@ export interface OptionItemInterface {
/** /**
* Shared control props for all DnD control. * Shared control props for all DnD control.
*/ */
export type DndControlProps< export type DndControlProps<ValueType extends JsonValue> =
ValueType extends JsonValue ControlComponentProps<ValueType | ValueType[] | null> & {
> = ControlComponentProps<ValueType | ValueType[] | null> & { multi?: boolean;
multi?: boolean; canDelete?: boolean;
canDelete?: boolean; ghostButtonText?: string;
ghostButtonText?: string; onChange: (value: ValueType | ValueType[] | null | undefined) => void;
onChange: (value: ValueType | ValueType[] | null | undefined) => void; };
};
export type OptionValueType = Record<string, any>; export type OptionValueType = Record<string, any>;

View File

@ -121,9 +121,9 @@ export default class AdhocFilter {
this.filterOptionName = this.filterOptionName =
adhocFilter.filterOptionName || adhocFilter.filterOptionName ||
`filter_${Math.random() `filter_${Math.random().toString(36).substring(2, 15)}_${Math.random()
.toString(36) .toString(36)
.substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`; .substring(2, 15)}`;
} }
duplicateWith(nextFields) { duplicateWith(nextFields) {

View File

@ -227,10 +227,8 @@ const AdhocFilterEditPopoverSimpleTabContent: React.FC<Props> = props => {
} = useSimpleTabFilterProps(props); } = useSimpleTabFilterProps(props);
const [suggestions, setSuggestions] = useState<Record<string, any>>([]); const [suggestions, setSuggestions] = useState<Record<string, any>>([]);
const [comparator, setComparator] = useState(props.adhocFilter.comparator); const [comparator, setComparator] = useState(props.adhocFilter.comparator);
const [ const [loadingComparatorSuggestions, setLoadingComparatorSuggestions] =
loadingComparatorSuggestions, useState(false);
setLoadingComparatorSuggestions,
] = useState(false);
const onInputComparatorChange = ( const onInputComparatorChange = (
event: React.ChangeEvent<HTMLInputElement>, event: React.ChangeEvent<HTMLInputElement>,

View File

@ -55,9 +55,8 @@ export default class AdhocFilterEditPopoverSqlTabContent extends React.Component
constructor(props) { constructor(props) {
super(props); super(props);
this.onSqlExpressionChange = this.onSqlExpressionChange.bind(this); this.onSqlExpressionChange = this.onSqlExpressionChange.bind(this);
this.onSqlExpressionClauseChange = this.onSqlExpressionClauseChange.bind( this.onSqlExpressionClauseChange =
this, this.onSqlExpressionClauseChange.bind(this);
);
this.handleAceEditorRef = this.handleAceEditorRef.bind(this); this.handleAceEditorRef = this.handleAceEditorRef.bind(this);
this.selectProps = { this.selectProps = {

View File

@ -80,9 +80,9 @@ export default class AdhocMetric {
this.optionName = this.optionName =
adhocMetric.optionName || adhocMetric.optionName ||
`metric_${Math.random() `metric_${Math.random().toString(36).substring(2, 15)}_${Math.random()
.toString(36) .toString(36)
.substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`; .substring(2, 15)}`;
} }
getDefaultLabel() { getDefaultLabel() {

View File

@ -207,19 +207,20 @@ const MetricsControl = ({
[value], [value],
); );
const isAddNewMetricDisabled = useCallback(() => !multi && value.length > 0, [ const isAddNewMetricDisabled = useCallback(
multi, () => !multi && value.length > 0,
value.length, [multi, value.length],
]); );
const savedMetricOptions = useMemo( const savedMetricOptions = useMemo(
() => getOptionsForSavedMetrics(savedMetrics, propsValue, null), () => getOptionsForSavedMetrics(savedMetrics, propsValue, null),
[propsValue, savedMetrics], [propsValue, savedMetrics],
); );
const newAdhocMetric = useMemo(() => new AdhocMetric({ isNew: true }), [ const newAdhocMetric = useMemo(
value, () => new AdhocMetric({ isNew: true }),
]); [value],
);
const addNewMetricPopoverTrigger = useCallback( const addNewMetricPopoverTrigger = useCallback(
trigger => { trigger => {
if (isAddNewMetricDisabled()) { if (isAddNewMetricDisabled()) {
@ -271,10 +272,10 @@ const MetricsControl = ({
setValue(coerceAdhocMetrics(propsValue)); setValue(coerceAdhocMetrics(propsValue));
}, [propsValue]); }, [propsValue]);
const onDropLabel = useCallback(() => handleChange(value), [ const onDropLabel = useCallback(
handleChange, () => handleChange(value),
value, [handleChange, value],
]); );
const valueRenderer = useCallback( const valueRenderer = useCallback(
(option, index) => ( (option, index) => (

View File

@ -46,7 +46,7 @@ const safeStringify = (value?: InputValueType | null) =>
value == null ? '' : String(value); value == null ? '' : String(value);
export default class TextControl< export default class TextControl<
T extends InputValueType = InputValueType T extends InputValueType = InputValueType,
> extends React.Component<TextControlProps<T>, TextControlState> { > extends React.Component<TextControlProps<T>, TextControlState> {
initialValue?: TextControlProps['value']; initialValue?: TextControlProps['value'];

View File

@ -31,8 +31,8 @@ import { testWithId } from 'src/utils/testUtils';
import { import {
EchartsMixedTimeseriesChartPlugin, EchartsMixedTimeseriesChartPlugin,
EchartsTimeseriesChartPlugin, EchartsTimeseriesChartPlugin,
} from '@superset-ui/plugin-chart-echarts/lib'; } from '@superset-ui/plugin-chart-echarts';
import { LineChartPlugin } from '@superset-ui/preset-chart-xy/lib'; import { LineChartPlugin } from '@superset-ui/preset-chart-xy';
import TimeTableChartPlugin from '../../../../visualizations/TimeTable/TimeTableChartPlugin'; import TimeTableChartPlugin from '../../../../visualizations/TimeTable/TimeTableChartPlugin';
import VizTypeControl, { VIZ_TYPE_CONTROL_TEST_ID } from './index'; import VizTypeControl, { VIZ_TYPE_CONTROL_TEST_ID } from './index';

View File

@ -102,9 +102,12 @@ export const DISABLE_INPUT_OPERATORS = [
Operators.IS_FALSE, Operators.IS_FALSE,
]; ];
export const sqlaAutoGeneratedMetricNameRegex = /^(sum|min|max|avg|count|count_distinct)__.*$/i; export const sqlaAutoGeneratedMetricNameRegex =
export const sqlaAutoGeneratedMetricRegex = /^(LONG|DOUBLE|FLOAT)?(SUM|AVG|MAX|MIN|COUNT)\([A-Z0-9_."]*\)$/i; /^(sum|min|max|avg|count|count_distinct)__.*$/i;
export const druidAutoGeneratedMetricRegex = /^(LONG|DOUBLE|FLOAT)?(SUM|MAX|MIN|COUNT)\([A-Z0-9_."]*\)$/i; export const sqlaAutoGeneratedMetricRegex =
/^(LONG|DOUBLE|FLOAT)?(SUM|AVG|MAX|MIN|COUNT)\([A-Z0-9_."]*\)$/i;
export const druidAutoGeneratedMetricRegex =
/^(LONG|DOUBLE|FLOAT)?(SUM|MAX|MIN|COUNT)\([A-Z0-9_."]*\)$/i;
export const TIME_FILTER_LABELS = { export const TIME_FILTER_LABELS = {
time_range: t('Time range'), time_range: t('Time range'),

View File

@ -47,10 +47,8 @@ export function findControlItem(
const getMemoizedControlConfig = memoizeOne( const getMemoizedControlConfig = memoizeOne(
(controlKey, controlPanelConfig) => { (controlKey, controlPanelConfig) => {
const { const { controlOverrides = {}, controlPanelSections = [] } =
controlOverrides = {}, controlPanelConfig;
controlPanelSections = [],
} = controlPanelConfig;
const control = expandControlConfig( const control = expandControlConfig(
findControlItem(controlPanelSections, controlKey), findControlItem(controlPanelSections, controlKey),
controlOverrides, controlOverrides,

View File

@ -20,14 +20,8 @@ import { ChartProps } from '@superset-ui/core';
import { DEFAULT_FORM_DATA } from './types'; import { DEFAULT_FORM_DATA } from './types';
export default function transformProps(chartProps: ChartProps) { export default function transformProps(chartProps: ChartProps) {
const { const { formData, height, hooks, queriesData, width, filterState } =
formData, chartProps;
height,
hooks,
queriesData,
width,
filterState,
} = chartProps;
const { const {
setDataMask = () => {}, setDataMask = () => {},
setFocusedFilter = () => {}, setFocusedFilter = () => {},

View File

@ -68,12 +68,8 @@ const loggerMiddleware = store => next => action => {
return next(action); return next(action);
} }
const { const { dashboardInfo, explore, impressionId, dashboardLayout } =
dashboardInfo, store.getState();
explore,
impressionId,
dashboardLayout,
} = store.getState();
let logMetadata = { let logMetadata = {
impression_id: impressionId, impression_id: impressionId,
version: 'v2', version: 'v2',

View File

@ -17,16 +17,18 @@
* under the License. * under the License.
*/ */
export const cacheWrapper = <T extends Array<any>, U>( export const cacheWrapper =
fn: (...args: T) => U, <T extends Array<any>, U>(
cache: Map<string, any>, fn: (...args: T) => U,
keyFn: (...args: T) => string = (...args: T) => JSON.stringify([...args]), cache: Map<string, any>,
) => (...args: T): U => { keyFn: (...args: T) => string = (...args: T) => JSON.stringify([...args]),
const key = keyFn(...args); ) =>
if (cache.has(key)) { (...args: T): U => {
return cache.get(key); const key = keyFn(...args);
} if (cache.has(key)) {
const result = fn(...args); return cache.get(key);
cache.set(key, result); }
return result; const result = fn(...args);
}; cache.set(key, result);
return result;
};

View File

@ -21,23 +21,25 @@ import { JsonObject } from '@superset-ui/core';
type TestWithIdType<T> = T extends string ? string : { 'data-test': string }; type TestWithIdType<T> = T extends string ? string : { 'data-test': string };
// Using bem standard // Using bem standard
export const testWithId = <T extends string | JsonObject = JsonObject>( export const testWithId =
prefix?: string, <T extends string | JsonObject = JsonObject>(
idOnly = false, prefix?: string,
) => (id?: string, localIdOnly = false): TestWithIdType<T> => { idOnly = false,
const resultIdOnly = localIdOnly || idOnly; ) =>
if (!id && prefix) { (id?: string, localIdOnly = false): TestWithIdType<T> => {
return (resultIdOnly const resultIdOnly = localIdOnly || idOnly;
? prefix if (!id && prefix) {
: { 'data-test': prefix }) as TestWithIdType<T>; return (
} resultIdOnly ? prefix : { 'data-test': prefix }
if (id && !prefix) { ) as TestWithIdType<T>;
return (resultIdOnly ? id : { 'data-test': id }) as TestWithIdType<T>; }
} if (id && !prefix) {
if (!id && !prefix) { return (resultIdOnly ? id : { 'data-test': id }) as TestWithIdType<T>;
console.warn('testWithId function has missed "prefix" and "id" params'); }
return (resultIdOnly ? '' : { 'data-test': '' }) as TestWithIdType<T>; if (!id && !prefix) {
} console.warn('testWithId function has missed "prefix" and "id" params');
const newId = `${prefix}__${id}`; return (resultIdOnly ? '' : { 'data-test': '' }) as TestWithIdType<T>;
return (resultIdOnly ? newId : { 'data-test': newId }) as TestWithIdType<T>; }
}; const newId = `${prefix}__${id}`;
return (resultIdOnly ? newId : { 'data-test': newId }) as TestWithIdType<T>;
};

View File

@ -123,10 +123,8 @@ function AlertList({
const [currentAlert, setCurrentAlert] = useState<Partial<AlertObject> | null>( const [currentAlert, setCurrentAlert] = useState<Partial<AlertObject> | null>(
null, null,
); );
const [ const [currentAlertDeleting, setCurrentAlertDeleting] =
currentAlertDeleting, useState<AlertObject | null>(null);
setCurrentAlertDeleting,
] = useState<AlertObject | null>(null);
// Actions // Actions
function handleAlertEdit(alert: AlertObject | null) { function handleAlertEdit(alert: AlertObject | null) {

View File

@ -408,10 +408,8 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
conf?.ALERT_REPORTS_NOTIFICATION_METHODS || DEFAULT_NOTIFICATION_METHODS; conf?.ALERT_REPORTS_NOTIFICATION_METHODS || DEFAULT_NOTIFICATION_METHODS;
const [disableSave, setDisableSave] = useState<boolean>(true); const [disableSave, setDisableSave] = useState<boolean>(true);
const [ const [currentAlert, setCurrentAlert] =
currentAlert, useState<Partial<AlertObject> | null>();
setCurrentAlert,
] = useState<Partial<AlertObject> | null>();
const [isHidden, setIsHidden] = useState<boolean>(true); const [isHidden, setIsHidden] = useState<boolean>(true);
const [contentType, setContentType] = useState<string>('dashboard'); const [contentType, setContentType] = useState<string>('dashboard');
const [reportFormat, setReportFormat] = useState<string>( const [reportFormat, setReportFormat] = useState<string>(
@ -432,10 +430,8 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
contentType === 'chart' && contentType === 'chart' &&
(isFeatureEnabled(FeatureFlag.ALERTS_ATTACH_REPORTS) || isReport); (isFeatureEnabled(FeatureFlag.ALERTS_ATTACH_REPORTS) || isReport);
const [ const [notificationAddState, setNotificationAddState] =
notificationAddState, useState<NotificationAddStatus>('active');
setNotificationAddState,
] = useState<NotificationAddStatus>('active');
const [notificationSettings, setNotificationSettings] = useState< const [notificationSettings, setNotificationSettings] = useState<
NotificationSetting[] NotificationSetting[]
>([]); >([]);
@ -581,20 +577,25 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
// Fetch data to populate form dropdowns // Fetch data to populate form dropdowns
const loadOwnerOptions = useMemo( const loadOwnerOptions = useMemo(
() => (input = '', page: number, pageSize: number) => { () =>
const query = rison.encode({ filter: input, page, page_size: pageSize }); (input = '', page: number, pageSize: number) => {
return SupersetClient.get({ const query = rison.encode({
endpoint: `/api/v1/report/related/owners?q=${query}`, filter: input,
}).then(response => ({ page,
data: response.json.result.map( page_size: pageSize,
(item: { value: number; text: string }) => ({ });
value: item.value, return SupersetClient.get({
label: item.text, endpoint: `/api/v1/report/related/owners?q=${query}`,
}), }).then(response => ({
), data: response.json.result.map(
totalCount: response.json.count, (item: { value: number; text: string }) => ({
})); value: item.value,
}, label: item.text,
}),
),
totalCount: response.json.count,
}));
},
[], [],
); );
@ -629,21 +630,26 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
}; };
const loadSourceOptions = useMemo( const loadSourceOptions = useMemo(
() => (input = '', page: number, pageSize: number) => { () =>
const query = rison.encode({ filter: input, page, page_size: pageSize }); (input = '', page: number, pageSize: number) => {
return SupersetClient.get({ const query = rison.encode({
endpoint: `/api/v1/report/related/database?q=${query}`, filter: input,
}).then(response => { page,
const list = response.json.result.map( page_size: pageSize,
(item: { value: number; text: string }) => ({ });
value: item.value, return SupersetClient.get({
label: item.text, endpoint: `/api/v1/report/related/database?q=${query}`,
}), }).then(response => {
); const list = response.json.result.map(
setSourceOptions(list); (item: { value: number; text: string }) => ({
return { data: list, totalCount: response.json.count }; value: item.value,
}); label: item.text,
}, }),
);
setSourceOptions(list);
return { data: list, totalCount: response.json.count };
});
},
[], [],
); );
@ -657,21 +663,26 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
}, [databaseLabel, getSourceData]); }, [databaseLabel, getSourceData]);
const loadDashboardOptions = useMemo( const loadDashboardOptions = useMemo(
() => (input = '', page: number, pageSize: number) => { () =>
const query = rison.encode({ filter: input, page, page_size: pageSize }); (input = '', page: number, pageSize: number) => {
return SupersetClient.get({ const query = rison.encode({
endpoint: `/api/v1/report/related/dashboard?q=${query}`, filter: input,
}).then(response => { page,
const list = response.json.result.map( page_size: pageSize,
(item: { value: number; text: string }) => ({ });
value: item.value, return SupersetClient.get({
label: item.text, endpoint: `/api/v1/report/related/dashboard?q=${query}`,
}), }).then(response => {
); const list = response.json.result.map(
setDashboardOptions(list); (item: { value: number; text: string }) => ({
return { data: list, totalCount: response.json.count }; value: item.value,
}); label: item.text,
}, }),
);
setDashboardOptions(list);
return { data: list, totalCount: response.json.count };
});
},
[], [],
); );
@ -726,22 +737,27 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
}, [getChartData, noChartLabel]); }, [getChartData, noChartLabel]);
const loadChartOptions = useMemo( const loadChartOptions = useMemo(
() => (input = '', page: number, pageSize: number) => { () =>
const query = rison.encode({ filter: input, page, page_size: pageSize }); (input = '', page: number, pageSize: number) => {
return SupersetClient.get({ const query = rison.encode({
endpoint: `/api/v1/report/related/chart?q=${query}`, filter: input,
}).then(response => { page,
const list = response.json.result.map( page_size: pageSize,
(item: { value: number; text: string }) => ({ });
value: item.value, return SupersetClient.get({
label: item.text, endpoint: `/api/v1/report/related/chart?q=${query}`,
}), }).then(response => {
); const list = response.json.result.map(
(item: { value: number; text: string }) => ({
value: item.value,
label: item.text,
}),
);
setChartOptions(list); setChartOptions(list);
return { data: list, totalCount: response.json.count }; return { data: list, totalCount: response.json.count };
}); });
}, },
[], [],
); );

Some files were not shown because too many files have changed in this diff Show More