Skip to content
This repository has been archived by the owner on Dec 10, 2021. It is now read-only.

Commit

Permalink
chore(chart): remove faux @superset-ui/core TS declaration (#121)
Browse files Browse the repository at this point in the history
* chore(chart): remove faux @superset-ui/core TS declaration

* test(chart): test all ChartPlugin.register() branches

* refactor(chart): support loaders that return esmodules

* refactor(chart): rename ChartMetaDataConfig => ChartMetadataConfig

* test(chart): fix loader test + branch coverage

* test(chart): hit all branches in sanitizeLoader

* refactor(chart): use ChartMetadata in registry
  • Loading branch information
williaster authored Mar 22, 2019
1 parent ccf9965 commit e4b7adb
Show file tree
Hide file tree
Showing 15 changed files with 133 additions and 94 deletions.
10 changes: 6 additions & 4 deletions packages/superset-ui-chart/src/clients/ChartClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,14 @@ export default class ChartClient {
: Promise.reject(new Error('At least one of sliceId or formData must be specified'));
}

loadQueryData(formData: ChartFormData, options?: Partial<RequestConfig>): Promise<object> {
async loadQueryData(formData: ChartFormData, options?: Partial<RequestConfig>): Promise<object> {
const { viz_type: visType } = formData;
const metaDataRegistry = getChartMetadataRegistry();
const buildQueryRegistry = getChartBuildQueryRegistry();

if (getChartMetadataRegistry().has(visType)) {
const { useLegacyApi } = getChartMetadataRegistry().get(visType);
const buildQuery = useLegacyApi ? () => formData : getChartBuildQueryRegistry().get(visType);
if (metaDataRegistry.has(visType)) {
const { useLegacyApi } = metaDataRegistry.get(visType)!;
const buildQuery = (await buildQueryRegistry.get(visType)) || (() => formData);

return this.client
.post({
Expand Down
36 changes: 14 additions & 22 deletions packages/superset-ui-chart/src/components/SuperChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import getChartComponentRegistry from '../registries/ChartComponentRegistrySingl
import getChartTransformPropsRegistry from '../registries/ChartTransformPropsRegistrySingleton';
import ChartProps from '../models/ChartProps';
import createLoadableRenderer, { LoadableRenderer } from './createLoadableRenderer';
import { ChartType } from '../models/ChartPlugin';
import { PreTransformProps, TransformProps, PostTransformProps } from '../types/Query';

const IDENTITY = (x: any) => x;

Expand All @@ -21,26 +23,21 @@ const defaultProps = {
};
/* eslint-enable sort-keys */

type TransformFunction<Input = PlainProps, Output = PlainProps> = (x: Input) => Output;
type HandlerFunction = (...args: any[]) => void;

interface LoadingProps {
error: any;
}

interface PlainProps {
[key: string]: any;
}

interface LoadedModules {
Chart: React.Component | { default: React.Component };
transformProps: TransformFunction | { default: TransformFunction };
Chart: ChartType;
transformProps: TransformProps;
}

interface RenderProps {
chartProps: ChartProps;
preTransformProps?: TransformFunction<ChartProps>;
postTransformProps?: TransformFunction;
preTransformProps?: PreTransformProps;
postTransformProps?: PostTransformProps;
}

const BLANK_CHART_PROPS = new ChartProps();
Expand All @@ -50,17 +47,13 @@ export interface SuperChartProps {
className?: string;
chartProps?: ChartProps | null;
chartType: string;
preTransformProps?: TransformFunction<ChartProps>;
overrideTransformProps?: TransformFunction;
postTransformProps?: TransformFunction;
preTransformProps?: PreTransformProps;
overrideTransformProps?: TransformProps;
postTransformProps?: PostTransformProps;
onRenderSuccess?: HandlerFunction;
onRenderFailure?: HandlerFunction;
}

function getModule<T>(value: any): T {
return (value.default ? value.default : value) as T;
}

export default class SuperChart extends React.PureComponent<SuperChartProps, {}> {
static defaultProps = defaultProps;

Expand Down Expand Up @@ -125,19 +118,18 @@ export default class SuperChart extends React.PureComponent<SuperChartProps, {}>

processChartProps: (input: {
chartProps: ChartProps;
preTransformProps?: TransformFunction<ChartProps>;
transformProps?: TransformFunction;
postTransformProps?: TransformFunction;
preTransformProps?: PreTransformProps;
transformProps?: TransformProps;
postTransformProps?: PostTransformProps;
}) => any;

createLoadableRenderer: (input: {
chartType: string;
overrideTransformProps?: TransformFunction;
overrideTransformProps?: TransformProps;
}) => LoadableRenderer<RenderProps, LoadedModules> | (() => null);

renderChart(loaded: LoadedModules, props: RenderProps) {
const Chart = getModule<typeof React.Component>(loaded.Chart);
const transformProps = getModule<TransformFunction>(loaded.transformProps);
const { Chart, transformProps } = loaded;
const { chartProps, preTransformProps, postTransformProps } = props;

return (
Expand Down
22 changes: 12 additions & 10 deletions packages/superset-ui-chart/src/models/ChartMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ interface LookupTable {
[key: string]: boolean;
}

export interface ChartMetadataConfig {
name: string;
canBeAnnotationTypes?: string[];
credits?: string[];
description?: string;
show?: boolean;
supportedAnnotationTypes?: string[];
thumbnail: string;
useLegacyApi?: boolean;
}

export default class ChartMetadata {
name: string;
canBeAnnotationTypes?: string[];
Expand All @@ -13,16 +24,7 @@ export default class ChartMetadata {
thumbnail: string;
useLegacyApi: boolean;

constructor(config: {
name: string;
canBeAnnotationTypes?: string[];
credits?: string[];
description?: string;
show?: boolean;
supportedAnnotationTypes?: string[];
thumbnail: string;
useLegacyApi?: boolean;
}) {
constructor(config: ChartMetadataConfig) {
const {
name,
canBeAnnotationTypes = [],
Expand Down
61 changes: 38 additions & 23 deletions packages/superset-ui-chart/src/models/ChartPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,57 @@
import { FunctionComponent, ComponentType } from 'react';
import { isRequired, Plugin } from '@superset-ui/core';
import ChartMetadata from './ChartMetadata';
import ChartProps from './ChartProps';
import getChartMetadataRegistry from '../registries/ChartMetadataRegistrySingleton';
import getChartBuildQueryRegistry from '../registries/ChartBuildQueryRegistrySingleton';
import getChartComponentRegistry from '../registries/ChartComponentRegistrySingleton';
import getChartTransformPropsRegistry from '../registries/ChartTransformPropsRegistrySingleton';
import { ChartFormData } from '../types/ChartFormData';
import { QueryContext } from '../types/Query';
import { BuildQueryFunction, TransformProps } from '../types/Query';

const IDENTITY = (x: any) => x;

type PromiseOrValue<T> = Promise<T> | T;
type PromiseOrValueLoader<T> = () => PromiseOrValue<T> | PromiseOrValue<{ default: T }>;

export type BuildQueryFunction<T extends ChartFormData> = (formData: T) => QueryContext;

export type TransformPropsFunction = (
chartProps: ChartProps,
) => {
[key: string]: any;
};
export type PromiseOrValue<T> = Promise<T> | T;
export type PromiseOrValueLoader<T> = () => PromiseOrValue<T>;
export type ChartType = ComponentType | FunctionComponent;
type ValueOrModuleWithValue<T> = T | { default: T };

interface ChartPluginConfig<T extends ChartFormData> {
metadata: ChartMetadata;
// use buildQuery for immediate value
buildQuery?: BuildQueryFunction<T>;
// use loadBuildQuery for dynamic import (lazy-loading)
loadBuildQuery?: PromiseOrValueLoader<BuildQueryFunction<T>>;
loadBuildQuery?: PromiseOrValueLoader<ValueOrModuleWithValue<BuildQueryFunction<T>>>;
// use transformProps for immediate value
transformProps?: TransformPropsFunction;
transformProps?: TransformProps;
// use loadTransformProps for dynamic import (lazy-loading)
loadTransformProps?: PromiseOrValueLoader<TransformPropsFunction>;
loadTransformProps?: PromiseOrValueLoader<ValueOrModuleWithValue<TransformProps>>;
// use Chart for immediate value
Chart?: Function;
Chart?: ChartType;
// use loadChart for dynamic import (lazy-loading)
loadChart?: PromiseOrValueLoader<Function>;
loadChart?: PromiseOrValueLoader<ValueOrModuleWithValue<ChartType>>;
}

/**
* Loaders of the form `() => import('foo')` may return esmodules
* which require the value to be extracted as `module.default`
* */
function sanitizeLoader<T>(
loader: PromiseOrValueLoader<ValueOrModuleWithValue<T>>,
): PromiseOrValueLoader<T> {
return () => {
const loaded = loader();

return loaded instanceof Promise
? (loaded.then(module => ('default' in module && module.default) || module) as Promise<T>)
: (loaded as T);
};
}

export default class ChartPlugin<T extends ChartFormData = ChartFormData> extends Plugin {
metadata: ChartMetadata;
loadBuildQuery?: PromiseOrValueLoader<BuildQueryFunction<T>>;
loadTransformProps: PromiseOrValueLoader<TransformPropsFunction>;
loadChart: PromiseOrValueLoader<Function>;
loadTransformProps: PromiseOrValueLoader<TransformProps>;
loadChart: PromiseOrValueLoader<ChartType>;

constructor(config: ChartPluginConfig<T>) {
super();
Expand All @@ -55,11 +65,14 @@ export default class ChartPlugin<T extends ChartFormData = ChartFormData> extend
loadChart,
} = config;
this.metadata = metadata;
this.loadBuildQuery = loadBuildQuery || (buildQuery ? () => buildQuery : undefined);
this.loadTransformProps = loadTransformProps || (() => transformProps);
this.loadBuildQuery =
(loadBuildQuery && sanitizeLoader(loadBuildQuery)) ||
(buildQuery && sanitizeLoader(() => buildQuery)) ||
undefined;
this.loadTransformProps = sanitizeLoader(loadTransformProps || (() => transformProps));

if (loadChart) {
this.loadChart = loadChart;
this.loadChart = sanitizeLoader<ChartType>(loadChart);
} else if (Chart) {
this.loadChart = () => Chart;
} else {
Expand All @@ -70,9 +83,11 @@ export default class ChartPlugin<T extends ChartFormData = ChartFormData> extend
register() {
const { key = isRequired('config.key') } = this.config;
getChartMetadataRegistry().registerValue(key, this.metadata);
getChartBuildQueryRegistry().registerLoader(key, this.loadBuildQuery);
getChartComponentRegistry().registerLoader(key, this.loadChart);
getChartTransformPropsRegistry().registerLoader(key, this.loadTransformProps);
if (this.loadBuildQuery) {
getChartBuildQueryRegistry().registerLoader(key, this.loadBuildQuery);
}

return this;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Registry, makeSingleton, OverwritePolicy } from '@superset-ui/core';
import { QueryContext } from '../types/Query';

class ChartBuildQueryRegistry extends Registry {
// Ideally this would be <T extends ChartFormData>
type BuildQuery = (formData: any) => QueryContext;

class ChartBuildQueryRegistry extends Registry<BuildQuery> {
constructor() {
super({ name: 'ChartBuildQuery', overwritePolicy: OverwritePolicy.WARN });
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Registry, makeSingleton, OverwritePolicy } from '@superset-ui/core';
import { ChartType } from '../models/ChartPlugin';

class ChartComponentRegistry extends Registry {
class ChartComponentRegistry extends Registry<ChartType> {
constructor() {
super({ name: 'ChartComponent', overwritePolicy: OverwritePolicy.WARN });
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Registry, makeSingleton, OverwritePolicy } from '@superset-ui/core';
import ChartMetadata from '../models/ChartMetadata';

class ChartMetadataRegistry extends Registry {
class ChartMetadataRegistry extends Registry<ChartMetadata, ChartMetadata> {
constructor() {
super({ name: 'ChartMetadata', overwritePolicy: OverwritePolicy.WARN });
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Registry, makeSingleton, OverwritePolicy } from '@superset-ui/core';
import { TransformProps } from '../types/Query';

class ChartTransformPropsRegistry extends Registry {
class ChartTransformPropsRegistry extends Registry<TransformProps> {
constructor() {
super({ name: 'ChartTransformProps', overwritePolicy: OverwritePolicy.WARN });
}
Expand Down
18 changes: 11 additions & 7 deletions packages/superset-ui-chart/src/types/Query.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* eslint camelcase: 0 */
import ChartProps from '../models/ChartProps';
import { DatasourceType } from './Datasource';
import { ChartFormData } from './ChartFormData';
import { Metric } from './Metric';
import ChartProps from '../models/ChartProps';

export interface QueryObject {
granularity: string;
Expand Down Expand Up @@ -32,10 +32,14 @@ export interface QueryContext {
queries: QueryObject[];
}

export type BuildQueryFunction<T extends ChartFormData> = (formData: T) => QueryContext;

export type TransformPropsFunction = (
chartProps: ChartProps,
) => {
export interface PlainProps {
[key: string]: any;
};
}

type TransformFunction<Input = PlainProps, Output = PlainProps> = (x: Input) => Output;

export type PreTransformProps = TransformFunction<ChartProps>;
export type TransformProps = TransformFunction;
export type PostTransformProps = TransformFunction;

export type BuildQueryFunction<T extends ChartFormData> = (formData: T) => QueryContext;
23 changes: 17 additions & 6 deletions packages/superset-ui-chart/test/clients/ChartClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
buildQueryContext,
ChartFormData,
getChartMetadataRegistry,
ChartMetadata,
} from '../../src';

import { SliceIdAndOrFormData } from '../../src/clients/ChartClient';
Expand Down Expand Up @@ -97,7 +98,10 @@ describe('ChartClient', () => {

describe('.loadQueryData(formData, options)', () => {
it('returns a promise of query data for known chart type', () => {
getChartMetadataRegistry().registerValue('word_cloud', { name: 'Word Cloud' });
getChartMetadataRegistry().registerValue(
'word_cloud',
new ChartMetadata({ name: 'Word Cloud', thumbnail: '' }),
);

getChartBuildQueryRegistry().registerValue('word_cloud', (formData: ChartFormData) =>
buildQueryContext(formData),
Expand Down Expand Up @@ -129,10 +133,14 @@ describe('ChartClient', () => {

it('fetches data from the legacy API if ChartMetadata has useLegacyApi=true,', () => {
// note legacy charts do not register a buildQuery function in the registry
getChartMetadataRegistry().registerValue('word_cloud_legacy', {
name: 'Legacy Word Cloud',
useLegacyApi: true,
});
getChartMetadataRegistry().registerValue(
'word_cloud_legacy',
new ChartMetadata({
name: 'Legacy Word Cloud',
thumbnail: '.png',
useLegacyApi: true,
}),
);

fetchMock.post('glob:*/api/v1/query/', () =>
Promise.reject(Error('Unexpected all to v1 API')),
Expand Down Expand Up @@ -233,7 +241,10 @@ describe('ChartClient', () => {
amet: true,
});

getChartMetadataRegistry().registerValue('line', { name: 'Line' });
getChartMetadataRegistry().registerValue(
'line',
new ChartMetadata({ name: 'Line', thumbnail: '.gif' }),
);

getChartBuildQueryRegistry().registerValue('line', (formData: ChartFormData) =>
buildQueryContext(formData),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ describe('SuperChart', () => {
name: 'second-chart',
thumbnail: '',
}),
// this mirrors `() => import(module)` syntax
loadChart: () => Promise.resolve({ default: TestComponent }),
transformProps: x => x,
// promise without .default
loadTransformProps: () => Promise.resolve((x: any) => x),
});
}
}
Expand All @@ -42,7 +44,7 @@ describe('SuperChart', () => {
thumbnail: '',
}),
loadChart: () =>
new Promise<Function>(resolve => {
new Promise(resolve => {
setTimeout(() => {
resolve(TestComponent);
}, 1000);
Expand Down
Loading

0 comments on commit e4b7adb

Please sign in to comment.