Skip to content

Commit

Permalink
Add ChartClient (v2) (apache#57)
Browse files Browse the repository at this point in the history
feat: Add ChartClient
  • Loading branch information
kristw committed Dec 12, 2018
1 parent 2896242 commit d8d2bb2
Show file tree
Hide file tree
Showing 12 changed files with 422 additions and 14 deletions.
2 changes: 2 additions & 0 deletions packages/superset-ui-chart/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
},
"devDependencies": {
"@superset-ui/connection": "^0.7.0",
"fetch-mock": "^7.2.5",
"node-fetch": "^2.2.0",
"react": "^15 || ^16"
},
"peerDependencies": {
Expand Down
138 changes: 138 additions & 0 deletions packages/superset-ui-chart/src/clients/ChartClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { isDefined } from '@superset-ui/core';
import {
SupersetClient,
RequestConfig,
SupersetClientClass,
SupersetClientResponse,
Json,
} from '@superset-ui/connection';
import getChartBuildQueryRegistry from '../registries/ChartBuildQueryRegistrySingleton';
import { FormData, AnnotationLayerMetadata } from '../query/FormData';

interface SliceIdAndOrFormData {
sliceId?: number;
formData?: FormData;
}

interface AnnotationData {
[key: string]: object;
}

interface ChartData {
annotationData: AnnotationData;
datasource: object;
formData: FormData;
queryData: object;
}

export interface ChartClientConfig {
client?: SupersetClientClass;
}

export class ChartClient {
readonly client: {
get: (config: RequestConfig) => Promise<SupersetClientResponse>;
post: (config: RequestConfig) => Promise<SupersetClientResponse>;
};

constructor(config: ChartClientConfig = {}) {
const { client = SupersetClient } = config;
this.client = client;
}

loadFormData(input: SliceIdAndOrFormData, options?: RequestConfig): Promise<FormData> {
/* If sliceId is provided, use it to fetch stored formData from API */
if (input.sliceId) {
const promise = this.client
.get({
endpoint: `/api/v1/formData/?slice_id=${input.sliceId}`,
...options,
} as RequestConfig)
.then(response => response.json as Json)
.then(json => json.form_data);

/*
* If formData is also specified, override API result
* with user-specified formData
*/
return input.formData
? promise.then(
(dbFormData: object) =>
({
...dbFormData,
...input.formData,
} as FormData),
)
: promise.then((dbFormData: object) => dbFormData as FormData);
}

/* If sliceId is not provided, returned formData wrapped in a Promise */
return input.formData
? Promise.resolve(input.formData)
: Promise.reject(new Error('At least one of sliceId or formData must be specified'));
}

loadQueryData(formData: FormData, options?: RequestConfig): Promise<object> {
const buildQuery = getChartBuildQueryRegistry().get(formData.viz_type);
if (buildQuery) {
return this.client
.post({
endpoint: '/api/v1/query/',
postPayload: { query_context: buildQuery(formData) },
...options,
} as RequestConfig)
.then(response => response.json as Json);
}

return Promise.reject(new Error(`Unknown chart type: ${formData.viz_type}`));
}

loadDatasource(datasourceKey: string, options?: RequestConfig): Promise<object> {
return this.client
.get({
endpoint: `/superset/fetch_datasource_metadata?datasourceKey=${datasourceKey}`,
...options,
} as RequestConfig)
.then(response => response.json as Json);
}

loadAnnotation(annotationLayer: AnnotationLayerMetadata): Promise<object> {
/* When annotation does not require query */
if (!isDefined(annotationLayer.sourceType)) {
return Promise.resolve({});
}

// TODO: Implement
return Promise.reject(new Error('This feature is not implemented yet.'));
}

loadAnnotations(annotationLayers?: Array<AnnotationLayerMetadata>): Promise<AnnotationData> {
if (Array.isArray(annotationLayers) && annotationLayers.length > 0) {
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];

return output;
}, {}),
);
}

return Promise.resolve({});
}

loadChartData(input: SliceIdAndOrFormData): Promise<ChartData> {
return this.loadFormData(input).then(formData =>
Promise.all([
this.loadAnnotations(formData.annotation_layers),
this.loadDatasource(formData.datasource),
this.loadQueryData(formData),
]).then(([annotationData, datasource, queryData]) => ({
annotationData,
datasource,
formData,
queryData,
})),
);
}
}
1 change: 1 addition & 0 deletions packages/superset-ui-chart/src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { ChartClient, ChartClientConfig } from './clients/ChartClient';
export { ChartMetadata, ChartMetadataConfig } from './models/ChartMetadata';
export {
ChartPlugin,
Expand Down
4 changes: 4 additions & 0 deletions packages/superset-ui-chart/src/models/ChartPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,8 @@ export class ChartPlugin extends Plugin {

return this;
}

configure(config: { [key: string]: any }): ChartPlugin {
return super.configure(config);
}
}
9 changes: 9 additions & 0 deletions packages/superset-ui-chart/src/query/FormData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,18 @@ import { FormDataMetric, MetricKey } from './Metric';
// unified into a proper Metric type during buildQuery (see `/query/Metrics.ts`).
type Metrics = Partial<Record<MetricKey, FormDataMetric | FormDataMetric[]>>;

export type AnnotationLayerMetadata = {
name: string;
sourceType?: string;
};

/* eslint-disable camelcase */
type BaseFormData = {
datasource: string;
viz_type: string;
annotation_layers?: Array<AnnotationLayerMetadata>;
} & Metrics;
/* eslint-enable camelcase */

// FormData is either sqla-based or druid-based
type SqlaFormData = {
Expand Down
Loading

0 comments on commit d8d2bb2

Please sign in to comment.