mirror of https://github.com/apache/superset.git
chore: refine prettier config as the main repository (#1456)
* change to prettier.config.js * printWidth to 80 * format * tweak
This commit is contained in:
parent
3cc4861a76
commit
c78551df4f
|
@ -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 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -18,5 +18,8 @@
|
|||
*/
|
||||
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional', '@commitlint/config-lerna-scopes'],
|
||||
extends: [
|
||||
'@commitlint/config-conventional',
|
||||
'@commitlint/config-lerna-scopes',
|
||||
],
|
||||
};
|
||||
|
|
|
@ -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/',
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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}`),
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
) : (
|
||||
<>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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' } },
|
||||
],
|
||||
],
|
||||
};
|
||||
|
|
|
@ -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 {};
|
||||
|
|
|
@ -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);
|
||||
}}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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[];
|
||||
|
||||
|
|
|
@ -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')],
|
||||
|
|
|
@ -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,
|
||||
) || []
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
[],
|
||||
) ?? [];
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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' });
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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> = (
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,8 @@ function SafeMarkdown({ source }: SafeMarkdownProps) {
|
|||
allowNode={isSafeMarkup}
|
||||
astPlugins={[
|
||||
htmlParser({
|
||||
isValidNode: (node: MarkdownAbstractSyntaxTree) => node.type !== 'script',
|
||||
isValidNode: (node: MarkdownAbstractSyntaxTree) =>
|
||||
node.type !== 'script',
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}'.`,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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]}`;
|
||||
});
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue