diff --git a/superset-frontend/.eslintrc.js b/superset-frontend/.eslintrc.js index 0a3b7aa996..95c687ddcc 100644 --- a/superset-frontend/.eslintrc.js +++ b/superset-frontend/.eslintrc.js @@ -101,7 +101,6 @@ module.exports = { 'no-multi-spaces': 0, 'no-prototype-builtins': 0, 'no-restricted-properties': 0, - 'no-restricted-syntax': 0, 'no-restricted-imports': [ 'error', { @@ -215,7 +214,6 @@ module.exports = { 'no-multi-spaces': 0, 'no-prototype-builtins': 0, 'no-restricted-properties': 0, - 'no-restricted-syntax': 0, 'no-restricted-imports': [ 'error', { diff --git a/superset-frontend/spec/javascripts/explore/controlUtils_spec.jsx b/superset-frontend/spec/javascripts/explore/controlUtils_spec.jsx index 02c683f77a..e52f70dd0f 100644 --- a/superset-frontend/spec/javascripts/explore/controlUtils_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/controlUtils_spec.jsx @@ -17,19 +17,20 @@ * under the License. */ -import React from 'react'; -import { - getChartControlPanelRegistry, - ColumnOption, - t, -} from '@superset-ui/core'; +import { getChartControlPanelRegistry, t } from '@superset-ui/core'; import { getControlConfig, getControlState, getFormDataFromControls, applyMapStateToPropsToControl, getAllControlsState, + findControlItem, } from 'src/explore/controlUtils'; +import { + controlPanelSectionsChartOptions, + controlPanelSectionsChartOptionsOnlyColorScheme, + controlPanelSectionsChartOptionsTable, +} from 'spec/javascripts/explore/fixtures'; describe('controlUtils', () => { const state = { @@ -43,56 +44,10 @@ describe('controlUtils', () => { beforeAll(() => { getChartControlPanelRegistry() .registerValue('test-chart', { - controlPanelSections: [ - { - label: t('Chart Options'), - expanded: true, - controlSetRows: [ - [ - 'color_scheme', - { - name: 'rose_area_proportion', - config: { - type: 'CheckboxControl', - label: t('Use Area Proportions'), - description: t( - 'Check if the Rose Chart should use segment area instead of ' + - 'segment radius for proportioning', - ), - default: false, - renderTrigger: true, - }, - }, - ], - [ - { - name: 'stacked_style', - config: { - type: 'SelectControl', - label: t('Stacked Style'), - renderTrigger: true, - choices: [ - ['stack', 'stack'], - ['stream', 'stream'], - ['expand', 'expand'], - ], - default: 'stack', - description: '', - }, - }, - ], - ], - }, - ], + controlPanelSections: controlPanelSectionsChartOptions, }) .registerValue('test-chart-override', { - controlPanelSections: [ - { - label: t('Chart Options'), - expanded: true, - controlSetRows: [['color_scheme']], - }, - ], + controlPanelSections: controlPanelSectionsChartOptionsOnlyColorScheme, controlOverrides: { color_scheme: { label: t('My beautiful colors'), @@ -100,40 +55,7 @@ describe('controlUtils', () => { }, }) .registerValue('table', { - controlPanelSections: [ - { - label: t('Chart Options'), - expanded: true, - controlSetRows: [ - [ - 'metric', - 'metrics', - { - name: 'all_columns', - config: { - type: 'SelectControl', - queryField: 'columns', - multi: true, - label: t('Columns'), - default: [], - description: t('Columns to display'), - optionRenderer: c => , - valueRenderer: c => , - valueKey: 'column_name', - allowAll: true, - mapStateToProps: stateRef => ({ - options: stateRef.datasource - ? stateRef.datasource.columns - : [], - }), - commaChoosesOption: false, - freeForm: true, - }, - }, - ], - ], - }, - ], + controlPanelSections: controlPanelSectionsChartOptionsTable, }); }); @@ -287,4 +209,38 @@ describe('controlUtils', () => { }); }); }); + + describe('findControlItem', () => { + it('find control as a string', () => { + const controlItem = findControlItem( + controlPanelSectionsChartOptions, + 'color_scheme', + ); + expect(controlItem).toEqual('color_scheme'); + }); + + it('find control as a control object', () => { + let controlItem = findControlItem( + controlPanelSectionsChartOptions, + 'rose_area_proportion', + ); + expect(controlItem.name).toEqual('rose_area_proportion'); + expect(controlItem).toHaveProperty('config'); + + controlItem = findControlItem( + controlPanelSectionsChartOptions, + 'stacked_style', + ); + expect(controlItem.name).toEqual('stacked_style'); + expect(controlItem).toHaveProperty('config'); + }); + + it('returns null when key is not found', () => { + const controlItem = findControlItem( + controlPanelSectionsChartOptions, + 'non_existing_key', + ); + expect(controlItem).toBeNull(); + }); + }); }); diff --git a/superset-frontend/spec/javascripts/explore/fixtures.jsx b/superset-frontend/spec/javascripts/explore/fixtures.jsx new file mode 100644 index 0000000000..a4340854e8 --- /dev/null +++ b/superset-frontend/spec/javascripts/explore/fixtures.jsx @@ -0,0 +1,104 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { ColumnOption, t } from '@superset-ui/core'; + +export const controlPanelSectionsChartOptions = [ + { + label: t('Chart Options'), + expanded: true, + controlSetRows: [ + [ + 'color_scheme', + { + name: 'rose_area_proportion', + config: { + type: 'CheckboxControl', + label: t('Use Area Proportions'), + description: t( + 'Check if the Rose Chart should use segment area instead of ' + + 'segment radius for proportioning', + ), + default: false, + renderTrigger: true, + }, + }, + ], + [ + { + name: 'stacked_style', + config: { + type: 'SelectControl', + label: t('Stacked Style'), + renderTrigger: true, + choices: [ + ['stack', 'stack'], + ['stream', 'stream'], + ['expand', 'expand'], + ], + default: 'stack', + description: '', + }, + }, + ], + ], + }, +]; + +export const controlPanelSectionsChartOptionsOnlyColorScheme = [ + { + label: t('Chart Options'), + expanded: true, + controlSetRows: [['color_scheme']], + }, +]; + +export const controlPanelSectionsChartOptionsTable = [ + { + label: t('Chart Options'), + expanded: true, + controlSetRows: [ + [ + 'metric', + 'metrics', + { + name: 'all_columns', + config: { + type: 'SelectControl', + queryField: 'columns', + multi: true, + label: t('Columns'), + default: [], + description: t('Columns to display'), + optionRenderer: c => , + valueRenderer: c => , + valueKey: 'column_name', + allowAll: true, + mapStateToProps: stateRef => ({ + options: stateRef.datasource ? stateRef.datasource.columns : [], + }), + commaChoosesOption: false, + freeForm: true, + }, + }, + ], + ], + }, +]; diff --git a/superset-frontend/src/SqlLab/components/TabbedSqlEditors.jsx b/superset-frontend/src/SqlLab/components/TabbedSqlEditors.jsx index b97d28d810..3e07db310a 100644 --- a/superset-frontend/src/SqlLab/components/TabbedSqlEditors.jsx +++ b/superset-frontend/src/SqlLab/components/TabbedSqlEditors.jsx @@ -173,12 +173,9 @@ class TabbedSqlEditors extends React.PureComponent { UNSAFE_componentWillReceiveProps(nextProps) { const nextActiveQeId = nextProps.tabHistory[nextProps.tabHistory.length - 1]; - const queriesArray = []; - for (const id in nextProps.queries) { - if (nextProps.queries[id].sqlEditorId === nextActiveQeId) { - queriesArray.push(nextProps.queries[id]); - } - } + const queriesArray = Object.values(nextProps.queries).filter( + query => query.sqlEditorId === nextActiveQeId, + ); if (!areArraysShallowEqual(queriesArray, this.state.queriesArray)) { this.setState({ queriesArray }); } diff --git a/superset-frontend/src/SqlLab/components/TableElement.jsx b/superset-frontend/src/SqlLab/components/TableElement.jsx index c65afd476e..f6bbabfc3c 100644 --- a/superset-frontend/src/SqlLab/components/TableElement.jsx +++ b/superset-frontend/src/SqlLab/components/TableElement.jsx @@ -110,10 +110,9 @@ class TableElement extends React.PureComponent { /> ); } - let latest = []; - for (const k in table.partitions.latest) { - latest.push(`${k}=${table.partitions.latest[k]}`); - } + let latest = Object.entries(table.partitions.latest).map( + ([key, value]) => `${key}=${value}`, + ); latest = latest.join('/'); header = ( diff --git a/superset-frontend/src/SqlLab/reducers/sqlLab.js b/superset-frontend/src/SqlLab/reducers/sqlLab.js index a64a8b0e46..97f3267840 100644 --- a/superset-frontend/src/SqlLab/reducers/sqlLab.js +++ b/superset-frontend/src/SqlLab/reducers/sqlLab.js @@ -493,8 +493,7 @@ export default function sqlLabReducer(state = {}, action) { // Fetch the updates to the queries present in the store. let change = false; let { queriesLastUpdate } = state; - for (const id in action.alteredQueries) { - const changedQuery = action.alteredQueries[id]; + Object.entries(action.alteredQueries).forEach(([id, changedQuery]) => { if ( !state.queries.hasOwnProperty(id) || (state.queries[id].state !== 'stopped' && @@ -506,7 +505,7 @@ export default function sqlLabReducer(state = {}, action) { newQueries[id] = { ...state.queries[id], ...changedQuery }; change = true; } - } + }); if (!change) { newQueries = state.queries; } diff --git a/superset-frontend/src/components/AlteredSliceTag.jsx b/superset-frontend/src/components/AlteredSliceTag.jsx index ace9694fe8..34244e2827 100644 --- a/superset-frontend/src/components/AlteredSliceTag.jsx +++ b/superset-frontend/src/components/AlteredSliceTag.jsx @@ -76,19 +76,17 @@ export default class AlteredSliceTag extends React.Component { const fdKeys = Object.keys(cfd); const diffs = {}; - for (const fdKey of fdKeys) { - // Ignore values that are undefined/nonexisting in either + fdKeys.forEach(fdKey => { if (!ofd[fdKey] && !cfd[fdKey]) { - continue; + return; } - // Ignore obsolete legacy filters if (['filters', 'having', 'having_filters', 'where'].includes(fdKey)) { - continue; + return; } if (!this.isEqualish(ofd[fdKey], cfd[fdKey])) { diffs[fdKey] = { before: ofd[fdKey], after: cfd[fdKey] }; } - } + }); return diffs; } @@ -149,7 +147,7 @@ export default class AlteredSliceTag extends React.Component { renderRows() { const { diffs } = this.state; const rows = []; - for (const key in diffs) { + Object.entries(diffs).forEach(([key, diff]) => { rows.push( - {this.formatValue(diffs[key].before, key)} - {this.formatValue(diffs[key].after, key)} + {this.formatValue(diff.before, key)} + {this.formatValue(diff.after, key)} , ); - } + }); return rows; } diff --git a/superset-frontend/src/components/FilterableTable/FilterableTable.tsx b/superset-frontend/src/components/FilterableTable/FilterableTable.tsx index 2df936f813..047698adaa 100644 --- a/superset-frontend/src/components/FilterableTable/FilterableTable.tsx +++ b/superset-frontend/src/components/FilterableTable/FilterableTable.tsx @@ -26,11 +26,11 @@ import { Grid, ScrollSync, SortDirection, + SortDirectionType, SortIndicator, Table, - SortDirectionType, } from 'react-virtualized'; -import { t, getMultipleTextDimensions } from '@superset-ui/core'; +import { getMultipleTextDimensions, t } from '@superset-ui/core'; import Button from '../Button'; import CopyToClipboard from '../CopyToClipboard'; @@ -241,24 +241,22 @@ export default class FilterableTable extends PureComponent< } formatTableData(data: Record[]): Datum[] { - const formattedData = data.map(row => { + return data.map(row => { const newRow = {}; - for (const k in row) { - const val = row[k]; + Object.entries(row).forEach(([key, val]) => { if (['string', 'number'].indexOf(typeof val) >= 0) { - newRow[k] = val; + newRow[key] = val; } else { - newRow[k] = val === null ? null : JSONbig.stringify(val); + newRow[key] = val === null ? null : JSONbig.stringify(val); } - } + }); return newRow; }); - return formattedData; } hasMatch(text: string, row: Datum) { - const values = []; - for (const key in row) { + const values: string[] = []; + Object.keys(row).forEach(key => { if (row.hasOwnProperty(key)) { const cellValue = row[key]; if (typeof cellValue === 'string') { @@ -270,7 +268,7 @@ export default class FilterableTable extends PureComponent< values.push(cellValue.toString()); } } - } + }); const lowerCaseText = text.toLowerCase(); return values.some(v => v.includes(lowerCaseText)); } diff --git a/superset-frontend/src/explore/components/ExploreViewContainer.jsx b/superset-frontend/src/explore/components/ExploreViewContainer.jsx index 22824010ea..7d6b6d11e3 100644 --- a/superset-frontend/src/explore/components/ExploreViewContainer.jsx +++ b/superset-frontend/src/explore/components/ExploreViewContainer.jsx @@ -293,20 +293,18 @@ class ExploreViewContainer extends React.Component { renderErrorMessage() { // Returns an error message as a node if any errors are in the store - const errors = []; - const ctrls = this.props.controls; - for (const controlName in this.props.controls) { - const control = this.props.controls[controlName]; - if (control.validationErrors && control.validationErrors.length > 0) { - errors.push( -
- {t('Control labeled ')} - {` "${control.label}" `} - {control.validationErrors.join('. ')} -
, - ); - } - } + const errors = Object.entries(this.props.controls) + .filter( + ([, control]) => + control.validationErrors && control.validationErrors.length > 0, + ) + .map(([key, control]) => ( +
+ {t('Control labeled ')} + {` "${control.label}" `} + {control.validationErrors.join('. ')} +
+ )); let errorMessage; if (errors.length > 0) { errorMessage =
{errors}
; diff --git a/superset-frontend/src/explore/components/controls/SelectControl.jsx b/superset-frontend/src/explore/components/controls/SelectControl.jsx index 3182434bc8..7ec6c69921 100644 --- a/superset-frontend/src/explore/components/controls/SelectControl.jsx +++ b/superset-frontend/src/explore/components/controls/SelectControl.jsx @@ -103,7 +103,7 @@ export default class SelectControl extends React.PureComponent { if (opt) { if (this.props.multi) { optionValue = []; - for (const o of opt) { + opt.forEach(o => { // select all options if (o.meta === true) { this.props.onChange( @@ -114,7 +114,7 @@ export default class SelectControl extends React.PureComponent { return; } optionValue.push(o[this.props.valueKey] || o); - } + }); } else if (opt.meta === true) { return; } else { diff --git a/superset-frontend/src/explore/controlUtils.js b/superset-frontend/src/explore/controlUtils.js index a273fd46dd..c05e4defcc 100644 --- a/superset-frontend/src/explore/controlUtils.js +++ b/superset-frontend/src/explore/controlUtils.js @@ -51,22 +51,19 @@ export function validateControl(control, processedState) { /** * Find control item from control panel config. */ -function findControlItem(controlPanelSections, controlKey) { - for (const section of controlPanelSections) { - for (const controlArr of section.controlSetRows) { - for (const control of controlArr) { - if (controlKey === control) return control; - if ( - control !== null && - typeof control === 'object' && - control.name === controlKey - ) { - return control; - } - } - } - } - return null; +export function findControlItem(controlPanelSections, controlKey) { + return ( + controlPanelSections + .map(section => section.controlSetRows) + .flat(2) + .find( + control => + controlKey === control || + (control !== null && + typeof control === 'object' && + control.name === controlKey), + ) ?? null + ); } export const getControlConfig = memoizeOne(function getControlConfig( diff --git a/superset-frontend/src/utils/common.js b/superset-frontend/src/utils/common.js index 39a023bbc3..033382294e 100644 --- a/superset-frontend/src/utils/common.js +++ b/superset-frontend/src/utils/common.js @@ -82,14 +82,6 @@ export function getShortUrl(longUrl) { ); } -export function supersetURL(rootUrl, getParams = {}) { - const url = new URL(rootUrl, window.location.origin); - for (const k in getParams) { - url.searchParams.set(k, getParams[k]); - } - return url.href; -} - export function optionLabel(opt) { if (opt === null) { return NULL_STRING; diff --git a/superset-frontend/webpack.config.js b/superset-frontend/webpack.config.js index 081b98fdaa..51e7eb285e 100644 --- a/superset-frontend/webpack.config.js +++ b/superset-frontend/webpack.config.js @@ -78,7 +78,7 @@ const plugins = [ // } // } const entryFiles = {}; - for (const [entry, chunks] of Object.entries(entrypoints)) { + Object.entries(entrypoints).forEach(([entry, chunks]) => { entryFiles[entry] = { css: chunks .filter(x => x.endsWith('.css')) @@ -87,7 +87,8 @@ const plugins = [ .filter(x => x.endsWith('.js')) .map(x => path.join(output.publicPath, x)), }; - } + }); + return { ...seed, entrypoints: entryFiles, @@ -430,7 +431,7 @@ if (isDevMode) { // find all the symlinked plugins and use their source code for imports let hasSymlink = false; - for (const [pkg, version] of Object.entries(packageConfig.dependencies)) { + Object.entries(packageConfig.dependencies).forEach(([pkg, version]) => { const srcPath = `./node_modules/${pkg}/src`; if (/superset-ui/.test(pkg) && fs.existsSync(srcPath)) { console.log( @@ -441,7 +442,7 @@ if (isDevMode) { config.resolve.alias[`${pkg}$`] = `${pkg}/src`; hasSymlink = true; } - } + }); if (hasSymlink) { console.log(''); // pure cosmetic new line }