chore: refine prettier config as the main repository (#1456)

* change to prettier.config.js

* printWidth to 80

* format

* tweak
This commit is contained in:
Yongjie Zhao 2021-11-09 12:42:28 +00:00
parent 3cc4861a76
commit c78551df4f
455 changed files with 6577 additions and 2072 deletions

View File

@ -17,7 +17,12 @@
* under the License.
*/
module.exports = {
extends: ['airbnb', 'prettier', 'prettier/react', 'plugin:react-hooks/recommended'],
extends: [
'airbnb',
'prettier',
'prettier/react',
'plugin:react-hooks/recommended',
],
parser: '@babel/eslint-parser',
parserOptions: {
ecmaFeatures: {
@ -82,7 +87,8 @@ module.exports = {
paths: [
{
name: 'antd',
message: 'Please import Ant components from the index of common/components',
message:
'Please import Ant components from the index of common/components',
},
],
},
@ -196,7 +202,10 @@ module.exports = {
rules: {
// this is to keep eslint from complaining about storybook addons,
// since they are included as dev dependencies rather than direct dependencies.
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
'import/no-extraneous-dependencies': [
'error',
{ devDependencies: true },
],
},
},
{

View File

@ -1,13 +0,0 @@
{
"arrowParens": "avoid",
"bracketSpacing": true,
"bracketSameLine": false,
"printWidth": 100,
"proseWrap": "always",
"requirePragma": false,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"useTabs": false
}

View File

@ -18,5 +18,8 @@
*/
module.exports = {
extends: ['@commitlint/config-conventional', '@commitlint/config-lerna-scopes'],
extends: [
'@commitlint/config-conventional',
'@commitlint/config-lerna-scopes',
],
};

View File

@ -19,7 +19,10 @@
module.exports = {
bail: false,
collectCoverageFrom: ['**/src/**/*.{ts,tsx,js,jsx}', '**/test/**/*.{ts,tsx,js,jsx}'],
collectCoverageFrom: [
'**/src/**/*.{ts,tsx,js,jsx}',
'**/test/**/*.{ts,tsx,js,jsx}',
],
coverageDirectory: './coverage',
coveragePathIgnorePatterns: [
'coverage/',

View File

@ -26,7 +26,9 @@ const yosay = require('yosay');
module.exports = class extends Generator {
async prompting() {
// Have Yeoman greet the user.
this.log(yosay(`Welcome to the rad ${chalk.red('generator-superset')} generator!`));
this.log(
yosay(`Welcome to the rad ${chalk.red('generator-superset')} generator!`),
);
this.option('skipInstall');

View File

@ -66,7 +66,10 @@ module.exports = class extends Generator {
this.answers,
);
const ext = this.answers.language === 'typescript' ? 'ts' : 'js';
this.fs.copy(this.templatePath('src/index.txt'), this.destinationPath(`src/index.${ext}`));
this.fs.copy(
this.templatePath('src/index.txt'),
this.destinationPath(`src/index.${ext}`),
);
this.fs.copy(
this.templatePath('test/index.txt'),
this.destinationPath(`test/index.test.${ext}`),

View File

@ -39,7 +39,9 @@ module.exports = class extends Generator {
name: 'description',
message: 'Description:',
// Default to current folder name
default: _.upperFirst(_.startCase(this.appname.replace('plugin chart', '').trim())),
default: _.upperFirst(
_.startCase(this.appname.replace('plugin chart', '').trim()),
),
},
{
type: 'list',
@ -106,9 +108,16 @@ module.exports = class extends Generator {
['src/MyChart.erb', `src/${packageLabel}.tsx`],
['test/index.erb', 'test/index.test.ts'],
['test/plugin/buildQuery.test.erb', 'test/plugin/buildQuery.test.ts'],
['test/plugin/transformProps.test.erb', 'test/plugin/transformProps.test.ts'],
[
'test/plugin/transformProps.test.erb',
'test/plugin/transformProps.test.ts',
],
].forEach(([src, dest]) => {
this.fs.copyTpl(this.templatePath(src), this.destinationPath(dest), params);
this.fs.copyTpl(
this.templatePath(src),
this.destinationPath(dest),
params,
);
});
['types/external.d.ts', 'src/images/thumbnail.png'].forEach(file => {

View File

@ -49,6 +49,11 @@ describe('generator-superset:app', () => {
});
it('creates files', () => {
assert.file(['package.json', 'README.md', 'src/index.ts', 'test/index.test.ts']);
assert.file([
'package.json',
'README.md',
'src/index.ts',
'test/index.test.ts',
]);
});
});

View File

@ -52,7 +52,12 @@ describe('generator-superset:package', () => {
);
it('creates files', () => {
assert.file(['package.json', 'README.md', 'src/index.ts', 'test/index.test.ts']);
assert.file([
'package.json',
'README.md',
'src/index.ts',
'test/index.test.ts',
]);
});
});
@ -65,7 +70,12 @@ describe('generator-superset:package', () => {
);
it('creates files', () => {
assert.file(['package.json', 'README.md', 'src/index.js', 'test/index.test.js']);
assert.file([
'package.json',
'README.md',
'src/index.js',
'test/index.test.js',
]);
});
});
});

View File

@ -42,7 +42,9 @@ function CertifiedIconWithTooltip({
id={`${kebabCase(metricName)}-tooltip`}
title={
<div>
{certifiedBy && <StyledDiv>{t('Certified by %s', certifiedBy)}</StyledDiv>}
{certifiedBy && (
<StyledDiv>{t('Certified by %s', certifiedBy)}</StyledDiv>
)}
<div>{details}</div>
</div>
}

View File

@ -64,7 +64,10 @@ export function ColumnOption({
trigger={['hover']}
placement="top"
>
<span className="m-r-5 option-label column-option-label" ref={labelRef}>
<span
className="m-r-5 option-label column-option-label"
ref={labelRef}
>
{column.verbose_name || column.column_name}
</span>
</Tooltip>

View File

@ -29,7 +29,8 @@ export type ControlFormItemProps = ControlFormItemSpec & {
onChange?: (fieldValue: JsonValue) => void;
};
export type ControlFormItemNode = FunctionComponentElement<ControlFormItemProps>;
export type ControlFormItemNode =
FunctionComponentElement<ControlFormItemProps>;
/**
* Accept `false` or `0`, but not empty string.
@ -53,7 +54,9 @@ export function ControlFormItem({
}: ControlFormItemProps) {
const { gridUnit } = useTheme();
const [hovered, setHovered] = useState(false);
const [value, setValue] = useState(initialValue === undefined ? defaultValue : initialValue);
const [value, setValue] = useState(
initialValue === undefined ? defaultValue : initialValue,
);
const [validationErrors, setValidationErrors] =
useState<ControlHeaderProps['validationErrors']>();
@ -66,7 +69,9 @@ export function ControlFormItem({
: e;
const errors =
(validators
?.map(validator => (!required && isEmptyValue(fieldValue) ? false : validator(fieldValue)))
?.map(validator =>
!required && isEmptyValue(fieldValue) ? false : validator(fieldValue),
)
.filter(x => !!x) as string[]) || [];
setValidationErrors(errors);
setValue(fieldValue);
@ -87,8 +92,14 @@ export function ControlFormItem({
onMouseLeave={() => setHovered(false)}
>
{controlType === 'Checkbox' ? (
<ControlFormItemComponents.Checkbox checked={value as boolean} onChange={handleChange}>
{label} {hovered && description && <InfoTooltipWithTrigger tooltip={description} />}
<ControlFormItemComponents.Checkbox
checked={value as boolean}
onChange={handleChange}
>
{label}{' '}
{hovered && description && (
<InfoTooltipWithTrigger tooltip={description} />
)}
</ControlFormItemComponents.Checkbox>
) : (
<>

View File

@ -17,7 +17,12 @@
* under the License.
*/
import React, { FunctionComponentElement, useMemo } from 'react';
import { FAST_DEBOUNCE, JsonObject, JsonValue, useTheme } from '@superset-ui/core';
import {
FAST_DEBOUNCE,
JsonObject,
JsonValue,
useTheme,
} from '@superset-ui/core';
import { debounce } from 'lodash';
import { ControlFormItemNode } from './ControlFormItem';
@ -57,7 +62,11 @@ export type ControlFormProps = {
/**
* Light weight form for control panel.
*/
export default function ControlForm({ onChange, value, children }: ControlFormProps) {
export default function ControlForm({
onChange,
value,
children,
}: ControlFormProps) {
const theme = useTheme();
const debouncedOnChange = useMemo(
() =>
@ -94,7 +103,10 @@ export default function ControlForm({ onChange, value, children }: ControlFormPr
}
// propagate to the form
if (!(debounceDelay in debouncedOnChange)) {
debouncedOnChange[debounceDelay] = debounce(onChange, debounceDelay);
debouncedOnChange[debounceDelay] = debounce(
onChange,
debounceDelay,
);
}
debouncedOnChange[debounceDelay]({
...value,

View File

@ -118,7 +118,11 @@ export default function ControlHeader({
)}
{validationErrors.length > 0 && (
<span>
<Tooltip id="error-tooltip" placement="top" title={validationErrors.join(' ')}>
<Tooltip
id="error-tooltip"
placement="top"
title={validationErrors.join(' ')}
>
<i className="fa fa-exclamation-circle text-danger" />
</Tooltip>{' '}
</span>

View File

@ -41,7 +41,9 @@ export function InfoTooltipWithTrigger({
className = 'text-muted',
placement = 'right',
}: InfoTooltipWithTriggerProps) {
const iconClass = `fa fa-${icon} ${className} ${bsStyle ? `text-${bsStyle}` : ''}`;
const iconClass = `fa fa-${icon} ${className} ${
bsStyle ? `text-${bsStyle}` : ''
}`;
const iconEl = (
<i
role="button"
@ -64,7 +66,11 @@ export function InfoTooltipWithTrigger({
return iconEl;
}
return (
<Tooltip id={`${kebabCase(label)}-tooltip`} title={tooltip} placement={placement}>
<Tooltip
id={`${kebabCase(label)}-tooltip`}
title={tooltip}
placement={placement}
>
{iconEl}
</Tooltip>
);

View File

@ -73,7 +73,12 @@ export function MetricOption({
/>
)}
{showTooltip ? (
<Tooltip id="metric-name-tooltip" title={verbose} trigger={['hover']} placement="top">
<Tooltip
id="metric-name-tooltip"
title={verbose}
trigger={['hover']}
placement="top"
>
<span className="option-label metric-option-label" ref={labelRef}>
{link}
</span>

View File

@ -43,7 +43,11 @@ export const Tooltip = ({ overlayStyle, color, ...props }: TooltipProps) => {
`}
/>
<BaseTooltip
overlayStyle={{ fontSize: theme.typography.sizes.s, lineHeight: '1.6', ...overlayStyle }}
overlayStyle={{
fontSize: theme.typography.sizes.s,
lineHeight: '1.6',
...overlayStyle,
}}
color={defaultColor || color}
{...props}
/>

View File

@ -36,7 +36,9 @@ export const TIME_COLUMN_OPTION: ColumnMeta = {
verbose_name: COLUMN_NAME_ALIASES[DTTM_ALIAS],
column_name: DTTM_ALIAS,
type_generic: GenericDataType.TEMPORAL,
description: t('A reference to the [Time] configuration, taking granularity into account'),
description: t(
'A reference to the [Time] configuration, taking granularity into account',
),
};
export const QueryModeLabel = {

View File

@ -31,7 +31,11 @@ export * from './components/ColumnTypeLabel';
export * from './components/MetricOption';
// React control components
export { sharedControls, dndEntity, dndColumnsControl } from './shared-controls';
export {
sharedControls,
dndEntity,
dndColumnsControl,
} from './shared-controls';
export { default as sharedControlComponents } from './shared-controls/components';
export { legacySortBy } from './shared-controls/legacySortBy';
export * from './shared-controls/emitFilterControl';

View File

@ -16,16 +16,20 @@
* specific language governing permissions and limitationsxw
* under the License.
*/
import { PostProcessingBoxplot, getMetricLabel, ensureIsArray } from '@superset-ui/core';
import {
PostProcessingBoxplot,
getMetricLabel,
ensureIsArray,
} from '@superset-ui/core';
import { PostProcessingFactory } from './types';
type BoxPlotQueryObjectWhiskerType = PostProcessingBoxplot['options']['whisker_type'];
type BoxPlotQueryObjectWhiskerType =
PostProcessingBoxplot['options']['whisker_type'];
const PERCENTILE_REGEX = /(\d+)\/(\d+) percentiles/;
export const boxplotOperator: PostProcessingFactory<PostProcessingBoxplot | undefined> = (
formData,
queryObject,
) => {
export const boxplotOperator: PostProcessingFactory<
PostProcessingBoxplot | undefined
> = (formData, queryObject) => {
const { groupby, whiskerOptions } = formData;
if (whiskerOptions) {
@ -39,7 +43,10 @@ export const boxplotOperator: PostProcessingFactory<PostProcessingBoxplot | unde
whiskerType = 'min/max';
} else if (percentileMatch) {
whiskerType = 'percentile';
percentiles = [parseInt(percentileMatch[1], 10), parseInt(percentileMatch[2], 10)];
percentiles = [
parseInt(percentileMatch[1], 10),
parseInt(percentileMatch[2], 10),
];
} else {
throw new Error(`Unsupported whisker type: ${whiskerOptions}`);
}

View File

@ -19,10 +19,9 @@
import { PostProcessingContribution } from '@superset-ui/core';
import { PostProcessingFactory } from './types';
export const contributionOperator: PostProcessingFactory<PostProcessingContribution | undefined> = (
formData,
queryObject,
) => {
export const contributionOperator: PostProcessingFactory<
PostProcessingContribution | undefined
> = (formData, queryObject) => {
if (formData.contributionMode) {
return {
operation: 'contribution',

View File

@ -16,15 +16,18 @@
* specific language governing permissions and limitationsxw
* under the License.
*/
import { ensureIsArray, getMetricLabel, PostProcessingPivot } from '@superset-ui/core';
import {
ensureIsArray,
getMetricLabel,
PostProcessingPivot,
} from '@superset-ui/core';
import { PostProcessingFactory } from './types';
import { TIME_COLUMN, isValidTimeCompare } from './utils';
import { timeComparePivotOperator } from './timeComparePivotOperator';
export const pivotOperator: PostProcessingFactory<PostProcessingPivot | undefined> = (
formData,
queryObject,
) => {
export const pivotOperator: PostProcessingFactory<
PostProcessingPivot | undefined
> = (formData, queryObject) => {
const metricLabels = ensureIsArray(queryObject.metrics).map(getMetricLabel);
if (queryObject.is_timeseries && metricLabels.length) {
if (isValidTimeCompare(formData, queryObject)) {
@ -38,7 +41,9 @@ export const pivotOperator: PostProcessingFactory<PostProcessingPivot | undefine
columns: queryObject.columns || [],
// Create 'dummy' mean aggregates to assign cell values in pivot table
// use the 'mean' aggregates to avoid drop NaN. PR: https://github.com/apache-superset/superset-ui/pull/1231
aggregates: Object.fromEntries(metricLabels.map(metric => [metric, { operator: 'mean' }])),
aggregates: Object.fromEntries(
metricLabels.map(metric => [metric, { operator: 'mean' }]),
),
drop_missing_columns: false,
},
};

View File

@ -19,10 +19,9 @@
import { PostProcessingProphet } from '@superset-ui/core';
import { PostProcessingFactory } from './types';
export const prophetOperator: PostProcessingFactory<PostProcessingProphet | undefined> = (
formData,
queryObject,
) => {
export const prophetOperator: PostProcessingFactory<
PostProcessingProphet | undefined
> = (formData, queryObject) => {
if (formData.forecastEnabled) {
return {
operation: 'prophet',

View File

@ -21,10 +21,9 @@ import { PostProcessingResample } from '@superset-ui/core';
import { PostProcessingFactory } from './types';
import { TIME_COLUMN } from './utils';
export const resampleOperator: PostProcessingFactory<PostProcessingResample | undefined> = (
formData,
queryObject,
) => {
export const resampleOperator: PostProcessingFactory<
PostProcessingResample | undefined
> = (formData, queryObject) => {
const resampleZeroFill = formData.resample_method === 'zerofill';
const resampleMethod = resampleZeroFill ? 'asfreq' : formData.resample_method;
const resampleRule = formData.resample_rule;

View File

@ -25,7 +25,11 @@ import {
PostProcessingCum,
ComparisionType,
} from '@superset-ui/core';
import { getMetricOffsetsMap, isValidTimeCompare, TIME_COMPARISON_SEPARATOR } from './utils';
import {
getMetricOffsetsMap,
isValidTimeCompare,
TIME_COMPARISON_SEPARATOR,
} from './utils';
import { PostProcessingFactory } from './types';
export const rollingWindowOperator: PostProcessingFactory<
@ -37,7 +41,10 @@ export const rollingWindowOperator: PostProcessingFactory<
const comparisonType = formData.comparison_type;
if (comparisonType === ComparisionType.Values) {
// time compare type: actual values
columns = [...Array.from(metricsMap.values()), ...Array.from(metricsMap.keys())];
columns = [
...Array.from(metricsMap.values()),
...Array.from(metricsMap.keys()),
];
} else {
// time compare type: difference / percentage / ratio
columns = Array.from(metricsMap.entries()).map(([offset, metric]) =>
@ -65,7 +72,11 @@ export const rollingWindowOperator: PostProcessingFactory<
};
}
if ([RollingType.Sum, RollingType.Mean, RollingType.Std].includes(formData.rolling_type)) {
if (
[RollingType.Sum, RollingType.Mean, RollingType.Std].includes(
formData.rolling_type,
)
) {
return {
operation: 'rolling',
options: {

View File

@ -21,11 +21,13 @@ import { PostProcessingSort, RollingType } from '@superset-ui/core';
import { PostProcessingFactory } from './types';
import { TIME_COLUMN } from './utils';
export const sortOperator: PostProcessingFactory<PostProcessingSort | undefined> = (
formData,
queryObject,
) => {
if (queryObject.is_timeseries && Object.values(RollingType).includes(formData.rolling_type)) {
export const sortOperator: PostProcessingFactory<
PostProcessingSort | undefined
> = (formData, queryObject) => {
if (
queryObject.is_timeseries &&
Object.values(RollingType).includes(formData.rolling_type)
) {
return {
operation: 'sort',
options: {

View File

@ -21,14 +21,16 @@ import { ComparisionType, PostProcessingCompare } from '@superset-ui/core';
import { getMetricOffsetsMap, isValidTimeCompare } from './utils';
import { PostProcessingFactory } from './types';
export const timeCompareOperator: PostProcessingFactory<PostProcessingCompare | undefined> = (
formData,
queryObject,
) => {
export const timeCompareOperator: PostProcessingFactory<
PostProcessingCompare | undefined
> = (formData, queryObject) => {
const comparisonType = formData.comparison_type;
const metricOffsetMap = getMetricOffsetsMap(formData, queryObject);
if (isValidTimeCompare(formData, queryObject) && comparisonType !== ComparisionType.Values) {
if (
isValidTimeCompare(formData, queryObject) &&
comparisonType !== ComparisionType.Values
) {
return {
operation: 'compare',
options: {

View File

@ -17,14 +17,21 @@
* specific language governing permissions and limitationsxw
* under the License.
*/
import { ComparisionType, PostProcessingPivot, NumpyFunction } from '@superset-ui/core';
import { getMetricOffsetsMap, isValidTimeCompare, TIME_COMPARISON_SEPARATOR } from './utils';
import {
ComparisionType,
PostProcessingPivot,
NumpyFunction,
} from '@superset-ui/core';
import {
getMetricOffsetsMap,
isValidTimeCompare,
TIME_COMPARISON_SEPARATOR,
} from './utils';
import { PostProcessingFactory } from './types';
export const timeComparePivotOperator: PostProcessingFactory<PostProcessingPivot | undefined> = (
formData,
queryObject,
) => {
export const timeComparePivotOperator: PostProcessingFactory<
PostProcessingPivot | undefined
> = (formData, queryObject) => {
const comparisonType = formData.comparison_type;
const metricOffsetMap = getMetricOffsetsMap(formData, queryObject);
@ -38,7 +45,9 @@ export const timeComparePivotOperator: PostProcessingFactory<PostProcessingPivot
);
const changeAgg = Object.fromEntries(
[...metricOffsetMap.entries()]
.map(([offset, metric]) => [comparisonType, metric, offset].join(TIME_COMPARISON_SEPARATOR))
.map(([offset, metric]) =>
[comparisonType, metric, offset].join(TIME_COMPARISON_SEPARATOR),
)
// use the 'mean' aggregates to avoid drop NaN
.map(metric => [metric, { operator: 'mean' as NumpyFunction }]),
);
@ -48,7 +57,8 @@ export const timeComparePivotOperator: PostProcessingFactory<PostProcessingPivot
options: {
index: ['__timestamp'],
columns: queryObject.columns || [],
aggregates: comparisonType === ComparisionType.Values ? valuesAgg : changeAgg,
aggregates:
comparisonType === ComparisionType.Values ? valuesAgg : changeAgg,
drop_missing_columns: false,
},
};

View File

@ -39,7 +39,10 @@ export const getMetricOffsetsMap: PostProcessingFactory<Map<string, string>> = (
const metricOffsetMap = new Map<string, string>();
metricLabels.forEach((metric: string) => {
timeOffsets.forEach((offset: string) => {
metricOffsetMap.set([metric, offset].join(TIME_COMPARISON_SEPARATOR), metric);
metricOffsetMap.set(
[metric, offset].join(TIME_COMPARISON_SEPARATOR),
metric,
);
});
});

View File

@ -21,9 +21,15 @@ import { ComparisionType } from '@superset-ui/core';
import { getMetricOffsetsMap } from './getMetricOffsetsMap';
import { PostProcessingFactory } from '../types';
export const isValidTimeCompare: PostProcessingFactory<boolean> = (formData, queryObject) => {
export const isValidTimeCompare: PostProcessingFactory<boolean> = (
formData,
queryObject,
) => {
const comparisonType = formData.comparison_type;
const metricOffsetMap = getMetricOffsetsMap(formData, queryObject);
return Object.values(ComparisionType).includes(comparisonType) && metricOffsetMap.size > 0;
return (
Object.values(ComparisionType).includes(comparisonType) &&
metricOffsetMap.size > 0
);
};

View File

@ -38,7 +38,9 @@ export const advancedAnalyticsControls: ControlPanelSectionConfig = {
type: 'SelectControl',
label: t('Rolling function'),
default: null,
choices: [[null, t('None')]].concat(formatSelectOptions(Object.values(RollingType))),
choices: [[null, t('None')]].concat(
formatSelectOptions(Object.values(RollingType)),
),
description: t(
'Defines a rolling window function to apply, works along ' +
'with the [Periods] text box',

View File

@ -16,7 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
import { legacyValidateInteger, legacyValidateNumber, t } from '@superset-ui/core';
import {
legacyValidateInteger,
legacyValidateNumber,
t,
} from '@superset-ui/core';
import { ControlPanelSectionConfig } from '../types';
export const FORECAST_DEFAULT_DATA = {
@ -52,7 +56,9 @@ export const forecastIntervalControls: ControlPanelSectionConfig = {
label: t('Forecast periods'),
validators: [legacyValidateInteger],
default: FORECAST_DEFAULT_DATA.forecastPeriods,
description: t('How many periods into the future do we want to predict'),
description: t(
'How many periods into the future do we want to predict',
),
},
},
],
@ -64,7 +70,9 @@ export const forecastIntervalControls: ControlPanelSectionConfig = {
label: t('Confidence interval'),
validators: [legacyValidateNumber],
default: FORECAST_DEFAULT_DATA.forecastInterval,
description: t('Width of the confidence interval. Should be between 0 and 1'),
description: t(
'Width of the confidence interval. Should be between 0 and 1',
),
},
},
{

View File

@ -75,7 +75,9 @@ export const datasourceAndVizType: ControlPanelSectionConfig = {
type: 'HiddenControl',
label: t('URL Parameters'),
hidden: true,
description: t('Extra url parameters for use in Jinja templated queries'),
description: t(
'Extra url parameters for use in Jinja templated queries',
),
},
},
{

View File

@ -17,23 +17,31 @@
* under the License.
*/
import React, { useMemo, useState } from 'react';
import { ChartDataResponseResult, useTheme, t, GenericDataType } from '@superset-ui/core';
import {
ChartDataResponseResult,
useTheme,
t,
GenericDataType,
} from '@superset-ui/core';
import ControlHeader from '../../../components/ControlHeader';
import { ControlComponentProps } from '../types';
import ColumnConfigItem from './ColumnConfigItem';
import { ColumnConfigInfo, ColumnConfig, ColumnConfigFormLayout } from './types';
import {
ColumnConfigInfo,
ColumnConfig,
ColumnConfigFormLayout,
} from './types';
import { DEFAULT_CONFIG_FORM_LAYOUT } from './constants';
import { COLUMN_NAME_ALIASES } from '../../../constants';
export type ColumnConfigControlProps<T extends ColumnConfig> = ControlComponentProps<
Record<string, T>
> & {
queryResponse?: ChartDataResponseResult;
configFormLayout?: ColumnConfigFormLayout;
appliedColumnNames?: string[];
emitFilter: boolean;
};
export type ColumnConfigControlProps<T extends ColumnConfig> =
ControlComponentProps<Record<string, T>> & {
queryResponse?: ChartDataResponseResult;
configFormLayout?: ColumnConfigFormLayout;
appliedColumnNames?: string[];
emitFilter: boolean;
};
/**
* Max number of columns to show by default.
@ -102,7 +110,9 @@ export default function ColumnConfigControl<T extends ColumnConfig>({
// Only keep configs for known columns
const validConfigs: Record<string, T> =
colnames && value
? Object.fromEntries(Object.entries(value).filter(([key]) => colnames.includes(key)))
? Object.fromEntries(
Object.entries(value).filter(([key]) => colnames.includes(key)),
)
: { ...value };
onChange({
...validConfigs,
@ -114,7 +124,10 @@ export default function ColumnConfigControl<T extends ColumnConfig>({
if (!colnames || colnames.length === 0) return null;
const needShowMoreButton = colnames.length > MAX_NUM_COLS + 2;
const cols = needShowMoreButton && !showAllColumns ? colnames.slice(0, MAX_NUM_COLS) : colnames;
const cols =
needShowMoreButton && !showAllColumns
? colnames.slice(0, MAX_NUM_COLS)
: colnames;
return (
<>

View File

@ -20,7 +20,9 @@ import React from 'react';
import { useTheme } from '@superset-ui/core';
import { Popover } from 'antd';
import ColumnTypeLabel from '../../../components/ColumnTypeLabel';
import ColumnConfigPopover, { ColumnConfigPopoverProps } from './ColumnConfigPopover';
import ColumnConfigPopover, {
ColumnConfigPopoverProps,
} from './ColumnConfigPopover';
export type ColumnConfigItemProps = ColumnConfigPopoverProps;

View File

@ -23,8 +23,15 @@ import ControlForm, {
ControlFormItem,
ControlFormItemSpec,
} from '../../../components/ControlForm';
import { SHARED_COLUMN_CONFIG_PROPS, SharedColumnConfigProp } from './constants';
import { ColumnConfig, ColumnConfigFormLayout, ColumnConfigInfo } from './types';
import {
SHARED_COLUMN_CONFIG_PROPS,
SharedColumnConfigProp,
} from './constants';
import {
ColumnConfig,
ColumnConfigFormLayout,
ColumnConfigInfo,
} from './types';
export type ColumnConfigPopoverProps = {
column: ColumnConfigInfo;
@ -39,24 +46,28 @@ export default function ColumnConfigPopover({
}: ColumnConfigPopoverProps) {
return (
<ControlForm onChange={onChange} value={column.config}>
{configFormLayout[column.type === undefined ? GenericDataType.STRING : column.type].map(
(row, i) => (
<ControlFormRow key={i}>
{row.map(meta => {
const key = typeof meta === 'string' ? meta : meta.name;
const override =
typeof meta === 'string' ? {} : 'override' in meta ? meta.override : meta.config;
const props = {
...(key in SHARED_COLUMN_CONFIG_PROPS
? SHARED_COLUMN_CONFIG_PROPS[key as SharedColumnConfigProp]
: undefined),
...override,
} as ControlFormItemSpec;
return <ControlFormItem key={key} name={key} {...props} />;
})}
</ControlFormRow>
),
)}
{configFormLayout[
column.type === undefined ? GenericDataType.STRING : column.type
].map((row, i) => (
<ControlFormRow key={i}>
{row.map(meta => {
const key = typeof meta === 'string' ? meta : meta.name;
const override =
typeof meta === 'string'
? {}
: 'override' in meta
? meta.override
: meta.config;
const props = {
...(key in SHARED_COLUMN_CONFIG_PROPS
? SHARED_COLUMN_CONFIG_PROPS[key as SharedColumnConfigProp]
: undefined),
...override,
} as ControlFormItemSpec;
return <ControlFormItem key={key} name={key} {...props} />;
})}
</ControlFormRow>
))}
</ControlForm>
);
}

View File

@ -125,7 +125,9 @@ const showCellBars: ControlFormItemSpec<'Checkbox'> = {
const alignPositiveNegative: ControlFormItemSpec<'Checkbox'> = {
controlType: 'Checkbox',
label: t('Align +/-'),
description: t('Whether to align positive and negative values in cell bar chart at 0'),
description: t(
'Whether to align positive and negative values in cell bar chart at 0',
),
defaultValue: false,
debounceDelay: 200,
};
@ -133,7 +135,9 @@ const alignPositiveNegative: ControlFormItemSpec<'Checkbox'> = {
const colorPositiveNegative: ControlFormItemSpec<'Checkbox'> = {
controlType: 'Checkbox',
label: t('Color +/-'),
description: t('Whether to colorize numeric values by if they are positive or negative'),
description: t(
'Whether to colorize numeric values by if they are positive or negative',
),
defaultValue: false,
debounceDelay: 200,
};
@ -167,20 +171,32 @@ export type SharedColumnConfig = {
export const DEFAULT_CONFIG_FORM_LAYOUT: ColumnConfigFormLayout = {
[GenericDataType.STRING]: [
['columnWidth', { name: 'horizontalAlign', override: { defaultValue: 'left' } }],
[
'columnWidth',
{ name: 'horizontalAlign', override: { defaultValue: 'left' } },
],
],
[GenericDataType.NUMERIC]: [
['columnWidth', { name: 'horizontalAlign', override: { defaultValue: 'right' } }],
[
'columnWidth',
{ name: 'horizontalAlign', override: { defaultValue: 'right' } },
],
['d3NumberFormat'],
['d3SmallNumberFormat'],
['alignPositiveNegative', 'colorPositiveNegative'],
['showCellBars'],
],
[GenericDataType.TEMPORAL]: [
['columnWidth', { name: 'horizontalAlign', override: { defaultValue: 'left' } }],
[
'columnWidth',
{ name: 'horizontalAlign', override: { defaultValue: 'left' } },
],
['d3TimeFormat'],
],
[GenericDataType.BOOLEAN]: [
['columnWidth', { name: 'horizontalAlign', override: { defaultValue: 'left' } }],
[
'columnWidth',
{ name: 'horizontalAlign', override: { defaultValue: 'left' } },
],
],
};

View File

@ -16,9 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
import { GenericDataType, JsonObject, StrictJsonValue } from '@superset-ui/core';
import {
GenericDataType,
JsonObject,
StrictJsonValue,
} from '@superset-ui/core';
import { ControlFormItemSpec } from '../../../components/ControlForm';
import { SHARED_COLUMN_CONFIG_PROPS, SharedColumnConfigProp } from './constants';
import {
SHARED_COLUMN_CONFIG_PROPS,
SharedColumnConfigProp,
} from './constants';
/**
* Column formatting configs.
@ -42,6 +49,9 @@ export type ColumnConfigFormItem =
| { name: SharedColumnConfigProp; override: Partial<ControlFormItemSpec> }
| { name: string; config: ControlFormItemSpec };
export type ColumnConfigFormLayout = Record<GenericDataType, ColumnConfigFormItem[][]>;
export type ColumnConfigFormLayout = Record<
GenericDataType,
ColumnConfigFormItem[][]
>;
export default {};

View File

@ -21,7 +21,10 @@ import { JsonValue, useTheme } from '@superset-ui/core';
import ControlHeader from '../../components/ControlHeader';
// [value, label]
export type RadioButtonOption = [JsonValue, Exclude<ReactNode, null | undefined | boolean>];
export type RadioButtonOption = [
JsonValue,
Exclude<ReactNode, null | undefined | boolean>,
];
export interface RadioButtonControlProps {
label?: ReactNode;
@ -66,7 +69,9 @@ export default function RadioButtonControl({
<button
key={JSON.stringify(val)}
type="button"
className={`btn btn-default ${val === currentValue ? 'active' : ''}`}
className={`btn btn-default ${
val === currentValue ? 'active' : ''
}`}
onClick={() => {
onChange(val);
}}

View File

@ -24,7 +24,9 @@ import { ReactNode } from 'react';
*
* Ref: superset-frontend/src/explore/components/Control.tsx
*/
export interface ControlComponentProps<ValueType extends JsonValue = JsonValue> {
export interface ControlComponentProps<
ValueType extends JsonValue = JsonValue,
> {
name: string;
label?: ReactNode;
description?: ReactNode;

View File

@ -17,7 +17,12 @@
* specific language governing permissions and limitations
* under the License.
*/
import { FeatureFlag, isFeatureEnabled, t, validateNonEmpty } from '@superset-ui/core';
import {
FeatureFlag,
isFeatureEnabled,
t,
validateNonEmpty,
} from '@superset-ui/core';
import { ExtraControlProps, SharedControlConfig } from '../types';
import { TIME_COLUMN_OPTION, TIME_FILTER_LABELS } from '../constants';
@ -36,7 +41,9 @@ export const dndGroupByControl: SharedControlConfig<'DndColumnSelect'> = {
if (includeTime) {
options.unshift(TIME_COLUMN_OPTION);
}
newState.options = Object.fromEntries(options.map(option => [option.column_name, option]));
newState.options = Object.fromEntries(
options.map(option => [option.column_name, option]),
);
newState.savedMetrics = state.datasource.metrics || [];
}
return newState;
@ -79,7 +86,8 @@ export const dnd_adhoc_filters: SharedControlConfig<'DndFilterSelect'> = {
columns: datasource?.columns.filter(c => c.filterable) || [],
savedMetrics: datasource?.metrics || [],
// current active adhoc metrics
selectedMetrics: form_data.metrics || (form_data.metric ? [form_data.metric] : []),
selectedMetrics:
form_data.metrics || (form_data.metric ? [form_data.metric] : []),
datasource,
}),
provideFormDataToProps: true,
@ -171,10 +179,13 @@ export const dnd_granularity_sqla: typeof dndGroupByControl = {
),
mapStateToProps: ({ datasource }) => {
const temporalColumns = datasource?.columns.filter(c => c.is_dttm) ?? [];
const options = Object.fromEntries(temporalColumns.map(option => [option.column_name, option]));
const options = Object.fromEntries(
temporalColumns.map(option => [option.column_name, option]),
);
return {
options,
default: datasource?.main_dttm_col || temporalColumns[0]?.column_name || null,
default:
datasource?.main_dttm_col || temporalColumns[0]?.column_name || null,
};
},
};

View File

@ -111,8 +111,10 @@ const groupByControl: SharedControlConfig<'SelectControl', ColumnMeta> = {
valueKey: 'column_name',
allowAll: true,
filterOption: ({ data: opt }, text: string) =>
(opt.column_name && opt.column_name.toLowerCase().includes(text.toLowerCase())) ||
(opt.verbose_name && opt.verbose_name.toLowerCase().includes(text.toLowerCase())) ||
(opt.column_name &&
opt.column_name.toLowerCase().includes(text.toLowerCase())) ||
(opt.verbose_name &&
opt.verbose_name.toLowerCase().includes(text.toLowerCase())) ||
false,
promptTextCreator: (label: unknown) => label,
mapStateToProps(state, { includeTime }) {
@ -187,7 +189,10 @@ const linear_color_scheme: SharedControlConfig<'ColorSchemeControl'> = {
type: 'ColorSchemeControl',
label: t('Linear Color Scheme'),
choices: () =>
(sequentialSchemeRegistry.values() as SequentialScheme[]).map(value => [value.id, value.label]),
(sequentialSchemeRegistry.values() as SequentialScheme[]).map(value => [
value.id,
value.label,
]),
default: sequentialSchemeRegistry.getDefaultKey(),
clearable: false,
description: '',
@ -463,7 +468,8 @@ const adhoc_filters: SharedControlConfig<'AdhocFilterControl'> = {
columns: datasource?.columns.filter(c => c.filterable) || [],
savedMetrics: datasource?.metrics || [],
// current active adhoc metrics
selectedMetrics: form_data.metrics || (form_data.metric ? [form_data.metric] : []),
selectedMetrics:
form_data.metrics || (form_data.metric ? [form_data.metric] : []),
datasource,
}),
};
@ -478,7 +484,9 @@ const color_scheme: SharedControlConfig<'ColorSchemeControl'> = {
schemes: () => categoricalSchemeRegistry.getMap(),
};
const enableExploreDnd = isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP);
const enableExploreDnd = isFeatureEnabled(
FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP,
);
const sharedControls = {
metrics: enableExploreDnd ? dnd_adhoc_metrics : metrics,

View File

@ -18,7 +18,13 @@
* under the License.
*/
import React, { ReactNode, ReactText, ReactElement } from 'react';
import { QueryFormData, DatasourceType, Metric, JsonValue, Column } from '@superset-ui/core';
import {
QueryFormData,
DatasourceType,
Metric,
JsonValue,
Column,
} from '@superset-ui/core';
import { sharedControls } from './shared-controls';
import sharedControlComponents from './shared-controls/components';
@ -70,7 +76,10 @@ export interface ControlPanelState {
* The action dispather will call Redux `dispatch` internally and return what's
* returned from `dispatch`, which by default is the original or another action.
*/
export interface ActionDispatcher<ARGS extends unknown[], A extends Action = AnyAction> {
export interface ActionDispatcher<
ARGS extends unknown[],
A extends Action = AnyAction,
> {
(...args: ARGS): A;
}
@ -89,11 +98,10 @@ export type ExtraControlProps = {
} & AnyDict;
// Ref:superset-frontend/src/explore/store.js
export type ControlState<T = ControlType, O extends SelectOption = SelectOption> = ControlConfig<
T,
O
> &
ExtraControlProps;
export type ControlState<
T = ControlType,
O extends SelectOption = SelectOption,
> = ControlConfig<T, O> & ExtraControlProps;
export interface ControlStateMapping {
[key: string]: ControlState;
@ -239,18 +247,24 @@ export interface SelectControlConfig<
options?: O[];
optionRenderer?: (option: O) => ReactNode;
valueRenderer?: (option: O) => ReactNode;
filterOption?: ((option: FilterOption<O>, rawInput: string) => Boolean) | null;
filterOption?:
| ((option: FilterOption<O>, rawInput: string) => Boolean)
| null;
}
export type SharedControlConfig<
T extends InternalControlType = InternalControlType,
O extends SelectOption = SelectOption,
> = T extends SelectControlType ? SelectControlConfig<O, T> : BaseControlConfig<T>;
> = T extends SelectControlType
? SelectControlConfig<O, T>
: BaseControlConfig<T>;
/** --------------------------------------------
* Custom controls
* --------------------------------------------- */
export type CustomControlConfig<P = {}> = BaseControlConfig<React.ComponentType<P>> &
export type CustomControlConfig<P = {}> = BaseControlConfig<
React.ComponentType<P>
> &
// two run-time properties from superset-frontend/src/explore/components/Control.jsx
Omit<P, 'onChange' | 'hovered'>;
@ -278,7 +292,9 @@ export type SharedSectionAlias =
| 'sqlaTimeSeries'
| 'NVD3TimeSeries';
export interface OverrideSharedControlItem<A extends SharedControlAlias = SharedControlAlias> {
export interface OverrideSharedControlItem<
A extends SharedControlAlias = SharedControlAlias,
> {
name: A;
override: Partial<SharedControls[A]>;
}
@ -293,7 +309,10 @@ export type CustomControlItem = {
// interfere with other ControlSetItem types
export type ExpandedControlItem = CustomControlItem | ReactElement | null;
export type ControlSetItem = SharedControlAlias | OverrideSharedControlItem | ExpandedControlItem;
export type ControlSetItem =
| SharedControlAlias
| OverrideSharedControlItem
| ExpandedControlItem;
export type ControlSetRow = ControlSetItem[];

View File

@ -19,7 +19,9 @@
import { t, smartDateFormatter, NumberFormats } from '@superset-ui/core';
// D3 specific formatting config
export const D3_FORMAT_DOCS = t('D3 format syntax: https://github.com/d3/d3-format');
export const D3_FORMAT_DOCS = t(
'D3 format syntax: https://github.com/d3/d3-format',
);
// input choices & options
export const D3_FORMAT_OPTIONS: [string, string][] = [
@ -39,7 +41,9 @@ export const D3_FORMAT_OPTIONS: [string, string][] = [
['DURATION_SUB', t('Duration in ms (1.40008 => 1ms 400µs 80ns)')],
];
export const D3_TIME_FORMAT_DOCS = t('D3 time format syntax: https://github.com/d3/d3-time-format');
export const D3_TIME_FORMAT_DOCS = t(
'D3 time format syntax: https://github.com/d3/d3-time-format',
);
export const D3_TIME_FORMAT_OPTIONS: [string, string][] = [
[smartDateFormatter.id, t('Adaptative formating')],

View File

@ -21,10 +21,17 @@ import { DatasourceMeta } from '../types';
/**
* Convert Datasource columns to column choices
*/
export default function columnChoices(datasource?: DatasourceMeta | null): [string, string][] {
export default function columnChoices(
datasource?: DatasourceMeta | null,
): [string, string][] {
return (
datasource?.columns
.map((col): [string, string] => [col.column_name, col.verbose_name || col.column_name])
.sort((opt1, opt2) => (opt1[1].toLowerCase() > opt2[1].toLowerCase() ? 1 : -1)) || []
.map((col): [string, string] => [
col.column_name,
col.verbose_name || col.column_name,
])
.sort((opt1, opt2) =>
opt1[1].toLowerCase() > opt2[1].toLowerCase() ? 1 : -1,
) || []
);
}

View File

@ -19,11 +19,21 @@
import React, { ReactElement } from 'react';
import { sharedControls } from '../shared-controls';
import sharedControlComponents from '../shared-controls/components';
import { ControlType, ControlSetItem, ExpandedControlItem, ControlOverrides } from '../types';
import {
ControlType,
ControlSetItem,
ExpandedControlItem,
ControlOverrides,
} from '../types';
export function expandControlType(controlType: ControlType) {
if (typeof controlType === 'string' && controlType in sharedControlComponents) {
return sharedControlComponents[controlType as keyof typeof sharedControlComponents];
if (
typeof controlType === 'string' &&
controlType in sharedControlComponents
) {
return sharedControlComponents[
controlType as keyof typeof sharedControlComponents
];
}
return controlType;
}
@ -54,7 +64,11 @@ export function expandControlConfig(
};
}
// JSX/React element or NULL
if (!control || typeof control === 'string' || React.isValidElement(control)) {
if (
!control ||
typeof control === 'string' ||
React.isValidElement(control)
) {
return control as ReactElement;
}
// already fully expanded control config, e.g.

View File

@ -44,7 +44,8 @@ export const getOpacity = (
? maxOpacity
: round(
Math.abs(
((maxOpacity - minOpacity) / (extremeValue - cutoffPoint)) * (value - cutoffPoint),
((maxOpacity - minOpacity) / (extremeValue - cutoffPoint)) *
(value - cutoffPoint),
) + minOpacity,
2,
);
@ -119,7 +120,9 @@ export const getColorFunction = (
break;
case COMPARATOR.EQUAL:
comparatorFunction = (value: number) =>
value === targetValue! ? { cutoffValue: targetValue!, extremeValue: targetValue! } : false;
value === targetValue!
? { cutoffValue: targetValue!, extremeValue: targetValue! }
: false;
break;
case COMPARATOR.NOT_EQUAL:
comparatorFunction = (value: number, allValues: number[]) => {
@ -130,7 +133,10 @@ export const getColorFunction = (
const min = Math.min(...allValues);
return {
cutoffValue: targetValue!,
extremeValue: Math.abs(targetValue! - min) > Math.abs(max - targetValue!) ? min : max,
extremeValue:
Math.abs(targetValue! - min) > Math.abs(max - targetValue!)
? min
: max,
};
};
break;
@ -178,22 +184,26 @@ export const getColorFormatters = (
columnConfig: ConditionalFormattingConfig[] | undefined,
data: DataRecord[],
) =>
columnConfig?.reduce((acc: ColorFormatters, config: ConditionalFormattingConfig) => {
if (
config?.column !== undefined &&
(config?.operator === COMPARATOR.NONE ||
(config?.operator !== undefined &&
(MULTIPLE_VALUE_COMPARATORS.includes(config?.operator)
? config?.targetValueLeft !== undefined && config?.targetValueRight !== undefined
: config?.targetValue !== undefined)))
) {
acc.push({
column: config?.column,
getColorFromValue: getColorFunction(
config,
data.map(row => row[config.column!] as number),
),
});
}
return acc;
}, []) ?? [];
columnConfig?.reduce(
(acc: ColorFormatters, config: ConditionalFormattingConfig) => {
if (
config?.column !== undefined &&
(config?.operator === COMPARATOR.NONE ||
(config?.operator !== undefined &&
(MULTIPLE_VALUE_COMPARATORS.includes(config?.operator)
? config?.targetValueLeft !== undefined &&
config?.targetValueRight !== undefined
: config?.targetValue !== undefined)))
) {
acc.push({
column: config?.column,
getColorFromValue: getColorFunction(
config,
data.map(row => row[config.column!] as number),
),
});
}
return acc;
},
[],
) ?? [];

View File

@ -104,6 +104,8 @@ describe('ColumnOption', () => {
props.column.type_generic = GenericDataType.TEMPORAL;
wrapper = shallow(factory(props));
expect(wrapper.find(ColumnTypeLabel)).toHaveLength(1);
expect(wrapper.find(ColumnTypeLabel).props().type).toBe(GenericDataType.TEMPORAL);
expect(wrapper.find(ColumnTypeLabel).props().type).toBe(
GenericDataType.TEMPORAL,
);
});
});

View File

@ -35,20 +35,28 @@ describe('ColumnOption', () => {
}
it('is a valid element', () => {
expect(React.isValidElement(<ColumnTypeLabel {...defaultProps} />)).toBe(true);
expect(React.isValidElement(<ColumnTypeLabel {...defaultProps} />)).toBe(
true,
);
});
it('string type shows ABC icon', () => {
const lbl = getWrapper({ type: GenericDataType.STRING }).find('.type-label');
const lbl = getWrapper({ type: GenericDataType.STRING }).find(
'.type-label',
);
expect(lbl).toHaveLength(1);
expect(lbl.first().text()).toBe('ABC');
});
it('int type shows # icon', () => {
const lbl = getWrapper({ type: GenericDataType.NUMERIC }).find('.type-label');
const lbl = getWrapper({ type: GenericDataType.NUMERIC }).find(
'.type-label',
);
expect(lbl).toHaveLength(1);
expect(lbl.first().text()).toBe('#');
});
it('bool type shows T/F icon', () => {
const lbl = getWrapper({ type: GenericDataType.BOOLEAN }).find('.type-label');
const lbl = getWrapper({ type: GenericDataType.BOOLEAN }).find(
'.type-label',
);
expect(lbl).toHaveLength(1);
expect(lbl.first().text()).toBe('T/F');
});
@ -63,7 +71,9 @@ describe('ColumnOption', () => {
expect(lbl.first().text()).toBe('?');
});
it('datetime type displays', () => {
const lbl = getWrapper({ type: GenericDataType.TEMPORAL }).find('.fa-clock-o');
const lbl = getWrapper({ type: GenericDataType.TEMPORAL }).find(
'.fa-clock-o',
);
expect(lbl).toHaveLength(1);
});
});

View File

@ -23,7 +23,9 @@ import { InfoTooltipWithTrigger } from '../../src';
describe('InfoTooltipWithTrigger', () => {
it('renders a tooltip', () => {
const wrapper = shallow(<InfoTooltipWithTrigger label="test" tooltip="this is a test" />);
const wrapper = shallow(
<InfoTooltipWithTrigger label="test" tooltip="this is a test" />,
);
expect(wrapper.find(Tooltip)).toHaveLength(1);
});
@ -35,7 +37,11 @@ describe('InfoTooltipWithTrigger', () => {
it('responds to keypresses', () => {
const clickHandler = jest.fn();
const wrapper = shallow(
<InfoTooltipWithTrigger label="test" tooltip="this is a test" onClick={clickHandler} />,
<InfoTooltipWithTrigger
label="test"
tooltip="this is a test"
onClick={clickHandler}
/>,
);
wrapper.find('.fa-info-circle').simulate('keypress', { key: 'Tab' });
expect(clickHandler).toHaveBeenCalledTimes(0);

View File

@ -68,9 +68,9 @@ describe('expandControlConfig()', () => {
label: 'Custom Metric',
},
};
expect((expandControlConfig(input) as CustomControlItem).config.type).toEqual(
sharedControlComponents.RadioButtonControl,
);
expect(
(expandControlConfig(input) as CustomControlItem).config.type,
).toEqual(sharedControlComponents.RadioButtonControl);
});
it('leave NULL and ReactElement untouched', () => {
@ -85,6 +85,8 @@ describe('expandControlConfig()', () => {
});
it('return null for invalid configs', () => {
expect(expandControlConfig({ type: 'SelectControl', label: 'Hello' } as never)).toBeNull();
expect(
expandControlConfig({ type: 'SelectControl', label: 'Hello' } as never),
).toBeNull();
});
});

View File

@ -359,14 +359,20 @@ describe('getColorFormatters()', () => {
expect(colorFormatters.length).toEqual(3);
expect(colorFormatters[0].column).toEqual('count');
expect(colorFormatters[0].getColorFromValue(100)).toEqual('rgba(255,0,0,1)');
expect(colorFormatters[0].getColorFromValue(100)).toEqual(
'rgba(255,0,0,1)',
);
expect(colorFormatters[1].column).toEqual('sum');
expect(colorFormatters[1].getColorFromValue(200)).toEqual('rgba(255,0,0,1)');
expect(colorFormatters[1].getColorFromValue(200)).toEqual(
'rgba(255,0,0,1)',
);
expect(colorFormatters[1].getColorFromValue(400)).toBeUndefined();
expect(colorFormatters[2].column).toEqual('count');
expect(colorFormatters[2].getColorFromValue(100)).toEqual('rgba(255,0,0,0.53)');
expect(colorFormatters[2].getColorFromValue(100)).toEqual(
'rgba(255,0,0,0.53)',
);
});
it('undefined column config', () => {

View File

@ -20,14 +20,20 @@ import { QueryObject, SqlaFormData } from '@superset-ui/core';
import { boxplotOperator } from '../../../src';
const formData: SqlaFormData = {
metrics: ['count(*)', { label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' }],
metrics: [
'count(*)',
{ label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' },
],
time_range: '2015 : 2016',
time_grain_sqla: 'P1Y',
datasource: 'foo',
viz_type: 'table',
};
const queryObject: QueryObject = {
metrics: ['count(*)', { label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' }],
metrics: [
'count(*)',
{ label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' },
],
time_range: '2015 : 2016',
granularity: 'P1Y',
};

View File

@ -20,14 +20,20 @@ import { QueryObject, SqlaFormData } from '@superset-ui/core';
import { contributionOperator } from '../../../src';
const formData: SqlaFormData = {
metrics: ['count(*)', { label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' }],
metrics: [
'count(*)',
{ label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' },
],
time_range: '2015 : 2016',
granularity: 'month',
datasource: 'foo',
viz_type: 'table',
};
const queryObject: QueryObject = {
metrics: ['count(*)', { label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' }],
metrics: [
'count(*)',
{ label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' },
],
time_range: '2015 : 2016',
granularity: 'month',
};
@ -37,7 +43,9 @@ test('should skip contributionOperator', () => {
});
test('should do contributionOperator', () => {
expect(contributionOperator({ ...formData, contributionMode: 'row' }, queryObject)).toEqual({
expect(
contributionOperator({ ...formData, contributionMode: 'row' }, queryObject),
).toEqual({
operation: 'contribution',
options: {
orientation: 'row',

View File

@ -20,14 +20,20 @@ import { QueryObject, SqlaFormData } from '@superset-ui/core';
import { pivotOperator } from '../../../src';
const formData: SqlaFormData = {
metrics: ['count(*)', { label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' }],
metrics: [
'count(*)',
{ label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' },
],
time_range: '2015 : 2016',
granularity: 'month',
datasource: 'foo',
viz_type: 'table',
};
const queryObject: QueryObject = {
metrics: ['count(*)', { label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' }],
metrics: [
'count(*)',
{ label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' },
],
time_range: '2015 : 2016',
granularity: 'month',
post_processing: [
@ -49,14 +55,22 @@ const queryObject: QueryObject = {
test('skip pivot', () => {
expect(pivotOperator(formData, queryObject)).toEqual(undefined);
expect(pivotOperator(formData, { ...queryObject, is_timeseries: false })).toEqual(undefined);
expect(pivotOperator(formData, { ...queryObject, is_timeseries: true, metrics: [] })).toEqual(
undefined,
);
expect(
pivotOperator(formData, { ...queryObject, is_timeseries: false }),
).toEqual(undefined);
expect(
pivotOperator(formData, {
...queryObject,
is_timeseries: true,
metrics: [],
}),
).toEqual(undefined);
});
test('pivot by __timestamp without groupby', () => {
expect(pivotOperator(formData, { ...queryObject, is_timeseries: true })).toEqual({
expect(
pivotOperator(formData, { ...queryObject, is_timeseries: true }),
).toEqual({
operation: 'pivot',
options: {
index: ['__timestamp'],
@ -72,7 +86,11 @@ test('pivot by __timestamp without groupby', () => {
test('pivot by __timestamp with groupby', () => {
expect(
pivotOperator(formData, { ...queryObject, columns: ['foo', 'bar'], is_timeseries: true }),
pivotOperator(formData, {
...queryObject,
columns: ['foo', 'bar'],
is_timeseries: true,
}),
).toEqual({
operation: 'pivot',
options: {

View File

@ -20,14 +20,20 @@ import { QueryObject, SqlaFormData } from '@superset-ui/core';
import { prophetOperator } from '../../../src';
const formData: SqlaFormData = {
metrics: ['count(*)', { label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' }],
metrics: [
'count(*)',
{ label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' },
],
time_range: '2015 : 2016',
time_grain_sqla: 'P1Y',
datasource: 'foo',
viz_type: 'table',
};
const queryObject: QueryObject = {
metrics: ['count(*)', { label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' }],
metrics: [
'count(*)',
{ label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' },
],
time_range: '2015 : 2016',
granularity: 'P1Y',
};

View File

@ -20,14 +20,20 @@ import { QueryObject, SqlaFormData } from '@superset-ui/core';
import { resampleOperator } from '../../../src';
const formData: SqlaFormData = {
metrics: ['count(*)', { label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' }],
metrics: [
'count(*)',
{ label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' },
],
time_range: '2015 : 2016',
granularity: 'month',
datasource: 'foo',
viz_type: 'table',
};
const queryObject: QueryObject = {
metrics: ['count(*)', { label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' }],
metrics: [
'count(*)',
{ label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' },
],
time_range: '2015 : 2016',
granularity: 'month',
post_processing: [
@ -48,15 +54,20 @@ const queryObject: QueryObject = {
test('should skip resampleOperator', () => {
expect(resampleOperator(formData, queryObject)).toEqual(undefined);
expect(resampleOperator({ ...formData, resample_method: 'ffill' }, queryObject)).toEqual(
undefined,
);
expect(resampleOperator({ ...formData, resample_rule: '1D' }, queryObject)).toEqual(undefined);
expect(
resampleOperator({ ...formData, resample_method: 'ffill' }, queryObject),
).toEqual(undefined);
expect(
resampleOperator({ ...formData, resample_rule: '1D' }, queryObject),
).toEqual(undefined);
});
test('should do resample', () => {
expect(
resampleOperator({ ...formData, resample_method: 'ffill', resample_rule: '1D' }, queryObject),
resampleOperator(
{ ...formData, resample_method: 'ffill', resample_rule: '1D' },
queryObject,
),
).toEqual({
operation: 'resample',
options: {

View File

@ -20,14 +20,20 @@ import { QueryObject, SqlaFormData } from '@superset-ui/core';
import { rollingWindowOperator } from '../../../src';
const formData: SqlaFormData = {
metrics: ['count(*)', { label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' }],
metrics: [
'count(*)',
{ label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' },
],
time_range: '2015 : 2016',
granularity: 'month',
datasource: 'foo',
viz_type: 'table',
};
const queryObject: QueryObject = {
metrics: ['count(*)', { label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' }],
metrics: [
'count(*)',
{ label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' },
],
time_range: '2015 : 2016',
granularity: 'month',
post_processing: [
@ -48,20 +54,24 @@ const queryObject: QueryObject = {
test('skip transformation', () => {
expect(rollingWindowOperator(formData, queryObject)).toEqual(undefined);
expect(rollingWindowOperator({ ...formData, rolling_type: 'None' }, queryObject)).toEqual(
undefined,
);
expect(rollingWindowOperator({ ...formData, rolling_type: 'foobar' }, queryObject)).toEqual(
undefined,
);
expect(
rollingWindowOperator({ ...formData, rolling_type: 'None' }, queryObject),
).toEqual(undefined);
expect(
rollingWindowOperator({ ...formData, rolling_type: 'foobar' }, queryObject),
).toEqual(undefined);
const formDataWithoutMetrics = { ...formData };
delete formDataWithoutMetrics.metrics;
expect(rollingWindowOperator(formDataWithoutMetrics, queryObject)).toEqual(undefined);
expect(rollingWindowOperator(formDataWithoutMetrics, queryObject)).toEqual(
undefined,
);
});
test('rolling_type: cumsum', () => {
expect(rollingWindowOperator({ ...formData, rolling_type: 'cumsum' }, queryObject)).toEqual({
expect(
rollingWindowOperator({ ...formData, rolling_type: 'cumsum' }, queryObject),
).toEqual({
operation: 'cum',
options: {
operator: 'sum',
@ -77,7 +87,12 @@ test('rolling_type: cumsum', () => {
test('rolling_type: sum/mean/std', () => {
const rollingTypes = ['sum', 'mean', 'std'];
rollingTypes.forEach(rollingType => {
expect(rollingWindowOperator({ ...formData, rolling_type: rollingType }, queryObject)).toEqual({
expect(
rollingWindowOperator(
{ ...formData, rolling_type: rollingType },
queryObject,
),
).toEqual({
operation: 'rolling',
options: {
rolling_type: rollingType,

View File

@ -20,14 +20,20 @@ import { QueryObject, SqlaFormData } from '@superset-ui/core';
import { sortOperator } from '../../../src';
const formData: SqlaFormData = {
metrics: ['count(*)', { label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' }],
metrics: [
'count(*)',
{ label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' },
],
time_range: '2015 : 2016',
granularity: 'month',
datasource: 'foo',
viz_type: 'table',
};
const queryObject: QueryObject = {
metrics: ['count(*)', { label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' }],
metrics: [
'count(*)',
{ label: 'sum(val)', expressionType: 'SQL', sqlExpression: 'sum(val)' },
],
time_range: '2015 : 2016',
granularity: 'month',
post_processing: [
@ -48,16 +54,26 @@ const queryObject: QueryObject = {
test('skip sort', () => {
expect(sortOperator(formData, queryObject)).toEqual(undefined);
expect(sortOperator(formData, { ...queryObject, is_timeseries: false })).toEqual(undefined);
expect(
sortOperator({ ...formData, rolling_type: 'xxxx' }, { ...queryObject, is_timeseries: true }),
sortOperator(formData, { ...queryObject, is_timeseries: false }),
).toEqual(undefined);
expect(
sortOperator(
{ ...formData, rolling_type: 'xxxx' },
{ ...queryObject, is_timeseries: true },
),
).toEqual(undefined);
expect(
sortOperator(formData, { ...queryObject, is_timeseries: true }),
).toEqual(undefined);
expect(sortOperator(formData, { ...queryObject, is_timeseries: true })).toEqual(undefined);
});
test('sort by __timestamp', () => {
expect(
sortOperator({ ...formData, rolling_type: 'cumsum' }, { ...queryObject, is_timeseries: true }),
sortOperator(
{ ...formData, rolling_type: 'cumsum' },
{ ...queryObject, is_timeseries: true },
),
).toEqual({
operation: 'sort',
options: {
@ -68,7 +84,10 @@ test('sort by __timestamp', () => {
});
expect(
sortOperator({ ...formData, rolling_type: 'sum' }, { ...queryObject, is_timeseries: true }),
sortOperator(
{ ...formData, rolling_type: 'sum' },
{ ...queryObject, is_timeseries: true },
),
).toEqual({
operation: 'sort',
options: {
@ -79,7 +98,10 @@ test('sort by __timestamp', () => {
});
expect(
sortOperator({ ...formData, rolling_type: 'mean' }, { ...queryObject, is_timeseries: true }),
sortOperator(
{ ...formData, rolling_type: 'mean' },
{ ...queryObject, is_timeseries: true },
),
).toEqual({
operation: 'sort',
options: {
@ -90,7 +112,10 @@ test('sort by __timestamp', () => {
});
expect(
sortOperator({ ...formData, rolling_type: 'std' }, { ...queryObject, is_timeseries: true }),
sortOperator(
{ ...formData, rolling_type: 'std' },
{ ...queryObject, is_timeseries: true },
),
).toEqual({
operation: 'sort',
options: {

View File

@ -56,16 +56,25 @@ const queryObject: QueryObject = {
test('time compare: skip transformation', () => {
expect(timeCompareOperator(formData, queryObject)).toEqual(undefined);
expect(timeCompareOperator({ ...formData, time_compare: [] }, queryObject)).toEqual(undefined);
expect(timeCompareOperator({ ...formData, comparison_type: null }, queryObject)).toEqual(
undefined,
);
expect(timeCompareOperator({ ...formData, comparison_type: 'foobar' }, queryObject)).toEqual(
undefined,
);
expect(
timeCompareOperator({ ...formData, time_compare: [] }, queryObject),
).toEqual(undefined);
expect(
timeCompareOperator({ ...formData, comparison_type: null }, queryObject),
).toEqual(undefined);
expect(
timeCompareOperator(
{ ...formData, comparison_type: 'values', time_compare: ['1 year ago', '1 year later'] },
{ ...formData, comparison_type: 'foobar' },
queryObject,
),
).toEqual(undefined);
expect(
timeCompareOperator(
{
...formData,
comparison_type: 'values',
time_compare: ['1 year ago', '1 year later'],
},
queryObject,
),
).toEqual(undefined);
@ -76,7 +85,11 @@ test('time compare: difference/percentage/ratio', () => {
comparisionTypes.forEach(cType => {
expect(
timeCompareOperator(
{ ...formData, comparison_type: cType, time_compare: ['1 year ago', '1 year later'] },
{
...formData,
comparison_type: cType,
time_compare: ['1 year ago', '1 year later'],
},
queryObject,
),
).toEqual({
@ -93,21 +106,31 @@ test('time compare: difference/percentage/ratio', () => {
test('time compare pivot: skip transformation', () => {
expect(timeComparePivotOperator(formData, queryObject)).toEqual(undefined);
expect(timeComparePivotOperator({ ...formData, time_compare: [] }, queryObject)).toEqual(
undefined,
);
expect(timeComparePivotOperator({ ...formData, comparison_type: null }, queryObject)).toEqual(
undefined,
);
expect(timeCompareOperator({ ...formData, comparison_type: 'foobar' }, queryObject)).toEqual(
undefined,
);
expect(
timeComparePivotOperator({ ...formData, time_compare: [] }, queryObject),
).toEqual(undefined);
expect(
timeComparePivotOperator(
{ ...formData, comparison_type: null },
queryObject,
),
).toEqual(undefined);
expect(
timeCompareOperator(
{ ...formData, comparison_type: 'foobar' },
queryObject,
),
).toEqual(undefined);
});
test('time compare pivot: values', () => {
expect(
timeComparePivotOperator(
{ ...formData, comparison_type: 'values', time_compare: ['1 year ago', '1 year later'] },
{
...formData,
comparison_type: 'values',
time_compare: ['1 year ago', '1 year later'],
},
queryObject,
),
).toEqual({
@ -130,7 +153,11 @@ test('time compare pivot: difference/percentage/ratio', () => {
comparisionTypes.forEach(cType => {
expect(
timeComparePivotOperator(
{ ...formData, comparison_type: cType, time_compare: ['1 year ago', '1 year later'] },
{
...formData,
comparison_type: cType,
time_compare: ['1 year ago', '1 year later'],
},
queryObject,
),
).toEqual({

View File

@ -31,7 +31,15 @@ describe('formatSelectOptions', () => {
});
it('formats a mix of values and already formated options', () => {
expect(
formatSelectOptions<number | string>([[0, 'all'], 1, 5, 10, 25, 50, 'unlimited']),
formatSelectOptions<number | string>([
[0, 'all'],
1,
5,
10,
25,
50,
'unlimited',
]),
).toEqual([
[0, 'all'],
[1, '1'],

View File

@ -28,7 +28,13 @@ type Props = {
contentWidth?: number;
contentHeight?: number;
height: number;
renderContent: ({ height, width }: { height: number; width: number }) => React.ReactNode;
renderContent: ({
height,
width,
}: {
height: number;
width: number;
}) => React.ReactNode;
width: number;
};
@ -38,7 +44,8 @@ export default class ChartFrame extends PureComponent<Props, {}> {
};
render() {
const { contentWidth, contentHeight, width, height, renderContent } = this.props;
const { contentWidth, contentHeight, width, height, renderContent } =
this.props;
const overflowX = checkNumber(contentWidth) && contentWidth > width;
const overflowY = checkNumber(contentHeight) && contentHeight > height;

View File

@ -85,8 +85,15 @@ class WithLegend extends PureComponent<Props, {}> {
}
render() {
const { className, debounceTime, width, height, position, renderChart, renderLegend } =
this.props;
const {
className,
debounceTime,
width,
height,
position,
renderChart,
renderLegend,
} = this.props;
const isHorizontal = position === 'left' || position === 'right';

View File

@ -51,7 +51,13 @@ export default class TooltipTable extends PureComponent<Props, {}> {
{data.map(({ key, keyColumn, keyStyle, valueColumn, valueStyle }) => (
<tr key={key}>
<td style={keyStyle}>{keyColumn ?? key}</td>
<td style={valueStyle ? { ...VALUE_CELL_STYLE, ...valueStyle } : VALUE_CELL_STYLE}>
<td
style={
valueStyle
? { ...VALUE_CELL_STYLE, ...valueStyle }
: VALUE_CELL_STYLE
}
>
{valueColumn}
</td>
</tr>

View File

@ -33,7 +33,8 @@ import { AnnotationLayerMetadata } from '../types/Annotation';
import { PlainObject } from '../types/Base';
// This expands to Partial<All> & (union of all possible single-property types)
type AtLeastOne<All, Each = { [K in keyof All]: Pick<All, K> }> = Partial<All> & Each[keyof Each];
type AtLeastOne<All, Each = { [K in keyof All]: Pick<All, K> }> = Partial<All> &
Each[keyof Each];
export type SliceIdAndOrFormData = AtLeastOne<{
sliceId: number;
@ -89,7 +90,9 @@ export default class ChartClient {
/* If sliceId is not provided, returned formData wrapped in a Promise */
return input.formData
? Promise.resolve(input.formData as QueryFormData)
: Promise.reject(new Error('At least one of sliceId or formData must be specified'));
: Promise.reject(
new Error('At least one of sliceId or formData must be specified'),
);
}
async loadQueryData(
@ -102,7 +105,8 @@ export default class ChartClient {
if (metaDataRegistry.has(visType)) {
const { useLegacyApi } = metaDataRegistry.get(visType)!;
const buildQuery = (await buildQueryRegistry.get(visType)) ?? (() => formData);
const buildQuery =
(await buildQueryRegistry.get(visType)) ?? (() => formData);
const requestConfig: RequestConfig = useLegacyApi
? {
endpoint: '/superset/explore_json/',
@ -121,13 +125,18 @@ export default class ChartClient {
return this.client
.post(requestConfig)
.then(response => (Array.isArray(response.json) ? response.json : [response.json]));
.then(response =>
Array.isArray(response.json) ? response.json : [response.json],
);
}
return Promise.reject(new Error(`Unknown chart type: ${visType}`));
}
loadDatasource(datasourceKey: string, options?: Partial<RequestConfig>): Promise<Datasource> {
loadDatasource(
datasourceKey: string,
options?: Partial<RequestConfig>,
): Promise<Datasource> {
return this.client
.get({
endpoint: `/superset/fetch_datasource_metadata?datasourceKey=${datasourceKey}`,
@ -137,7 +146,9 @@ export default class ChartClient {
}
// eslint-disable-next-line class-methods-use-this
loadAnnotation(annotationLayer: AnnotationLayerMetadata): Promise<AnnotationData> {
loadAnnotation(
annotationLayer: AnnotationLayerMetadata,
): Promise<AnnotationData> {
/* When annotation does not require query */
if (!isDefined(annotationLayer.sourceType)) {
return Promise.resolve({} as AnnotationData);
@ -147,9 +158,13 @@ export default class ChartClient {
return Promise.reject(new Error('This feature is not implemented yet.'));
}
loadAnnotations(annotationLayers?: AnnotationLayerMetadata[]): Promise<AnnotationData> {
loadAnnotations(
annotationLayers?: AnnotationLayerMetadata[],
): Promise<AnnotationData> {
if (Array.isArray(annotationLayers) && annotationLayers.length > 0) {
return Promise.all(annotationLayers.map(layer => this.loadAnnotation(layer))).then(results =>
return Promise.all(
annotationLayers.map(layer => this.loadAnnotation(layer)),
).then(results =>
annotationLayers.reduce((prev, layer, i) => {
const output: AnnotationData = prev;
output[layer.name] = results[i];

View File

@ -19,7 +19,12 @@
/* eslint react/sort-comp: 'off' */
import React, { ReactNode } from 'react';
import { SupersetClientInterface, RequestConfig, QueryFormData, Datasource } from '../..';
import {
SupersetClientInterface,
RequestConfig,
QueryFormData,
Datasource,
} from '../..';
import ChartClient, { SliceIdAndOrFormData } from '../clients/ChartClient';
import { QueryData } from '../types/QueryResponse';
@ -101,11 +106,17 @@ class ChartDataProvider extends React.PureComponent<
this.setState({ status: 'loading' }, () => {
try {
this.chartClient
.loadFormData(this.extractSliceIdAndFormData(), formDataRequestOptions)
.loadFormData(
this.extractSliceIdAndFormData(),
formDataRequestOptions,
)
.then(formData =>
Promise.all([
loadDatasource
? this.chartClient.loadDatasource(formData.datasource, datasourceRequestOptions)
? this.chartClient.loadDatasource(
formData.datasource,
datasourceRequestOptions,
)
: Promise.resolve(undefined),
this.chartClient.loadQueryData(formData, queryRequestOptions),
]).then(

View File

@ -29,7 +29,12 @@ const CONTAINER_STYLE = {
padding: 32,
};
export default function FallbackComponent({ componentStack, error, height, width }: Props) {
export default function FallbackComponent({
componentStack,
error,
height,
width,
}: Props) {
return (
<div style={{ ...CONTAINER_STYLE, height, width }}>
<div>

View File

@ -21,21 +21,27 @@ import React, { CSSProperties, useMemo } from 'react';
import { t } from '../../translation';
const MESSAGE_STYLES: CSSProperties = { maxWidth: 800 };
const TITLE_STYLES: CSSProperties = { fontSize: 16, fontWeight: 'bold', paddingBottom: 8 };
const TITLE_STYLES: CSSProperties = {
fontSize: 16,
fontWeight: 'bold',
paddingBottom: 8,
};
const BODY_STYLES: CSSProperties = { fontSize: 14 };
const MIN_WIDTH_FOR_BODY = 250;
const generateContainerStyles: (height: number | string, width: number | string) => CSSProperties =
(height: number | string, width: number | string) => ({
alignItems: 'center',
display: 'flex',
flexDirection: 'column',
height,
justifyContent: 'center',
padding: 16,
textAlign: 'center',
width,
});
const generateContainerStyles: (
height: number | string,
width: number | string,
) => CSSProperties = (height: number | string, width: number | string) => ({
alignItems: 'center',
display: 'flex',
flexDirection: 'column',
height,
justifyContent: 'center',
padding: 16,
textAlign: 'center',
width,
});
type Props = {
className?: string;
@ -45,10 +51,14 @@ type Props = {
};
const NoResultsComponent = ({ className, height, id, width }: Props) => {
const containerStyles = useMemo(() => generateContainerStyles(height, width), [height, width]);
const containerStyles = useMemo(
() => generateContainerStyles(height, width),
[height, width],
);
// render the body if the width is auto/100% or greater than 250 pixels
const shouldRenderBody = typeof width === 'string' || width > MIN_WIDTH_FOR_BODY;
const shouldRenderBody =
typeof width === 'string' || width > MIN_WIDTH_FOR_BODY;
const BODY_STRING = t(
'No results were returned for this query. If you expected results to be returned, ensure any filters are configured properly and the datasource contains data for the selected time range.',

View File

@ -18,7 +18,10 @@
*/
import React, { ReactNode, RefObject } from 'react';
import ErrorBoundary, { ErrorBoundaryProps, FallbackProps } from 'react-error-boundary';
import ErrorBoundary, {
ErrorBoundaryProps,
FallbackProps,
} from 'react-error-boundary';
import { ParentSize } from '@vx/responsive';
import { createSelector } from 'reselect';
import { parseLength, Dimension } from '../../dimension';
@ -94,8 +97,12 @@ export default class SuperChart extends React.PureComponent<Props, {}> {
const widthInfo = parseLength(width);
const heightInfo = parseLength(height);
const boxHeight = heightInfo.isDynamic ? `${heightInfo.multiplier * 100}%` : heightInfo.value;
const boxWidth = widthInfo.isDynamic ? `${widthInfo.multiplier * 100}%` : widthInfo.value;
const boxHeight = heightInfo.isDynamic
? `${heightInfo.multiplier * 100}%`
: heightInfo.value;
const boxWidth = widthInfo.isDynamic
? `${widthInfo.multiplier * 100}%`
: widthInfo.value;
const style = {
height: boxHeight,
width: boxWidth,
@ -111,7 +118,9 @@ export default class SuperChart extends React.PureComponent<Props, {}> {
widthInfo.multiplier === 1 &&
heightInfo.multiplier === 1
? React.Fragment
: ({ children }: { children: ReactNode }) => <div style={style}>{children}</div>;
: ({ children }: { children: ReactNode }) => (
<div style={style}>{children}</div>
);
return { BoundingBox, heightInfo, widthInfo };
},
@ -154,9 +163,18 @@ export default class SuperChart extends React.PureComponent<Props, {}> {
const noResultQueries =
enableNoResults &&
(!queriesData ||
queriesData.every(({ data }) => !data || (Array.isArray(data) && data.length === 0)));
queriesData.every(
({ data }) => !data || (Array.isArray(data) && data.length === 0),
));
if (noResultQueries) {
chart = <NoResultsComponent id={id} className={className} height={height} width={width} />;
chart = (
<NoResultsComponent
id={id}
className={className}
height={height}
width={width}
/>
);
} else {
const chartWithoutWrapper = (
<SuperChartCore

View File

@ -25,7 +25,11 @@ import getChartTransformPropsRegistry from '../registries/ChartTransformPropsReg
import ChartProps from '../models/ChartProps';
import createLoadableRenderer from './createLoadableRenderer';
import { ChartType } from '../models/ChartPlugin';
import { PreTransformProps, TransformProps, PostTransformProps } from '../types/TransformFunction';
import {
PreTransformProps,
TransformProps,
PostTransformProps,
} from '../types/TransformFunction';
import { HandlerFunction } from '../types/Base';
function IDENTITY<T>(x: T) {
@ -112,7 +116,8 @@ export default class SuperChartCore extends React.PureComponent<Props, {}> {
* is changed.
*/
private createLoadableRenderer = createSelector(
(input: { chartType: string; overrideTransformProps?: TransformProps }) => input.chartType,
(input: { chartType: string; overrideTransformProps?: TransformProps }) =>
input.chartType,
input => input.overrideTransformProps,
(chartType, overrideTransformProps) => {
if (chartType) {
@ -123,7 +128,8 @@ export default class SuperChartCore extends React.PureComponent<Props, {}> {
? () => Promise.resolve(overrideTransformProps)
: () => getChartTransformPropsRegistry().getAsPromise(chartType),
},
loading: (loadingProps: LoadingProps) => this.renderLoading(loadingProps, chartType),
loading: (loadingProps: LoadingProps) =>
this.renderLoading(loadingProps, chartType),
render: this.renderChart,
});

View File

@ -92,14 +92,18 @@ export default function reactify<Props extends object>(
}
}
const ReactifiedClass: React.ComponentClass<Props & ReactifyProps> = ReactifiedComponent;
const ReactifiedClass: React.ComponentClass<Props & ReactifyProps> =
ReactifiedComponent;
if (renderFn.displayName) {
ReactifiedClass.displayName = renderFn.displayName;
}
// eslint-disable-next-line react/forbid-foreign-prop-types
if (renderFn.propTypes) {
ReactifiedClass.propTypes = { ...ReactifiedClass.propTypes, ...renderFn.propTypes };
ReactifiedClass.propTypes = {
...ReactifiedClass.propTypes,
...renderFn.propTypes,
};
}
if (renderFn.defaultProps) {
ReactifiedClass.defaultProps = renderFn.defaultProps;

View File

@ -48,11 +48,15 @@ interface ChartPluginConfig<
/** Use buildQuery for immediate value. For lazy-loading, use loadBuildQuery. */
buildQuery?: BuildQueryFunction<FormData>;
/** Use loadBuildQuery for dynamic import (lazy-loading) */
loadBuildQuery?: PromiseOrValueLoader<ValueOrModuleWithValue<BuildQueryFunction<FormData>>>;
loadBuildQuery?: PromiseOrValueLoader<
ValueOrModuleWithValue<BuildQueryFunction<FormData>>
>;
/** Use transformProps for immediate value. For lazy-loading, use loadTransformProps. */
transformProps?: TransformProps<Props>;
/** Use loadTransformProps for dynamic import (lazy-loading) */
loadTransformProps?: PromiseOrValueLoader<ValueOrModuleWithValue<TransformProps<Props>>>;
loadTransformProps?: PromiseOrValueLoader<
ValueOrModuleWithValue<TransformProps<Props>>
>;
/** Use Chart for immediate value. For lazy-loading, use loadChart. */
Chart?: ChartType;
/** Use loadChart for dynamic import (lazy-loading) */
@ -72,7 +76,9 @@ function sanitizeLoader<T>(
const loaded = loader();
return loaded instanceof Promise
? (loaded.then(module => ('default' in module && module.default) || module) as Promise<T>)
? (loaded.then(
module => ('default' in module && module.default) || module,
) as Promise<T>)
: (loaded as T);
};
}
@ -109,7 +115,9 @@ export default class ChartPlugin<
(loadBuildQuery && sanitizeLoader(loadBuildQuery)) ||
(buildQuery && sanitizeLoader(() => buildQuery)) ||
undefined;
this.loadTransformProps = sanitizeLoader(loadTransformProps ?? (() => transformProps));
this.loadTransformProps = sanitizeLoader(
loadTransformProps ?? (() => transformProps),
);
if (loadChart) {
this.loadChart = sanitizeLoader<ChartType>(loadChart);
@ -125,7 +133,10 @@ export default class ChartPlugin<
getChartMetadataRegistry().registerValue(key, this.metadata);
getChartComponentRegistry().registerLoader(key, this.loadChart);
getChartControlPanelRegistry().registerValue(key, this.controlPanel);
getChartTransformPropsRegistry().registerLoader(key, this.loadTransformProps);
getChartTransformPropsRegistry().registerLoader(
key,
this.loadTransformProps,
);
if (this.loadBuildQuery) {
getChartBuildQueryRegistry().registerLoader(key, this.loadBuildQuery);
}

View File

@ -20,7 +20,10 @@
import { Registry, makeSingleton } from '../..';
import { ChartControlPanel } from '../models/ChartControlPanel';
class ChartControlPanelRegistry extends Registry<ChartControlPanel, ChartControlPanel> {
class ChartControlPanelRegistry extends Registry<
ChartControlPanel,
ChartControlPanel
> {
constructor() {
super({ name: 'ChartControlPanel' });
}

View File

@ -22,7 +22,10 @@ import { TransformProps } from '../types/TransformFunction';
class ChartTransformPropsRegistry extends Registry<TransformProps<any>> {
constructor() {
super({ name: 'ChartTransformProps', overwritePolicy: OverwritePolicy.WARN });
super({
name: 'ChartTransformProps',
overwritePolicy: OverwritePolicy.WARN,
});
}
}

View File

@ -20,7 +20,11 @@
/**
* Types for query response
*/
import { DataRecordValue, DataRecord, ChartDataResponseResult } from '../../types';
import {
DataRecordValue,
DataRecord,
ChartDataResponseResult,
} from '../../types';
import { PlainObject } from './Base';
export interface TimeseriesDataRecord extends DataRecord {

View File

@ -17,16 +17,24 @@
* under the License.
*/
import { QueryFormData, QueryContext, SetDataMaskHook, JsonObject } from '../..';
import {
QueryFormData,
QueryContext,
SetDataMaskHook,
JsonObject,
} from '../..';
import ChartProps from '../models/ChartProps';
import { PlainObject } from './Base';
export type PlainProps = PlainObject;
type TransformFunction<Input = PlainProps, Output = PlainProps> = (x: Input) => Output;
type TransformFunction<Input = PlainProps, Output = PlainProps> = (
x: Input,
) => Output;
export type PreTransformProps = TransformFunction<ChartProps, ChartProps>;
export type TransformProps<Props extends ChartProps = ChartProps> = TransformFunction<Props>;
export type TransformProps<Props extends ChartProps = ChartProps> =
TransformFunction<Props>;
export type PostTransformProps = TransformFunction;
export type BuildQueryFunction<T extends QueryFormData> = (

View File

@ -40,7 +40,10 @@ export default class CategoricalColorNamespace {
getScale(schemeId?: string) {
const id = schemeId ?? getCategoricalSchemeRegistry().getDefaultKey() ?? '';
const scheme = getCategoricalSchemeRegistry().get(id);
const newScale = new CategoricalColorScale(scheme?.colors ?? [], this.forcedItems);
const newScale = new CategoricalColorScale(
scheme?.colors ?? [],
this.forcedItems,
);
return newScale;
}
@ -80,7 +83,11 @@ export function getNamespace(name: string = DEFAULT_NAMESPACE) {
return newInstance;
}
export function getColor(value?: string, schemeId?: string, namespace?: string) {
export function getColor(
value?: string,
schemeId?: string,
namespace?: string,
) {
return getNamespace(namespace).getScale(schemeId).getColor(value);
}

View File

@ -57,7 +57,8 @@ class CategoricalColorScale extends ExtensibleFunction {
getColor(value?: string) {
const cleanedValue = stringifyAndTrim(value);
const parentColor = this.parentForcedColors && this.parentForcedColors[cleanedValue];
const parentColor =
this.parentForcedColors && this.parentForcedColors[cleanedValue];
if (parentColor) {
return parentColor;
}
@ -101,7 +102,10 @@ class CategoricalColorScale extends ExtensibleFunction {
* Returns an exact copy of this scale. Changes to this scale will not affect the returned scale, and vice versa.
*/
copy() {
const copy = new CategoricalColorScale(this.scale.range(), this.parentForcedColors);
const copy = new CategoricalColorScale(
this.scale.range(),
this.parentForcedColors,
);
copy.forcedColors = { ...this.forcedColors };
copy.domain(this.domain());
copy.unknown(this.unknown());

View File

@ -36,7 +36,13 @@ export default class ColorScheme {
isDefault?: boolean;
constructor({ colors, description = '', id, label, isDefault }: ColorSchemeConfig) {
constructor({
colors,
description = '',
id,
label,
isDefault,
}: ColorSchemeConfig) {
this.id = id;
this.label = label ?? id;
this.colors = colors;

View File

@ -18,7 +18,12 @@
*/
import { scaleLinear } from 'd3-scale';
import { interpolateHcl, interpolateNumber, piecewise, quantize } from 'd3-interpolate';
import {
interpolateHcl,
interpolateNumber,
piecewise,
quantize,
} from 'd3-interpolate';
import ColorScheme, { ColorSchemeConfig } from './ColorScheme';
export interface SequentialSchemeConfig extends ColorSchemeConfig {
@ -51,7 +56,9 @@ export default class SequentialScheme extends ColorScheme {
return modifyRange || domain.length === this.colors.length
? scale.domain(domain).range(this.getColors(domain.length))
: scale
.domain(quantize(piecewise(interpolateNumber, domain), this.colors.length))
.domain(
quantize(piecewise(interpolateNumber, domain), this.colors.length),
)
.range(this.colors);
}
@ -63,14 +70,27 @@ export default class SequentialScheme extends ColorScheme {
* For example [0.2, 1] will rescale the color scheme
* such that color values in the range [0, 0.2) are excluded from the scheme.
*/
getColors(numColors = this.colors.length, extent: number[] = [0, 1]): string[] {
if (numColors === this.colors.length && extent[0] === 0 && extent[1] === 1) {
getColors(
numColors = this.colors.length,
extent: number[] = [0, 1],
): string[] {
if (
numColors === this.colors.length &&
extent[0] === 0 &&
extent[1] === 1
) {
return this.colors;
}
const piecewiseScale: (t: number) => string = piecewise(interpolateHcl, this.colors);
const piecewiseScale: (t: number) => string = piecewise(
interpolateHcl,
this.colors,
);
const adjustExtent = scaleLinear().range(extent).clamp(true);
return quantize<string>(t => piecewiseScale(adjustExtent(t) as number), numColors);
return quantize<string>(
t => piecewiseScale(adjustExtent(t) as number),
numColors,
);
}
}

View File

@ -43,7 +43,8 @@ function SafeMarkdown({ source }: SafeMarkdownProps) {
allowNode={isSafeMarkup}
astPlugins={[
htmlParser({
isValidNode: (node: MarkdownAbstractSyntaxTree) => node.type !== 'script',
isValidNode: (node: MarkdownAbstractSyntaxTree) =>
node.type !== 'script',
}),
]}
/>

View File

@ -24,7 +24,9 @@ let singletonClient: SupersetClientClass | undefined;
function getInstance(): SupersetClientClass {
if (!singletonClient) {
throw new Error('You must call SupersetClient.configure(...) before calling other methods');
throw new Error(
'You must call SupersetClient.configure(...) before calling other methods',
);
}
return singletonClient;
}

View File

@ -66,7 +66,9 @@ export default class SupersetClientClass {
csrfToken = undefined,
}: ClientConfig = {}) {
const url = new URL(
host || protocol ? `${protocol || 'https:'}//${host || 'localhost'}` : baseUrl,
host || protocol
? `${protocol || 'https:'}//${host || 'localhost'}`
: baseUrl,
// baseUrl for API could also be relative, so we provide current location.href
// as the base of baseUrl
window.location.href,
@ -79,7 +81,10 @@ export default class SupersetClientClass {
this.timeout = timeout;
this.credentials = credentials;
this.csrfToken = csrfToken;
this.fetchRetryOptions = { ...DEFAULT_FETCH_RETRY_OPTIONS, ...fetchRetryOptions };
this.fetchRetryOptions = {
...DEFAULT_FETCH_RETRY_OPTIONS,
...fetchRetryOptions,
};
if (typeof this.csrfToken === 'string') {
this.headers = { ...this.headers, 'X-CSRFToken': this.csrfToken };
this.csrfPromise = Promise.resolve(this.csrfToken);
@ -102,19 +107,27 @@ export default class SupersetClientClass {
return this.csrfToken !== null && this.csrfToken !== undefined;
}
async get<T extends ParseMethod = 'json'>(requestConfig: RequestConfig & { parseMethod?: T }) {
async get<T extends ParseMethod = 'json'>(
requestConfig: RequestConfig & { parseMethod?: T },
) {
return this.request({ ...requestConfig, method: 'GET' });
}
async delete<T extends ParseMethod = 'json'>(requestConfig: RequestConfig & { parseMethod?: T }) {
async delete<T extends ParseMethod = 'json'>(
requestConfig: RequestConfig & { parseMethod?: T },
) {
return this.request({ ...requestConfig, method: 'DELETE' });
}
async put<T extends ParseMethod = 'json'>(requestConfig: RequestConfig & { parseMethod?: T }) {
async put<T extends ParseMethod = 'json'>(
requestConfig: RequestConfig & { parseMethod?: T },
) {
return this.request({ ...requestConfig, method: 'PUT' });
}
async post<T extends ParseMethod = 'json'>(requestConfig: RequestConfig & { parseMethod?: T }) {
async post<T extends ParseMethod = 'json'>(
requestConfig: RequestConfig & { parseMethod?: T },
) {
return this.request({ ...requestConfig, method: 'POST' });
}
@ -197,6 +210,8 @@ export default class SupersetClientClass {
const host = inputHost ?? this.host;
const cleanHost = host.slice(-1) === '/' ? host.slice(0, -1) : host; // no backslash
return `${this.protocol}//${cleanHost}/${endpoint[0] === '/' ? endpoint.slice(1) : endpoint}`;
return `${this.protocol}//${cleanHost}/${
endpoint[0] === '/' ? endpoint.slice(1) : endpoint
}`;
}
}

View File

@ -20,11 +20,18 @@
import 'whatwg-fetch';
import fetchRetry from 'fetch-retry';
import { CallApi, Payload, JsonValue, JsonObject } from '../types';
import { CACHE_AVAILABLE, CACHE_KEY, HTTP_STATUS_NOT_MODIFIED, HTTP_STATUS_OK } from '../constants';
import {
CACHE_AVAILABLE,
CACHE_KEY,
HTTP_STATUS_NOT_MODIFIED,
HTTP_STATUS_OK,
} from '../constants';
function tryParsePayload(payload: Payload) {
try {
return typeof payload === 'string' ? (JSON.parse(payload) as JsonValue) : payload;
return typeof payload === 'string'
? (JSON.parse(payload) as JsonValue)
: payload;
} catch (error) {
throw new Error(`Invalid payload:\n\n${payload}`);
}
@ -36,7 +43,8 @@ function tryParsePayload(payload: Payload) {
function getFullUrl(partialUrl: string, params: CallApi['searchParams']) {
if (params) {
const url = new URL(partialUrl, window.location.href);
const search = params instanceof URLSearchParams ? params : new URLSearchParams(params);
const search =
params instanceof URLSearchParams ? params : new URLSearchParams(params);
// will completely override any existing search params
url.search = search.toString();
return url.href;
@ -129,7 +137,10 @@ export default async function callApi({
Object.keys(payload).forEach(key => {
const value = (payload as JsonObject)[key] as JsonValue;
if (typeof value !== 'undefined') {
formData.append(key, stringify ? JSON.stringify(value) : String(value));
formData.append(
key,
stringify ? JSON.stringify(value) : String(value),
);
}
});
request.body = formData;
@ -137,7 +148,10 @@ export default async function callApi({
}
if (jsonPayload !== undefined) {
request.body = JSON.stringify(jsonPayload);
request.headers = { ...request.headers, 'Content-Type': 'application/json' };
request.headers = {
...request.headers,
'Content-Type': 'application/json',
};
}
}

View File

@ -22,7 +22,9 @@ import rejectAfterTimeout from './rejectAfterTimeout';
import parseResponse from './parseResponse';
import { CallApi, ClientTimeout, ParseMethod } from '../types';
export default async function callApiAndParseWithTimeout<T extends ParseMethod = 'json'>({
export default async function callApiAndParseWithTimeout<
T extends ParseMethod = 'json',
>({
timeout,
parseMethod,
...rest

View File

@ -55,5 +55,7 @@ export default async function parseResponse<T extends ParseMethod = 'json'>(
};
return result as ReturnType;
}
throw new Error(`Expected parseResponse=json|text|raw|null, got '${parseMethod}'.`);
throw new Error(
`Expected parseResponse=json|text|raw|null, got '${parseMethod}'.`,
);
}

View File

@ -24,8 +24,12 @@ export type Credentials = RequestInit['credentials'];
export type Endpoint = string;
export type FetchRetryOptions = {
retries?: number;
retryDelay?: number | ((attempt: number, error: Error, response: Response) => number);
retryOn?: number[] | ((attempt: number, error: Error, response: Response) => boolean);
retryDelay?:
| number
| ((attempt: number, error: Error, response: Response) => number);
retryOn?:
| number[]
| ((attempt: number, error: Error, response: Response) => boolean);
};
export type Headers = { [k: string]: string };
export type Host = string;
@ -38,7 +42,10 @@ export type JsonPrimitive = string | number | boolean | null;
* used as function arguments.
* (Ref: https://github.com/microsoft/TypeScript/issues/15300).
*/
export type StrictJsonValue = JsonPrimitive | StrictJsonObject | StrictJsonArray;
export type StrictJsonValue =
| JsonPrimitive
| StrictJsonObject
| StrictJsonArray;
export type StrictJsonArray = StrictJsonValue[];
/**
* More strict JSON objects that makes sure all values are plain objects.
@ -132,7 +139,14 @@ export interface ClientConfig {
export interface SupersetClientInterface
extends Pick<
SupersetClientClass,
'delete' | 'get' | 'post' | 'put' | 'request' | 'init' | 'isAuthenticated' | 'reAuthenticate'
| 'delete'
| 'get'
| 'post'
| 'put'
| 'request'
| 'init'
| 'isAuthenticated'
| 'reAuthenticate'
> {
configure: (config?: ClientConfig) => SupersetClientClass;
getInstance: (maybeClient?: SupersetClientClass) => SupersetClientClass;

View File

@ -48,7 +48,9 @@ export default function computeMaxFontSize(
if (idealFontSize !== undefined && idealFontSize !== null) {
size = idealFontSize;
} else if (maxHeight === undefined || maxHeight === null) {
throw new Error('You must specify at least one of maxHeight or idealFontSize');
throw new Error(
'You must specify at least one of maxHeight or idealFontSize',
);
} else {
size = Math.floor(maxHeight);
}
@ -61,11 +63,19 @@ export default function computeMaxFontSize(
}
if (maxWidth !== undefined && maxWidth !== null) {
size = decreaseSizeUntil(size, computeDimension, dim => dim.width <= maxWidth);
size = decreaseSizeUntil(
size,
computeDimension,
dim => dim.width <= maxWidth,
);
}
if (maxHeight !== undefined && maxHeight !== null) {
size = decreaseSizeUntil(size, computeDimension, dim => dim.height <= maxHeight);
size = decreaseSizeUntil(
size,
computeDimension,
dim => dim.height <= maxHeight,
);
}
return size;

View File

@ -19,7 +19,11 @@
import { Margin } from './types';
function mergeOneSide(operation: (a: number, b: number) => number, a = 0, b = 0) {
function mergeOneSide(
operation: (a: number, b: number) => number,
a = 0,
b = 0,
) {
if (Number.isNaN(a) || a === null) return b;
if (Number.isNaN(b) || b === null) return a;

View File

@ -21,12 +21,18 @@ const HUNDRED_PERCENT = { isDynamic: true, multiplier: 1 } as const;
export default function parseLength(
input: string | number,
): { isDynamic: true; multiplier: number } | { isDynamic: false; value: number } {
):
| { isDynamic: true; multiplier: number }
| { isDynamic: false; value: number } {
if (input === 'auto' || input === '100%') {
return HUNDRED_PERCENT;
}
if (typeof input === 'string' && input.length > 0 && input[input.length - 1] === '%') {
if (
typeof input === 'string' &&
input.length > 0 &&
input[input.length - 1] === '%'
) {
return { isDynamic: true, multiplier: parseFloat(input) / 100 };
}
const value = typeof input === 'number' ? input : parseFloat(input);

View File

@ -61,7 +61,8 @@ export default function updateTextNode(
// Apply new style
// Note: the font field will auto-populate other font fields when applicable.
STYLE_FIELDS.filter(
(field: keyof TextStyle) => typeof style[field] !== 'undefined' && style[field] !== null,
(field: keyof TextStyle) =>
typeof style[field] !== 'undefined' && style[field] !== null,
).forEach((field: keyof TextStyle) => {
textNode.style[field] = `${style[field]}`;
});

View File

@ -49,7 +49,10 @@ const withNamespace = (name: string) => `__superset__/${name}`;
* @param name the module's name (should match name in package.json)
* @param promise the promise resulting from a call to `import(name)`
*/
export async function defineSharedModule(name: string, fetchModule: () => Promise<Module>) {
export async function defineSharedModule(
name: string,
fetchModule: () => Promise<Module>,
) {
// this field on window is used by dynamic plugins to reference the module
const moduleKey = withNamespace(name);
@ -75,9 +78,13 @@ export async function defineSharedModule(name: string, fetchModule: () => Promis
* @see defineSharedModule
* @param moduleMap
*/
export async function defineSharedModules(moduleMap: { [key: string]: () => Promise<Module> }) {
export async function defineSharedModules(moduleMap: {
[key: string]: () => Promise<Module>;
}) {
return Promise.all(
Object.entries(moduleMap).map(([name, fetchModule]) => defineSharedModule(name, fetchModule)),
Object.entries(moduleMap).map(([name, fetchModule]) =>
defineSharedModule(name, fetchModule),
),
);
}

View File

@ -35,7 +35,10 @@ interface ItemWithLoader<T> {
*/
type InclusiveLoaderResult<V> = V | Promise<V>;
export type RegistryValue<V, W extends InclusiveLoaderResult<V>> = V | W | undefined;
export type RegistryValue<V, W extends InclusiveLoaderResult<V>> =
| V
| W
| undefined;
export type RegistryEntry<V, W extends InclusiveLoaderResult<V>> = {
key: string;
@ -63,7 +66,10 @@ export interface RegistryConfig {
* By default W is set to V | Promise<V> to support
* both synchronous and asynchronous loaders.
*/
export default class Registry<V, W extends InclusiveLoaderResult<V> = InclusiveLoaderResult<V>> {
export default class Registry<
V,
W extends InclusiveLoaderResult<V> = InclusiveLoaderResult<V>,
> {
name: string;
overwritePolicy: OverwritePolicy;
@ -106,13 +112,18 @@ export default class Registry<V, W extends InclusiveLoaderResult<V> = InclusiveL
registerValue(key: string, value: V) {
const item = this.items[key];
const willOverwrite =
this.has(key) && (('value' in item && item.value !== value) || 'loader' in item);
this.has(key) &&
(('value' in item && item.value !== value) || 'loader' in item);
if (willOverwrite) {
if (this.overwritePolicy === OverwritePolicy.WARN) {
// eslint-disable-next-line no-console
console.warn(`Item with key "${key}" already exists. You are assigning a new value.`);
console.warn(
`Item with key "${key}" already exists. You are assigning a new value.`,
);
} else if (this.overwritePolicy === OverwritePolicy.PROHIBIT) {
throw new Error(`Item with key "${key}" already exists. Cannot overwrite.`);
throw new Error(
`Item with key "${key}" already exists. Cannot overwrite.`,
);
}
}
if (!item || willOverwrite) {
@ -127,13 +138,18 @@ export default class Registry<V, W extends InclusiveLoaderResult<V> = InclusiveL
registerLoader(key: string, loader: () => W) {
const item = this.items[key];
const willOverwrite =
this.has(key) && (('loader' in item && item.loader !== loader) || 'value' in item);
this.has(key) &&
(('loader' in item && item.loader !== loader) || 'value' in item);
if (willOverwrite) {
if (this.overwritePolicy === OverwritePolicy.WARN) {
// eslint-disable-next-line no-console
console.warn(`Item with key "${key}" already exists. You are assigning a new value.`);
console.warn(
`Item with key "${key}" already exists. You are assigning a new value.`,
);
} else if (this.overwritePolicy === OverwritePolicy.PROHIBIT) {
throw new Error(`Item with key "${key}" already exists. Cannot overwrite.`);
throw new Error(
`Item with key "${key}" already exists. Cannot overwrite.`,
);
}
}
if (!item || willOverwrite) {
@ -172,7 +188,9 @@ export default class Registry<V, W extends InclusiveLoaderResult<V> = InclusiveL
return newPromise;
}
return Promise.reject<V>(new Error(`Item with key "${key}" is not registered.`));
return Promise.reject<V>(
new Error(`Item with key "${key}" is not registered.`),
);
}
getMap() {

View File

@ -36,7 +36,8 @@ export default class RegistryWithDefaultKey<
constructor(config: RegistryWithDefaultKeyConfig = {}) {
super(config);
const { initialDefaultKey = undefined, setFirstItemAsDefault = false } = config;
const { initialDefaultKey = undefined, setFirstItemAsDefault = false } =
config;
this.initialDefaultKey = initialDefaultKey;
this.defaultKey = initialDefaultKey;
this.setFirstItemAsDefault = setFirstItemAsDefault;

View File

@ -32,7 +32,10 @@ export default class NumberFormatterRegistry extends RegistryWithDefaultKey<
overwritePolicy: OverwritePolicy.WARN,
});
this.registerValue(NumberFormats.SMART_NUMBER, createSmartNumberFormatter());
this.registerValue(
NumberFormats.SMART_NUMBER,
createSmartNumberFormatter(),
);
this.registerValue(
NumberFormats.SMART_NUMBER_SIGNED,
createSmartNumberFormatter({ signed: true }),
@ -42,7 +45,9 @@ export default class NumberFormatterRegistry extends RegistryWithDefaultKey<
get(formatterId?: string) {
const targetFormat = `${
formatterId === null || typeof formatterId === 'undefined' || formatterId === ''
formatterId === null ||
typeof formatterId === 'undefined' ||
formatterId === ''
? this.defaultKey
: formatterId
}`.trim();
@ -60,7 +65,10 @@ export default class NumberFormatterRegistry extends RegistryWithDefaultKey<
return formatter;
}
format(formatterId: string | undefined, value: number | null | undefined): string {
format(
formatterId: string | undefined,
value: number | null | undefined,
): string {
return this.get(formatterId)(value);
}
}

View File

@ -27,6 +27,9 @@ export function getNumberFormatter(format?: string) {
return getInstance().get(format);
}
export function formatNumber(format: string | undefined, value: number | null | undefined) {
export function formatNumber(
format: string | undefined,
value: number | null | undefined,
) {
return getInstance().format(format, value);
}

View File

@ -16,7 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
import { format as d3Format, formatLocale, FormatLocaleDefinition } from 'd3-format';
import {
format as d3Format,
formatLocale,
FormatLocaleDefinition,
} from 'd3-format';
import { isRequired } from '../../utils';
import NumberFormatter from '../NumberFormatter';
import { NumberFormatFunction } from '../types';
@ -27,7 +31,12 @@ export default function createD3NumberFormatter(config: {
label?: string;
locale?: FormatLocaleDefinition;
}) {
const { description, formatString = isRequired('config.formatString'), label, locale } = config;
const {
description,
formatString = isRequired('config.formatString'),
label,
locale,
} = config;
let formatFunc: NumberFormatFunction;
let isInvalid = false;

View File

@ -61,7 +61,10 @@ export default function createSmartNumberFormatter(
return new NumberFormatter({
description,
formatFunc: value => `${getSign(value)}${formatValue(value)}`,
id: id || signed ? NumberFormats.SMART_NUMBER_SIGNED : NumberFormats.SMART_NUMBER,
id:
id || signed
? NumberFormats.SMART_NUMBER_SIGNED
: NumberFormats.SMART_NUMBER,
label: label ?? 'Adaptive formatter',
});
}

View File

@ -27,7 +27,8 @@ export default class DatasourceKey {
constructor(key: string) {
const [idStr, typeStr] = key.split('__');
this.id = parseInt(idStr, 10);
this.type = typeStr === 'table' ? DatasourceType.Table : DatasourceType.Druid;
this.type =
typeStr === 'table' ? DatasourceType.Table : DatasourceType.Druid;
}
public toString() {

View File

@ -40,7 +40,9 @@ export default async function fetchExploreJson({
method,
endpoint,
searchParams:
method === 'GET' ? new URLSearchParams({ form_data: JSON.stringify(formData) }) : undefined,
method === 'GET'
? new URLSearchParams({ form_data: JSON.stringify(formData) })
: undefined,
postPayload: method === 'POST' ? { form_data: formData } : undefined,
});
return json as LegacyChartDataResponse;

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