You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by cc...@apache.org on 2018/11/16 23:51:56 UTC

[incubator-superset] branch master updated: [SIP-5] QueryBuilder in the client for granularity and groupby in word cloud (#6377)

This is an automated email from the ASF dual-hosted git repository.

ccwilliams pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 8b2cae0  [SIP-5] QueryBuilder in the client for granularity and groupby in word cloud (#6377)
8b2cae0 is described below

commit 8b2cae007d0b38217bd9f227c7079cca690fafa8
Author: Christine Chambers <ch...@gmail.com>
AuthorDate: Fri Nov 16 15:51:50 2018 -0800

    [SIP-5] QueryBuilder in the client for granularity and groupby in word cloud (#6377)
    
    * [SIP-5] QueryBuilder in the client for groupby field
    
    - Lay the structure of the QueryContext builder in the client
    - QueryContext builder composes different portions of the queryContext object (to be sent to server from the client) from the datasourceBuilder and the queryObjectBuilder.
    - The datasourceBuilder builds the datasource id and type by parsing them from the datasource field in formdata
    - The queryObjectBuilder currently only builds the groupby field. It will further compose the queryObject from sub query builders in future PRs.
    - Create a buildQuery method for WordCloud and override the groupby value with the value of series from formdata.
    
    * Addressing PR comments
    - Rename query builder files and their default exports
    - Move tests into spec/javascripts/superset-ui for easy mass migration to superset-uiin the future
    - Define viz specific formData and export formData for each viz type as intersection type of the generic formData and the viz specific one
    - Specify the type signature for sqla and druid based data sources
    
    * Addressing additional PR comments.
    - Introduce a Datasource class and leverage Typescript's declaration merging of the interface
    by the same name.
    - Let Typescript infer the return type of various builders instead of specifying them explicitly
    
    * Further tweaking the generic buildQueryContext method and viz speicific buildQuery methodes per PR comments.
    
    * git mv a renamed file.
    
    * addressing additional pr comments
---
 superset/assets/jest.config.js                     |  2 +-
 .../superset-ui/WordCloudBuildQuery.test.ts        | 15 +++++++++++
 .../superset-ui/buildQueryContext.test.ts          | 22 ++++++++++++++++
 .../superset-ui/buildQueryObject.test.ts           | 13 ++++++++++
 superset/assets/src/query/DatasourceKey.ts         | 29 ++++++++++++++++++++++
 superset/assets/src/query/FormData.ts              | 21 ++++++++++++++++
 superset/assets/src/query/buildQueryContext.ts     | 15 +++++++++++
 superset/assets/src/query/buildQueryObject.ts      | 18 ++++++++++++++
 superset/assets/src/query/index.ts                 |  3 +++
 .../src/visualizations/wordcloud/FormData.ts       | 11 ++++++++
 .../wordcloud/WordCloudChartPlugin.js              |  2 ++
 .../src/visualizations/wordcloud/buildQuery.ts     | 10 ++++++++
 12 files changed, 160 insertions(+), 1 deletion(-)

diff --git a/superset/assets/jest.config.js b/superset/assets/jest.config.js
index 64dcb6f..858f6f9 100644
--- a/superset/assets/jest.config.js
+++ b/superset/assets/jest.config.js
@@ -1,5 +1,5 @@
 module.exports = {
-  testRegex: '\\/spec\\/.*_spec\\.(j|t)sx?$',
+  testRegex: '\\/spec\\/.*(_spec|\\.test)\\.(j|t)sx?$',
   moduleNameMapper: {
     '\\.(css|less)$': '<rootDir>/spec/__mocks__/styleMock.js',
     '\\.(gif|ttf|eot|svg)$': '<rootDir>/spec/__mocks__/fileMock.js',
diff --git a/superset/assets/spec/javascripts/superset-ui/WordCloudBuildQuery.test.ts b/superset/assets/spec/javascripts/superset-ui/WordCloudBuildQuery.test.ts
new file mode 100644
index 0000000..a6edfbc
--- /dev/null
+++ b/superset/assets/spec/javascripts/superset-ui/WordCloudBuildQuery.test.ts
@@ -0,0 +1,15 @@
+import buildQuery from 'src/visualizations/wordcloud/buildQuery';
+
+describe('WordCloud buildQuery', () => {
+  const formData = {
+    datasource: '5__table',
+    granularity_sqla: 'ds',
+    series: 'foo',
+  };
+
+  it('should build groupby with series in form data', () => {
+    const queryContext = buildQuery(formData);
+    const [ query ] = queryContext.queries;
+    expect(query.groupby).toEqual(['foo']);
+  });
+});
diff --git a/superset/assets/spec/javascripts/superset-ui/buildQueryContext.test.ts b/superset/assets/spec/javascripts/superset-ui/buildQueryContext.test.ts
new file mode 100644
index 0000000..b6fd995
--- /dev/null
+++ b/superset/assets/spec/javascripts/superset-ui/buildQueryContext.test.ts
@@ -0,0 +1,22 @@
+import build from 'src/query/buildQueryContext';
+import * as queryObjectBuilder from 'src/query/buildQueryObject';
+
+describe('queryContextBuilder', () => {
+  it('should build datasource for table sources', () => {
+    const queryContext = build({ datasource: '5__table', granularity_sqla: 'ds'});
+    expect(queryContext.datasource.id).toBe(5);
+    expect(queryContext.datasource.type).toBe('table');
+  });
+
+  it('should build datasource for druid sources', () => {
+    const queryContext = build({ datasource: '5__druid', granularity: 'ds'});
+    expect(queryContext.datasource.id).toBe(5);
+    expect(queryContext.datasource.type).toBe('druid');
+  });
+
+  it('should call queryObjectBuilder to build queries', () => {
+    const buildQueryObjectSpy = jest.spyOn(queryObjectBuilder, 'default');
+    build({ datasource: '5__table', granularity_sqla: 'ds'});
+    expect(buildQueryObjectSpy).toHaveBeenCalledTimes(1);
+  });
+});
diff --git a/superset/assets/spec/javascripts/superset-ui/buildQueryObject.test.ts b/superset/assets/spec/javascripts/superset-ui/buildQueryObject.test.ts
new file mode 100644
index 0000000..ec111df
--- /dev/null
+++ b/superset/assets/spec/javascripts/superset-ui/buildQueryObject.test.ts
@@ -0,0 +1,13 @@
+import build from 'src/query/buildQueryObject';
+
+describe('queryObjectBuilder', () => {
+  it('should build granularity for sql alchemy datasources', () => {
+    const query = build({datasource: '5__table', granularity_sqla: 'ds'});
+    expect(query.granularity).toEqual('ds');
+  });
+
+  it('should build granularity for sql alchemy datasources', () => {
+    const query = build({datasource: '5__druid', granularity: 'ds'});
+    expect(query.granularity).toEqual('ds');
+  });
+});
diff --git a/superset/assets/src/query/DatasourceKey.ts b/superset/assets/src/query/DatasourceKey.ts
new file mode 100644
index 0000000..a13c121
--- /dev/null
+++ b/superset/assets/src/query/DatasourceKey.ts
@@ -0,0 +1,29 @@
+enum DatasourceType {
+  Table = 'table',
+  Druid = 'druid',
+}
+
+export default interface DatasourceKey {
+  id: number;
+  type: DatasourceType;
+}
+
+// Declaration merging with the interface above. No need to redeclare id and type.
+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;
+  }
+
+  public toString() {
+    return `${this.id}__${this.type}`;
+  }
+
+  public toObject() {
+    return {
+      id: this.id,
+      type: this.type,
+    };
+  }
+}
diff --git a/superset/assets/src/query/FormData.ts b/superset/assets/src/query/FormData.ts
new file mode 100644
index 0000000..daa7abf
--- /dev/null
+++ b/superset/assets/src/query/FormData.ts
@@ -0,0 +1,21 @@
+// Type signature and utility functions for formData shared by all viz types
+// It will be gradually filled out as we build out the query object
+interface BaseFormData {
+  datasource: string;
+}
+
+// FormData is either sqla-based or druid-based
+interface SqlaFormData extends BaseFormData {
+  granularity_sqla: string;
+}
+
+interface DruidFormData extends BaseFormData {
+  granularity: string;
+}
+
+type FormData = SqlaFormData | DruidFormData;
+export default FormData;
+
+export function getGranularity(formData: FormData): string {
+  return 'granularity_sqla' in formData ? formData.granularity_sqla : formData.granularity;
+}
diff --git a/superset/assets/src/query/buildQueryContext.ts b/superset/assets/src/query/buildQueryContext.ts
new file mode 100644
index 0000000..8ce0464
--- /dev/null
+++ b/superset/assets/src/query/buildQueryContext.ts
@@ -0,0 +1,15 @@
+import buildQueryObject, { QueryObject } from './buildQueryObject';
+import DatasourceKey from './DatasourceKey';
+import FormData from './FormData';
+
+const WRAP_IN_ARRAY = (baseQueryObject: QueryObject) => [baseQueryObject];
+
+// Note: let TypeScript infer the return type
+export default function buildQueryContext(
+  formData: FormData,
+  buildQuery: (baseQueryObject: QueryObject) => QueryObject[] = WRAP_IN_ARRAY) {
+  return {
+    datasource: new DatasourceKey(formData.datasource).toObject(),
+    queries: buildQuery(buildQueryObject(formData)),
+  };
+}
diff --git a/superset/assets/src/query/buildQueryObject.ts b/superset/assets/src/query/buildQueryObject.ts
new file mode 100644
index 0000000..578d9ae
--- /dev/null
+++ b/superset/assets/src/query/buildQueryObject.ts
@@ -0,0 +1,18 @@
+import FormData, { getGranularity } from './FormData';
+
+// TODO: fill out the rest of the query object
+export interface QueryObject {
+  granularity: string;
+  groupby?: string[];
+}
+
+// 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 FormData>(formData: T): QueryObject {
+  return {
+    granularity: getGranularity(formData),
+  };
+}
diff --git a/superset/assets/src/query/index.ts b/superset/assets/src/query/index.ts
new file mode 100644
index 0000000..cda7126
--- /dev/null
+++ b/superset/assets/src/query/index.ts
@@ -0,0 +1,3 @@
+// Public API of the query module
+export { default } from './buildQueryContext';
+export { default as FormData } from './FormData';
diff --git a/superset/assets/src/visualizations/wordcloud/FormData.ts b/superset/assets/src/visualizations/wordcloud/FormData.ts
new file mode 100644
index 0000000..55f8131
--- /dev/null
+++ b/superset/assets/src/visualizations/wordcloud/FormData.ts
@@ -0,0 +1,11 @@
+import { FormData as GenericFormData } from 'src/query';
+
+// FormData specific to the wordcloud viz
+interface WordCloudFormData {
+  series: string;
+}
+
+// FormData for wordcloud contains both common properties of all form data
+// and properties specific to wordcloud vizzes
+type FormData = GenericFormData & WordCloudFormData;
+export default FormData;
diff --git a/superset/assets/src/visualizations/wordcloud/WordCloudChartPlugin.js b/superset/assets/src/visualizations/wordcloud/WordCloudChartPlugin.js
index b828820..19b43c5 100644
--- a/superset/assets/src/visualizations/wordcloud/WordCloudChartPlugin.js
+++ b/superset/assets/src/visualizations/wordcloud/WordCloudChartPlugin.js
@@ -1,5 +1,6 @@
 import { t } from '@superset-ui/translation';
 import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
+import buildQuery from './buildQuery';
 import transformProps from './transformProps';
 import thumbnail from './images/thumbnail.png';
 
@@ -14,6 +15,7 @@ export default class WordCloudChartPlugin extends ChartPlugin {
   constructor() {
     super({
       metadata,
+      buildQuery,
       transformProps,
       loadChart: () => import('./ReactWordCloud.js'),
     });
diff --git a/superset/assets/src/visualizations/wordcloud/buildQuery.ts b/superset/assets/src/visualizations/wordcloud/buildQuery.ts
new file mode 100644
index 0000000..2aa0f2c
--- /dev/null
+++ b/superset/assets/src/visualizations/wordcloud/buildQuery.ts
@@ -0,0 +1,10 @@
+import buildQueryContext from 'src/query';
+import FormData from './FormData';
+
+export default function buildQuery(formData: FormData) {
+  // Set the single QueryObject's groupby field with series in formData
+  return buildQueryContext(formData, (baseQueryObject) => [{
+    ...baseQueryObject,
+    groupby: [formData.series],
+  }]);
+}