feat: split superset-ui/query from superset-ui/chart (#178)

BREAKING CHANGE: some api and types are removed from @superset-ui/chart and moved to /query

* feat: split superset-ui/query from superset-ui/chart

* fix: update references

* test: fix broken tests

* refactor: rename ChartFormData to QueryFormData

* fix: rename file

* fix: remove annotation layer from query package
This commit is contained in:
Krist Wongsuphasawat 2019-08-13 12:50:05 -07:00 committed by Yongjie Zhao
parent fa70a61f50
commit 9decd815ec
37 changed files with 155 additions and 107 deletions

View File

@ -43,6 +43,7 @@
"@superset-ui/connection": "^0.11.0",
"@superset-ui/core": "^0.11.0",
"@superset-ui/dimension": "^0.11.10",
"@superset-ui/query": "^0.11.10",
"react": "^15 || ^16"
}
}

View File

@ -6,19 +6,18 @@ import {
Json,
SupersetClientClass,
} from '@superset-ui/connection';
import { QueryFormData, Datasource } from '@superset-ui/query';
import getChartBuildQueryRegistry from '../registries/ChartBuildQueryRegistrySingleton';
import getChartMetadataRegistry from '../registries/ChartMetadataRegistrySingleton';
import { AnnotationLayerMetadata } from '../types/Annotation';
import { ChartFormData } from '../types/ChartFormData';
import { QueryData } from '../models/ChartProps';
import { Datasource } from '../types/Datasource';
import { AnnotationLayerMetadata } from '../types/Annotation';
// 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];
export type SliceIdAndOrFormData = AtLeastOne<{
sliceId: number;
formData: Partial<ChartFormData>;
formData: Partial<QueryFormData>;
}>;
interface AnnotationData {
@ -28,7 +27,7 @@ interface AnnotationData {
export interface ChartData {
annotationData: AnnotationData;
datasource: object;
formData: ChartFormData;
formData: QueryFormData;
queryData: QueryData;
}
@ -47,7 +46,7 @@ export default class ChartClient {
loadFormData(
input: SliceIdAndOrFormData,
options?: Partial<RequestConfig>,
): Promise<ChartFormData> {
): Promise<QueryFormData> {
/* If sliceId is provided, use it to fetch stored formData from API */
if ('sliceId' in input) {
const promise = this.client
@ -62,7 +61,7 @@ export default class ChartClient {
* If formData is also specified, override API result
* with user-specified formData
*/
return promise.then((dbFormData: ChartFormData) => ({
return promise.then((dbFormData: QueryFormData) => ({
...dbFormData,
...input.formData,
}));
@ -70,11 +69,11 @@ export default class ChartClient {
/* If sliceId is not provided, returned formData wrapped in a Promise */
return input.formData
? Promise.resolve(input.formData as ChartFormData)
? Promise.resolve(input.formData as QueryFormData)
: Promise.reject(new Error('At least one of sliceId or formData must be specified'));
}
async loadQueryData(formData: ChartFormData, options?: Partial<RequestConfig>): Promise<object> {
async loadQueryData(formData: QueryFormData, options?: Partial<RequestConfig>): Promise<object> {
const { viz_type: visType } = formData;
const metaDataRegistry = getChartMetadataRegistry();
const buildQueryRegistry = getChartBuildQueryRegistry();
@ -132,17 +131,23 @@ export default class ChartClient {
}
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,
})),
return this.loadFormData(input).then(
(
formData: QueryFormData & {
// eslint-disable-next-line camelcase
annotation_layers?: AnnotationLayerMetadata[];
},
) =>
Promise.all([
this.loadAnnotations(formData.annotation_layers),
this.loadDatasource(formData.datasource),
this.loadQueryData(formData),
]).then(([annotationData, datasource, queryData]) => ({
annotationData,
datasource,
formData,
queryData,
})),
);
}
}

View File

@ -1,14 +1,12 @@
/* eslint react/sort-comp: 'off' */
import React, { ReactNode } from 'react';
import { SupersetClientInterface, RequestConfig } from '@superset-ui/connection';
import { QueryFormData, Datasource } from '@superset-ui/query';
import ChartClient, { SliceIdAndOrFormData } from '../clients/ChartClient';
import { ChartFormData } from '../types/ChartFormData';
import { Datasource } from '../types/Datasource';
import { QueryData } from '../models/ChartProps';
interface Payload {
formData: Partial<ChartFormData>;
formData: Partial<QueryFormData>;
queryData: QueryData;
datasource?: Datasource;
}

View File

@ -16,14 +16,6 @@ export {
default as getChartTransformPropsRegistry,
} from './registries/ChartTransformPropsRegistrySingleton';
export { default as buildQueryContext } from './query/buildQueryContext';
export { default as DatasourceKey } from './query/DatasourceKey';
export { default as ChartDataProvider } from './components/ChartDataProvider';
export * from './types/Annotation';
export * from './types/ChartFormData';
export * from './types/Datasource';
export * from './types/Metric';
export * from './types/Query';
export * from './types/TransformFunction';

View File

@ -1,11 +1,11 @@
import { FunctionComponent, ComponentType } from 'react';
import { isRequired, Plugin } from '@superset-ui/core';
import { QueryFormData } from '@superset-ui/query';
import ChartMetadata from './ChartMetadata';
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 { BuildQueryFunction, TransformProps } from '../types/TransformFunction';
const IDENTITY = (x: any) => x;
@ -15,7 +15,7 @@ export type PromiseOrValueLoader<T> = () => PromiseOrValue<T>;
export type ChartType = ComponentType<any> | FunctionComponent<any>;
type ValueOrModuleWithValue<T> = T | { default: T };
interface ChartPluginConfig<T extends ChartFormData> {
interface ChartPluginConfig<T extends QueryFormData> {
metadata: ChartMetadata;
/** Use buildQuery for immediate value. For lazy-loading, use loadBuildQuery. */
buildQuery?: BuildQueryFunction<T>;
@ -47,7 +47,7 @@ function sanitizeLoader<T>(
};
}
export default class ChartPlugin<T extends ChartFormData = ChartFormData> extends Plugin {
export default class ChartPlugin<T extends QueryFormData = QueryFormData> extends Plugin {
metadata: ChartMetadata;
loadBuildQuery?: PromiseOrValueLoader<BuildQueryFunction<T>>;
loadTransformProps: PromiseOrValueLoader<TransformProps>;

View File

@ -1,7 +1,7 @@
import { Registry, makeSingleton, OverwritePolicy } from '@superset-ui/core';
import { QueryContext } from '../types/Query';
import { QueryContext } from '@superset-ui/query';
// Ideally this would be <T extends ChartFormData>
// Ideally this would be <T extends QueryFormData>
type BuildQuery = (formData: any) => QueryContext;
class ChartBuildQueryRegistry extends Registry<BuildQuery> {

View File

@ -1,6 +1,5 @@
import { ChartFormData } from './ChartFormData';
import { QueryFormData, QueryContext } from '@superset-ui/query';
import ChartProps from '../models/ChartProps';
import { QueryContext } from './Query';
export interface PlainProps {
[key: string]: any;
@ -12,4 +11,4 @@ export type PreTransformProps = TransformFunction<ChartProps, ChartProps>;
export type TransformProps = TransformFunction<ChartProps>;
export type PostTransformProps = TransformFunction;
export type BuildQueryFunction<T extends ChartFormData> = (formData: T) => QueryContext;
export type BuildQueryFunction<T extends QueryFormData> = (formData: T) => QueryContext;

View File

@ -1,15 +1,12 @@
import fetchMock from 'fetch-mock';
import { SupersetClientClass, SupersetClient } from '@superset-ui/connection';
import { buildQueryContext, QueryFormData } from '@superset-ui/query';
import {
ChartClient,
getChartBuildQueryRegistry,
buildQueryContext,
ChartFormData,
getChartMetadataRegistry,
ChartMetadata,
} from '../../src';
import { SliceIdAndOrFormData } from '../../src/clients/ChartClient';
import { LOGIN_GLOB } from '../../../superset-ui-connection/test/fixtures/constants';
@ -103,7 +100,7 @@ describe('ChartClient', () => {
new ChartMetadata({ name: 'Word Cloud', thumbnail: '' }),
);
getChartBuildQueryRegistry().registerValue('word_cloud', (formData: ChartFormData) =>
getChartBuildQueryRegistry().registerValue('word_cloud', (formData: QueryFormData) =>
buildQueryContext(formData),
);
fetchMock.post('glob:*/api/v1/query/', {
@ -247,7 +244,7 @@ describe('ChartClient', () => {
new ChartMetadata({ name: 'Line', thumbnail: '.gif' }),
);
getChartBuildQueryRegistry().registerValue('line', (formData: ChartFormData) =>
getChartBuildQueryRegistry().registerValue('line', (formData: QueryFormData) =>
buildQueryContext(formData),
);

View File

@ -1,5 +1,5 @@
import React from 'react';
import { ChartMetadata, ChartPlugin, ChartFormData } from '../../src';
import { ChartMetadata, ChartPlugin, QueryFormData } from '../../src';
const DIMENSION_STYLE = {
fontSize: 36,
@ -52,7 +52,7 @@ export const ChartKeys = {
BUGGY: 'buggy-chart',
};
export class DiligentChartPlugin extends ChartPlugin<ChartFormData> {
export class DiligentChartPlugin extends ChartPlugin<QueryFormData> {
constructor() {
super({
metadata: new ChartMetadata({
@ -65,7 +65,7 @@ export class DiligentChartPlugin extends ChartPlugin<ChartFormData> {
}
}
export class LazyChartPlugin extends ChartPlugin<ChartFormData> {
export class LazyChartPlugin extends ChartPlugin<QueryFormData> {
constructor() {
super({
metadata: new ChartMetadata({
@ -80,7 +80,7 @@ export class LazyChartPlugin extends ChartPlugin<ChartFormData> {
}
}
export class SlowChartPlugin extends ChartPlugin<ChartFormData> {
export class SlowChartPlugin extends ChartPlugin<QueryFormData> {
constructor() {
super({
metadata: new ChartMetadata({
@ -98,7 +98,7 @@ export class SlowChartPlugin extends ChartPlugin<ChartFormData> {
}
}
export class BuggyChartPlugin extends ChartPlugin<ChartFormData> {
export class BuggyChartPlugin extends ChartPlugin<QueryFormData> {
constructor() {
super({
metadata: new ChartMetadata({

View File

@ -1,10 +1,8 @@
import React from 'react';
import { QueryFormData, DatasourceType } from '@superset-ui/query';
import {
ChartPlugin,
ChartMetadata,
ChartFormData,
DatasourceType,
ChartProps,
BuildQueryFunction,
TransformProps,
@ -61,7 +59,7 @@ describe('ChartPlugin', () => {
loadBuildQuery: () => buildQuery,
});
if (typeof plugin.loadBuildQuery === 'function') {
const fn = plugin.loadBuildQuery() as BuildQueryFunction<ChartFormData>;
const fn = plugin.loadBuildQuery() as BuildQueryFunction<QueryFormData>;
expect(fn(FORM_DATA).queries[0]).toEqual({ granularity: 'day' });
}
});
@ -73,7 +71,7 @@ describe('ChartPlugin', () => {
buildQuery,
});
if (typeof plugin.loadBuildQuery === 'function') {
const fn = plugin.loadBuildQuery() as BuildQueryFunction<ChartFormData>;
const fn = plugin.loadBuildQuery() as BuildQueryFunction<QueryFormData>;
expect(fn(FORM_DATA).queries[0]).toEqual({ granularity: 'day' });
}
});

View File

@ -0,0 +1,23 @@
## @superset-ui/query
[![Version](https://img.shields.io/npm/v/@superset-ui/query.svg?style=flat)](https://img.shields.io/npm/v/@superset-ui/query.svg?style=flat)
[![David (path)](https://img.shields.io/david/apache-superset/superset-ui.svg?path=packages%2Fsuperset-ui-query&style=flat-square)](https://david-dm.org/apache-superset/superset-ui?path=packages/superset-ui-query)
Description
#### Example usage
```js
import { xxx } from '@superset-ui/query';
```
#### API
`fn(args)`
- Do something
### Development
`@data-ui/build-config` is used to manage the build configuration for this package including babel
builds, jest testing, eslint, and prettier.

View File

@ -0,0 +1,26 @@
{
"name": "@superset-ui/query",
"version": "0.0.0",
"description": "Superset UI query",
"sideEffects": false,
"main": "lib/index.js",
"module": "esm/index.js",
"files": [
"esm",
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/apache-superset/superset-ui.git"
},
"keywords": ["superset"],
"author": "Superset",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/apache-superset/superset-ui/issues"
},
"homepage": "https://github.com/apache-superset/superset-ui#readme",
"publishConfig": {
"access": "public"
}
}

View File

@ -1,4 +1,4 @@
import { DatasourceType } from '../types/Datasource';
import { DatasourceType } from './types/Datasource';
export default class DatasourceKey {
readonly id: number;

View File

@ -1,12 +1,12 @@
import buildQueryObject from './buildQueryObject';
import DatasourceKey from './DatasourceKey';
import { ChartFormData } from '../types/ChartFormData';
import { QueryContext, QueryObject } from '../types/Query';
import { QueryFormData } from './types/QueryFormData';
import { QueryContext, QueryObject } from './types/Query';
const WRAP_IN_ARRAY = (baseQueryObject: QueryObject) => [baseQueryObject];
export default function buildQueryContext(
formData: ChartFormData,
formData: QueryFormData,
buildQuery: (baseQueryObject: QueryObject) => QueryObject[] = WRAP_IN_ARRAY,
): QueryContext {
return {

View File

@ -1,6 +1,6 @@
/* eslint-disable camelcase */
import { QueryObject } from '../types/Query';
import { ChartFormData, isSqlaFormData } from '../types/ChartFormData';
import { QueryObject } from './types/Query';
import { QueryFormData, isSqlaFormData } from './types/QueryFormData';
import convertMetric from './convertMetric';
import processFilters from './processFilters';
import processMetrics from './processMetrics';
@ -8,16 +8,18 @@ import processExtras from './processExtras';
export const DTTM_ALIAS = '__timestamp';
function processGranularity(formData: ChartFormData): string {
function processGranularity(formData: QueryFormData): string {
return isSqlaFormData(formData) ? formData.granularity_sqla : formData.granularity;
}
// Build the common segments of all query objects (e.g. the granularity field derived from
// either sql alchemy or druid). The segments specific to each viz type is constructed in the
// buildQuery method for each viz type (see `wordcloud/buildQuery.ts` for an example).
// Note the type of the formData argument passed in here is the type of the formData for a
// specific viz, which is a subtype of the generic formData shared among all viz types.
export default function buildQueryObject<T extends ChartFormData>(formData: T): QueryObject {
/**
* Build the common segments of all query objects (e.g. the granularity field derived from
* either sql alchemy or druid). The segments specific to each viz type is constructed in the
* buildQuery method for each viz type (see `wordcloud/buildQuery.ts` for an example).
* Note the type of the formData argument passed in here is the type of the formData for a
* specific viz, which is a subtype of the generic formData shared among all viz types.
*/
export default function buildQueryObject<T extends QueryFormData>(formData: T): QueryObject {
const {
time_range,
since,

View File

@ -1,5 +1,5 @@
import { SimpleAdhocFilter, isBinaryAdhocFilter, isUnaryAdhocFilter } from '../types/Filter';
import { QueryObjectFilterClause } from '../types/Query';
import { SimpleAdhocFilter, isBinaryAdhocFilter, isUnaryAdhocFilter } from './types/Filter';
import { QueryObjectFilterClause } from './types/Query';
export default function convertFilter(filter: SimpleAdhocFilter): QueryObjectFilterClause {
const { subject } = filter;

View File

@ -1,6 +1,6 @@
import { ChartFormDataMetric } from '../types/ChartFormData';
import { QueryObjectMetric } from '../types/Query';
import { AdhocMetric } from '../types/Metric';
import { QueryFormDataMetric } from './types/QueryFormData';
import { QueryObjectMetric } from './types/Query';
import { AdhocMetric } from './types/Metric';
export const LABEL_MAX_LENGTH = 43;
@ -17,7 +17,7 @@ function getDefaultLabel(metric: AdhocMetric) {
: `${label.substring(0, LABEL_MAX_LENGTH - 3)}...`;
}
export default function convertMetric(metric: ChartFormDataMetric): QueryObjectMetric {
export default function convertMetric(metric: QueryFormDataMetric): QueryObjectMetric {
let formattedMetric;
if (typeof metric === 'string') {
formattedMetric = {

View File

@ -0,0 +1,11 @@
export { default as buildQueryContext } from './buildQueryContext';
export { default as buildQueryObject } from './buildQueryObject';
export { default as convertFilter } from './convertFilter';
export { default as convertMetric } from './convertMetric';
export { default as DatasourceKey } from './DatasourceKey';
export * from './types/QueryFormData';
export * from './types/Column';
export * from './types/Datasource';
export * from './types/Metric';
export * from './types/Query';

View File

@ -1,8 +1,8 @@
/* eslint-disable camelcase */
import { ChartFormData, isDruidFormData } from '../types/ChartFormData';
import { QueryObjectExtras } from '../types/Query';
import { QueryFormData, isDruidFormData } from './types/QueryFormData';
import { QueryObjectExtras } from './types/Query';
export default function processExtras(formData: ChartFormData): QueryObjectExtras {
export default function processExtras(formData: QueryFormData): QueryObjectExtras {
const { where = '' } = formData;
if (isDruidFormData(formData)) {

View File

@ -1,10 +1,10 @@
import { ChartFormData } from '../types/ChartFormData';
import { QueryObjectFilterClause } from '../types/Query';
import { isSimpleAdhocFilter } from '../types/Filter';
import { QueryFormData } from './types/QueryFormData';
import { QueryObjectFilterClause } from './types/Query';
import { isSimpleAdhocFilter } from './types/Filter';
import convertFilter from './convertFilter';
/** Logic formerly in viz.py's process_query_filters */
export default function processFilters(formData: ChartFormData) {
export default function processFilters(formData: QueryFormData) {
// TODO: Implement
// utils.convert_legacy_filters_into_adhoc(self.form_data)

View File

@ -1,9 +1,9 @@
import { ChartFormData } from '../types/ChartFormData';
import { QueryObjectMetric } from '../types/Query';
import { MetricKey } from '../types/Metric';
import { QueryFormData } from './types/QueryFormData';
import { QueryObjectMetric } from './types/Query';
import { MetricKey } from './types/Metric';
import convertMetric from './convertMetric';
export default function processMetrics(formData: ChartFormData) {
export default function processMetrics(formData: QueryFormData) {
// Use Array to maintain insertion order
// for metrics that are order sensitive
const metrics: QueryObjectMetric[] = [];

View File

@ -2,18 +2,17 @@
/* eslint-disable import/prefer-default-export */
// FormData uses snake_cased keys.
import { MetricKey, AdhocMetric } from './Metric';
import { AnnotationLayerMetadata } from './Annotation';
import { TimeRange } from './Time';
import { AdhocFilter } from './Filter';
export type ChartFormDataMetric = string | AdhocMetric;
export type QueryFormDataMetric = string | AdhocMetric;
// Define mapped type separately to work around a limitation of TypeScript
// https://github.com/Microsoft/TypeScript/issues/13573
// The Metrics in formData is either a string or a proper metric. It will be
// unified into a proper Metric type during buildQuery (see `/query/Metrics.ts`).
export type ChartFormDataMetrics = Partial<
Record<MetricKey, ChartFormDataMetric | ChartFormDataMetric[]>
export type QueryFormDataMetrics = Partial<
Record<MetricKey, QueryFormDataMetric | QueryFormDataMetric[]>
>;
// Type signature for formData shared by all viz types
@ -46,11 +45,9 @@ export type BaseFormData = {
/** limit number of row in the results */
row_limit?: number;
/** The metric used to order timeseries for limiting */
timeseries_limit_metric?: ChartFormDataMetric;
annotation_layers?: AnnotationLayerMetadata[];
timeseries_limit_metric?: QueryFormDataMetric;
} & TimeRange &
ChartFormDataMetrics;
QueryFormDataMetrics;
// FormData is either sqla-based or druid-based
export type SqlaFormData = {
@ -65,16 +62,16 @@ export type DruidFormData = {
druid_time_origin?: string;
} & BaseFormData;
export type ChartFormData = SqlaFormData | DruidFormData;
export type QueryFormData = SqlaFormData | DruidFormData;
//---------------------------------------------------
// Type guards
//---------------------------------------------------
export function isDruidFormData(formData: ChartFormData): formData is DruidFormData {
export function isDruidFormData(formData: QueryFormData): formData is DruidFormData {
return 'granularity' in formData;
}
export function isSqlaFormData(formData: ChartFormData): formData is SqlaFormData {
export function isSqlaFormData(formData: QueryFormData): formData is SqlaFormData {
return 'granularity_sqla' in formData;
}

View File

@ -1,4 +1,4 @@
import { DatasourceKey } from '../../src';
import { DatasourceKey } from '../src';
describe('DatasourceKey', () => {
const tableKey = '5__table';

View File

@ -1,4 +1,4 @@
import { buildQueryContext } from '../../src';
import { buildQueryContext } from '../src';
describe('queryContextBuilder', () => {
it('should build datasource for table sources', () => {

View File

@ -1,5 +1,4 @@
import buildQueryObject from '../../src/query/buildQueryObject';
import { QueryObject } from '../../src';
import { buildQueryObject, QueryObject } from '../src';
describe('queryObjectBuilder', () => {
let query: QueryObject;

View File

@ -1,4 +1,4 @@
import convertFilter from '../../src/query/convertFilter';
import { convertFilter } from '../src';
describe('convertFilter', () => {
it('should handle unary filter', () => {

View File

@ -1,5 +1,5 @@
import { ColumnType } from '../../src/types/Column';
import convertMetric, { LABEL_MAX_LENGTH } from '../../src/query/convertMetric';
import { ColumnType, convertMetric } from '../src';
import { LABEL_MAX_LENGTH } from '../src/convertMetric';
describe('convertMetric', () => {
it('should handle string metric name', () => {

View File

@ -1,4 +1,4 @@
import processFilters from '../../src/query/processFilters';
import processFilters from '../src/processFilters';
describe('processFilters', () => {
it('should handle non-array adhoc_filters', () => {

View File

@ -1,5 +1,5 @@
import { ColumnType } from '../../src/types/Column';
import processMetrics from '../../src/query/processMetrics';
import { ColumnType } from '../src';
import processMetrics from '../src/processMetrics';
describe('processMetrics', () => {
const formData = {