diff --git a/superset-frontend/temporary_superset_ui/superset-ui/README.md b/superset-frontend/temporary_superset_ui/superset-ui/README.md index 9edf8fc6a0b68..e5dd1141d2a69 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/README.md +++ b/superset-frontend/temporary_superset_ui/superset-ui/README.md @@ -11,6 +11,7 @@ applications that leverage a Superset backend :chart_with_upwards_trend: | Package | Version | |--|--| +| [@superset-ui/chart](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-chart) | [![Version](https://img.shields.io/npm/v/@superset-ui/chart.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/chart.svg?style=flat-square) | | [@superset-ui/color](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-color) | [![Version](https://img.shields.io/npm/v/@superset-ui/color.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/color.svg?style=flat-square) | | [@superset-ui/connection](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-connection) | [![Version](https://img.shields.io/npm/v/@superset-ui/connection.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/connection.svg?style=flat-square) | | [@superset-ui/core](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-core) | [![Version](https://img.shields.io/npm/v/@superset-ui/core.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/core.svg?style=flat-square) | diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/README.md b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/README.md new file mode 100644 index 0000000000000..2d78601083b5e --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/README.md @@ -0,0 +1,23 @@ +## @superset-ui/chart + +[![Version](https://img.shields.io/npm/v/@superset-ui/chart.svg?style=flat)](https://img.shields.io/npm/v/@superset-ui/chart.svg?style=flat) +[![David (path)](https://img.shields.io/david/apache-superset/superset-ui.svg?path=packages%2Fsuperset-ui-chart&style=flat-square)](https://david-dm.org/apache-superset/superset-ui?path=packages/superset-ui-chart) + +Description + +#### Example usage + +```js +import { xxx } from '@superset-ui/chart'; +``` + +#### 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. diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/package.json b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/package.json new file mode 100644 index 0000000000000..23cfc2a98af85 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/package.json @@ -0,0 +1,32 @@ +{ + "name": "@superset-ui/chart", + "version": "0.0.0", + "description": "SuperChart and related modules", + "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" + }, + "dependencies": { + "@superset-ui/core": "^0.3.0", + "reselect": "^4.0.0" + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/index.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/index.js new file mode 100644 index 0000000000000..3e8dfc65984f8 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/index.js @@ -0,0 +1,11 @@ +export { default as ChartMetadata } from './models/ChartMetadata'; +export { default as ChartPlugin } from './models/ChartPlugin'; +export { default as ChartProps } from './models/ChartProps'; +export { + default as getChartBuildQueryRegistry, +} from './registries/ChartBuildQueryRegistrySingleton'; +export { default as getChartComponentRegistry } from './registries/ChartComponentRegistrySingleton'; +export { default as getChartMetadataRegistry } from './registries/ChartMetadataRegistrySingleton'; +export { + default as getChartTransformPropsRegistry, +} from './registries/ChartTransformPropsRegistrySingleton'; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/models/ChartMetadata.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/models/ChartMetadata.js new file mode 100644 index 0000000000000..1889bc98df57b --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/models/ChartMetadata.js @@ -0,0 +1,28 @@ +export default class ChartMetadata { + constructor({ + name, + credits = [], + description = '', + show = true, + canBeAnnotationTypes = [], + supportedAnnotationTypes = [], + thumbnail, + }) { + this.name = name; + this.credits = credits; + this.description = description; + this.show = show; + this.canBeAnnotationTypesLookup = canBeAnnotationTypes.reduce((prev, type) => { + const lookup = prev; + lookup[type] = true; + + return lookup; + }, {}); + this.supportedAnnotationTypes = supportedAnnotationTypes; + this.thumbnail = thumbnail; + } + + canBeAnnotationType(type) { + return this.canBeAnnotationTypesLookup[type] || false; + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/models/ChartPlugin.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/models/ChartPlugin.js new file mode 100644 index 0000000000000..1788b69da130d --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/models/ChartPlugin.js @@ -0,0 +1,51 @@ +import { isRequired, Plugin } from '@superset-ui/core'; +import getChartMetadataRegistry from '../registries/ChartMetadataRegistrySingleton'; +import getChartBuildQueryRegistry from '../registries/ChartBuildQueryRegistrySingleton'; +import getChartComponentRegistry from '../registries/ChartComponentRegistrySingleton'; +import getChartTransformPropsRegistry from '../registries/ChartTransformPropsRegistrySingleton'; + +const IDENTITY = x => x; + +export default class ChartPlugin extends Plugin { + constructor({ + metadata = isRequired('metadata'), + + // use buildQuery for immediate value + buildQuery = IDENTITY, + // use loadBuildQuery for dynamic import (lazy-loading) + loadBuildQuery, + + // use transformProps for immediate value + transformProps = IDENTITY, + // use loadTransformProps for dynamic import (lazy-loading) + loadTransformProps, + + // use Chart for immediate value + Chart, + // use loadChart for dynamic import (lazy-loading) + loadChart, + } = {}) { + super(); + this.metadata = metadata; + this.loadBuildQuery = loadBuildQuery || (() => buildQuery); + this.loadTransformProps = loadTransformProps || (() => transformProps); + + if (loadChart) { + this.loadChart = loadChart; + } else if (Chart) { + this.loadChart = () => Chart; + } else { + throw new Error('Chart or loadChart is required'); + } + } + + 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); + + return this; + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/models/ChartProps.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/models/ChartProps.js new file mode 100644 index 0000000000000..cdb78a80d74b6 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/models/ChartProps.js @@ -0,0 +1,74 @@ +import { createSelector } from 'reselect'; +import { convertKeysToCamelCase } from '@superset-ui/core'; + +export default class ChartProps { + constructor({ + annotationData, + datasource, + filters, + formData, + height, + onAddFilter, + onError, + payload, + setControlValue, + setTooltip, + width, + }) { + this.width = width; + this.height = height; + this.annotationData = annotationData; + this.datasource = convertKeysToCamelCase(datasource); + this.rawDatasource = datasource; + this.filters = filters; + this.formData = convertKeysToCamelCase(formData); + this.rawFormData = formData; + this.onAddFilter = onAddFilter; + this.onError = onError; + this.payload = payload; + this.setControlValue = setControlValue; + this.setTooltip = setTooltip; + } +} + +ChartProps.createSelector = function create() { + return createSelector( + input => input.annotationData, + input => input.datasource, + input => input.filters, + input => input.formData, + input => input.height, + input => input.onAddFilter, + input => input.onError, + input => input.payload, + input => input.setControlValue, + input => input.setTooltip, + input => input.width, + ( + annotationData, + datasource, + filters, + formData, + height, + onAddFilter, + onError, + payload, + setControlValue, + setTooltip, + width, + ) => + new ChartProps({ + annotationData, + datasource, + filters, + formData, + height, + onAddFilter, + onError, + payload, + setControlValue, + setTooltip, + width, + }), + ); +}; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/registries/ChartBuildQueryRegistrySingleton.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/registries/ChartBuildQueryRegistrySingleton.js new file mode 100644 index 0000000000000..eed835cce6bcd --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/registries/ChartBuildQueryRegistrySingleton.js @@ -0,0 +1,11 @@ +import { Registry, makeSingleton } from '@superset-ui/core'; + +class ChartBuildQueryRegistry extends Registry { + constructor() { + super('ChartBuildQuery'); + } +} + +const getInstance = makeSingleton(ChartBuildQueryRegistry); + +export default getInstance; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/registries/ChartComponentRegistrySingleton.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/registries/ChartComponentRegistrySingleton.js new file mode 100644 index 0000000000000..aea7630bf7b64 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/registries/ChartComponentRegistrySingleton.js @@ -0,0 +1,11 @@ +import { Registry, makeSingleton } from '@superset-ui/core'; + +class ChartComponentRegistry extends Registry { + constructor() { + super('ChartComponent'); + } +} + +const getInstance = makeSingleton(ChartComponentRegistry); + +export default getInstance; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/registries/ChartMetadataRegistrySingleton.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/registries/ChartMetadataRegistrySingleton.js new file mode 100644 index 0000000000000..0f9c7695389c0 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/registries/ChartMetadataRegistrySingleton.js @@ -0,0 +1,11 @@ +import { Registry, makeSingleton } from '@superset-ui/core'; + +class ChartMetadataRegistry extends Registry { + constructor() { + super('ChartMetadata'); + } +} + +const getInstance = makeSingleton(ChartMetadataRegistry); + +export default getInstance; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/registries/ChartTransformPropsRegistrySingleton.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/registries/ChartTransformPropsRegistrySingleton.js new file mode 100644 index 0000000000000..df724ef7b5636 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/src/registries/ChartTransformPropsRegistrySingleton.js @@ -0,0 +1,11 @@ +import { Registry, makeSingleton } from '@superset-ui/core'; + +class ChartTransformPropsRegistry extends Registry { + constructor() { + super('ChartTransformProps'); + } +} + +const getInstance = makeSingleton(ChartTransformPropsRegistry); + +export default getInstance; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/test/index.test.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/test/index.test.js new file mode 100644 index 0000000000000..0ac49b42abcb1 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/test/index.test.js @@ -0,0 +1,23 @@ +import { + ChartMetadata, + ChartPlugin, + ChartProps, + getChartBuildQueryRegistry, + getChartComponentRegistry, + getChartMetadataRegistry, + getChartTransformPropsRegistry, +} from '../src/index'; + +describe('index', () => { + it('exports modules', () => { + [ + ChartMetadata, + ChartPlugin, + ChartProps, + getChartBuildQueryRegistry, + getChartComponentRegistry, + getChartMetadataRegistry, + getChartTransformPropsRegistry, + ].forEach(x => expect(x).toBeDefined()); + }); +}); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/test/models/ChartMetadata.test.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/test/models/ChartMetadata.test.js new file mode 100644 index 0000000000000..1be0b3d55051e --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/test/models/ChartMetadata.test.js @@ -0,0 +1,33 @@ +import ChartMetadata from '../../src/models/ChartMetadata'; + +describe('ChartMetadata', () => { + it('exists', () => { + expect(ChartMetadata).toBeDefined(); + }); + describe('new ChartMetadata({})', () => { + it('creates new metadata instance', () => { + const metadata = new ChartMetadata({ + name: 'test chart', + credits: [], + description: 'some kind of chart', + thumbnail: 'test.png', + }); + expect(metadata).toBeInstanceOf(ChartMetadata); + }); + }); + describe('.canBeAnnotationType(type)', () => { + const metadata = new ChartMetadata({ + name: 'test chart', + credits: [], + description: 'some kind of chart', + canBeAnnotationTypes: ['event'], + thumbnail: 'test.png', + }); + it('returns true if can', () => { + expect(metadata.canBeAnnotationType('event')).toBeTruthy(); + }); + it('returns false otherwise', () => { + expect(metadata.canBeAnnotationType('invalid-type')).toBeFalsy(); + }); + }); +}); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/test/models/ChartPlugin.test.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/test/models/ChartPlugin.test.js new file mode 100644 index 0000000000000..a1bc166563fbd --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/test/models/ChartPlugin.test.js @@ -0,0 +1,109 @@ +import ChartPlugin from '../../src/models/ChartPlugin'; +import ChartMetadata from '../../src/models/ChartMetadata'; + +describe('ChartPlugin', () => { + const metadata = new ChartMetadata({}); + + it('exists', () => { + expect(ChartPlugin).toBeDefined(); + }); + + describe('new ChartPlugin()', () => { + it('creates a new plugin', () => { + const plugin = new ChartPlugin({ + metadata, + Chart() {}, + }); + expect(plugin).toBeInstanceOf(ChartPlugin); + }); + it('throws an error if metadata is not specified', () => { + expect(() => new ChartPlugin()).toThrowError(Error); + }); + describe('buildQuery', () => { + const FORM_DATA = { field: 1 }; + it('defaults to identity function', () => { + const plugin = new ChartPlugin({ + metadata, + Chart: 'test', + }); + expect(plugin.loadBuildQuery()(FORM_DATA)).toBe(FORM_DATA); + }); + it('uses loadBuildQuery field if specified', () => { + const plugin = new ChartPlugin({ + metadata, + Chart: 'test', + loadBuildQuery: () => () => ({ field2: 2 }), + }); + expect(plugin.loadBuildQuery()(FORM_DATA)).toEqual({ field2: 2 }); + }); + it('uses buildQuery field if specified', () => { + const plugin = new ChartPlugin({ + metadata, + Chart: 'test', + buildQuery: () => ({ field2: 2 }), + }); + expect(plugin.loadBuildQuery()(FORM_DATA)).toEqual({ field2: 2 }); + }); + }); + describe('Chart', () => { + it('uses loadChart if specified', () => { + const loadChart = () => 'test'; + const plugin = new ChartPlugin({ + metadata, + loadChart, + }); + expect(plugin.loadChart).toBe(loadChart); + }); + it('uses Chart field if specified', () => { + const plugin = new ChartPlugin({ + metadata, + Chart: 'test', + }); + expect(plugin.loadChart()).toEqual('test'); + }); + it('throws an error if none of Chart or loadChart is specified', () => { + expect(() => new ChartPlugin({ metadata })).toThrowError(Error); + }); + }); + describe('transformProps', () => { + const PROPS = { field: 1 }; + it('defaults to identity function', () => { + const plugin = new ChartPlugin({ + metadata, + Chart: 'test', + }); + expect(plugin.loadTransformProps()(PROPS)).toBe(PROPS); + }); + it('uses loadTransformProps field if specified', () => { + const plugin = new ChartPlugin({ + metadata, + Chart: 'test', + loadTransformProps: () => () => ({ field2: 2 }), + }); + expect(plugin.loadTransformProps()(PROPS)).toEqual({ field2: 2 }); + }); + it('uses transformProps field if specified', () => { + const plugin = new ChartPlugin({ + metadata, + Chart: 'test', + transformProps: () => ({ field2: 2 }), + }); + expect(plugin.loadTransformProps()(PROPS)).toEqual({ field2: 2 }); + }); + }); + }); + + describe('.register(key)', () => { + const plugin = new ChartPlugin({ + metadata, + Chart() {}, + }); + it('throws an error if key is not provided', () => { + expect(() => plugin.register()).toThrowError(Error); + expect(() => plugin.configure({ key: 'abc' }).register()).not.toThrowError(Error); + }); + it('returns itself', () => { + expect(plugin.configure({ key: 'abc' }).register()).toBe(plugin); + }); + }); +}); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/test/models/ChartProps.test.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/test/models/ChartProps.test.js new file mode 100644 index 0000000000000..308ed747f1120 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-chart/test/models/ChartProps.test.js @@ -0,0 +1,64 @@ +import ChartProps from '../../src/models/ChartProps'; + +const RAW_FORM_DATA = { + some_field: 1, +}; + +const RAW_DATASOURCE = { + another_field: 2, +}; + +describe('ChartProps', () => { + it('exists', () => { + expect(ChartProps).toBeDefined(); + }); + describe('new ChartProps({})', () => { + it('returns new instance', () => { + const props = new ChartProps({}); + expect(props).toBeInstanceOf(ChartProps); + }); + it('processes formData and datasource to convert field names to camelCase', () => { + const props = new ChartProps({ + formData: RAW_FORM_DATA, + datasource: RAW_DATASOURCE, + }); + expect(props.formData.someField).toEqual(1); + expect(props.datasource.anotherField).toEqual(2); + expect(props.rawFormData).toEqual(RAW_FORM_DATA); + expect(props.rawDatasource).toEqual(RAW_DATASOURCE); + }); + }); + describe('ChartProps.createSelector()', () => { + const selector = ChartProps.createSelector(); + it('returns a selector function', () => { + expect(selector).toBeInstanceOf(Function); + }); + it('selector returns previous chartProps if all input fields do not change', () => { + const props1 = selector({ + formData: RAW_FORM_DATA, + datasource: RAW_DATASOURCE, + }); + const props2 = selector({ + formData: RAW_FORM_DATA, + datasource: RAW_DATASOURCE, + }); + expect(props1).toBe(props2); + }); + it('selector returns a new chartProps if some input fields change', () => { + const props1 = selector({ + formData: RAW_FORM_DATA, + datasource: RAW_DATASOURCE, + }); + const props2 = selector({ + formData: { new_field: 3 }, + datasource: RAW_DATASOURCE, + }); + const props3 = selector({ + formData: RAW_FORM_DATA, + datasource: RAW_DATASOURCE, + }); + expect(props1).not.toBe(props2); + expect(props1).not.toBe(props3); + }); + }); +});