diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/README.md b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/README.md new file mode 100644 index 0000000000000..5006542836a14 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/README.md @@ -0,0 +1,23 @@ +## @superset-ui/encodeable + +[![Version](https://img.shields.io/npm/v/@superset-ui/encodeable.svg?style=flat)](https://img.shields.io/npm/v/@superset-ui/encodeable.svg?style=flat) +[![David (path)](https://img.shields.io/david/apache-superset/superset-ui.svg?path=packages%2Fsuperset-ui-encodeable&style=flat-square)](https://david-dm.org/apache-superset/superset-ui?path=packages/superset-ui-encodeable) + +Description + +#### Example usage + +```js +import { xxx } from '@superset-ui/encodeable'; +``` + +#### 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-encodeable/package.json b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/package.json new file mode 100644 index 0000000000000..8e3e226b432d2 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/package.json @@ -0,0 +1,35 @@ +{ + "name": "@superset-ui/encodeable", + "version": "0.0.0", + "description": "Superset UI encodeable", + "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" + }, + "private": true, + "dependencies": { + "vega": "^5.4.0", + "vega-lite": "^3.4.0" + }, + "peerDependencies": { + "@superset-ui/time-format": "^0.11.14", + "@superset-ui/number-format": "^0.11.14" + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/index.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/index.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/typeGuards/Base.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/typeGuards/Base.ts new file mode 100644 index 0000000000000..c2633141aab1d --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/typeGuards/Base.ts @@ -0,0 +1,11 @@ +export function isArray(maybeArray: T | T[]): maybeArray is T[] { + return Array.isArray(maybeArray); +} + +export function isNotArray(maybeArray: T | T[]): maybeArray is T { + return !Array.isArray(maybeArray); +} + +export function isDefined(value: any): value is T { + return typeof value !== 'undefined' && value !== null; +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/typeGuards/ChannelDef.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/typeGuards/ChannelDef.ts new file mode 100644 index 0000000000000..b485e63032350 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/typeGuards/ChannelDef.ts @@ -0,0 +1,45 @@ +import { Value, ValueDef } from '../types/VegaLite'; +import { + ChannelDef, + NonValueDef, + FieldDef, + TypedFieldDef, + PositionFieldDef, + ScaleFieldDef, +} from '../types/ChannelDef'; + +export function isValueDef( + channelDef: ChannelDef, +): channelDef is ValueDef { + return channelDef && 'value' in channelDef; +} + +export function isNonValueDef( + channelDef: ChannelDef, +): channelDef is NonValueDef { + return channelDef && !('value' in channelDef); +} + +export function isFieldDef( + channelDef: ChannelDef, +): channelDef is FieldDef { + return channelDef && 'field' in channelDef && !!channelDef.field; +} + +export function isTypedFieldDef( + channelDef: ChannelDef, +): channelDef is TypedFieldDef { + return isFieldDef(channelDef) && 'type' in channelDef && !!channelDef.type; +} + +export function isScaleFieldDef( + channelDef: ChannelDef, +): channelDef is ScaleFieldDef { + return channelDef && 'scale' in channelDef; +} + +export function isPositionFieldDef( + channelDef: ChannelDef, +): channelDef is PositionFieldDef { + return channelDef && 'axis' in channelDef; +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Axis.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Axis.ts new file mode 100644 index 0000000000000..460de9016c7a8 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Axis.ts @@ -0,0 +1,65 @@ +/** See https://vega.github.io/vega-lite/docs/axis.html */ + +import { DateTime } from './VegaLite'; + +/** Axis orientation */ +export type AxisOrient = 'top' | 'bottom' | 'left' | 'right'; + +/** Strategy for handling label overlap */ +export type LabelOverlapStrategy = 'auto' | 'flat' | 'rotate'; + +export interface CoreAxis { + /** Tick label format */ + format?: string; + /** Angle to rotate the tick labels */ + labelAngle: number; + /** + * Indicates if the first and last axis labels should be aligned flush with the scale range. + * Flush alignment for a horizontal axis will left-align the first label and right-align the last label. + * For vertical axes, bottom and top text baselines are applied instead. + * If this property is a number, it also indicates the number of pixels by which to offset the first and last labels; + * for example, a value of 2 will flush-align the first and last labels + * and also push them 2 pixels outward from the center of the axis. + * The additional adjustment can sometimes help the labels better visually group with corresponding axis ticks. */ + labelFlush?: boolean | number; + /** Strategy for handling label overlap */ + labelOverlap: LabelOverlapStrategy; + /** The padding, in pixels, between axis and text labels. */ + labelPadding: number; + /** Axis orientation */ + orient: AxisOrient; + /** Estimated number of desired ticks */ + tickCount: number; + /** Tick length */ + tickSize?: number; + /** Axis title */ + title?: string | boolean; + /** Explicitly set the visible axis tick values. */ + values?: string[] | number[] | boolean[] | DateTime[]; +} + +export type Axis = Partial; + +export interface XAxis extends Axis { + orient?: 'top' | 'bottom'; + labelAngle?: number; + labelOverlap?: LabelOverlapStrategy; +} + +export interface WithXAxis { + axis?: XAxis | boolean; +} + +export interface YAxis extends Axis { + orient?: 'left' | 'right'; + labelAngle?: 0; + labelOverlap?: 'auto' | 'flat'; +} + +export interface WithYAxis { + axis?: YAxis; +} + +export interface WithAxis { + axis?: XAxis | YAxis; +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Base.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Base.ts new file mode 100644 index 0000000000000..9f45402e08f92 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Base.ts @@ -0,0 +1,5 @@ +/** Extract generic type from array */ +export type Unarray = T extends Array ? U : T; + +/** T or an array of T */ +export type MayBeArray = T | T[]; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Channel.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Channel.ts new file mode 100644 index 0000000000000..2473641fe6deb --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Channel.ts @@ -0,0 +1,29 @@ +import { XFieldDef, YFieldDef, MarkPropChannelDef, TextChannelDef } from './ChannelDef'; +import { Value } from './VegaLite'; + +/** Possible input for a channel */ +export type ChannelInput = number | string | boolean | null | Date | undefined; + +/** + * Define all channel types and mapping to channel definition grammar + */ +export interface ChannelTypeToDefMap { + /** position on x-axis */ + X: XFieldDef; + /** position on y-axis */ + Y: YFieldDef; + /** position on x-axis but as a range, e.g., bar chart or heat map */ + XBand: XFieldDef; + /** position on y-axis but as a range, e.g., bar chart or heat map */ + YBand: YFieldDef; + /** numeric attributes of the mark, e.g., size, opacity */ + Numeric: MarkPropChannelDef; + /** categorical attributes of the mark, e.g., color, visibility, shape */ + Category: MarkPropChannelDef; + /** color of the mark */ + Color: MarkPropChannelDef; + /** plain text, e.g., tooltip, key */ + Text: TextChannelDef; +} + +export type ChannelType = keyof ChannelTypeToDefMap; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/ChannelDef.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/ChannelDef.ts new file mode 100644 index 0000000000000..601bf691c8b1d --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/ChannelDef.ts @@ -0,0 +1,56 @@ +import { TimeFormatter } from '@superset-ui/time-format'; +import { NumberFormatter } from '@superset-ui/number-format'; +import { ValueDef, Value, Type } from './VegaLite'; +import { WithScale } from './Scale'; +import { WithXAxis, WithYAxis, WithAxis } from './Axis'; +import { WithLegend } from './Legend'; + +export type Formatter = NumberFormatter | TimeFormatter | ((d: any) => string); + +export interface FieldDef { + field: string; + format?: string; + title?: string; +} + +export interface TypedFieldDef extends FieldDef { + type: Type; +} + +export type TextFieldDef = FieldDef; + +export type ScaleFieldDef = TypedFieldDef & WithScale; + +export type MarkPropFieldDef = ScaleFieldDef & WithLegend; + +// PositionFieldDef is { field: 'fieldName', scale: xxx, axis: xxx } + +type PositionFieldDefBase = ScaleFieldDef; + +export type XFieldDef = PositionFieldDefBase & WithXAxis; + +export type YFieldDef = PositionFieldDefBase & WithYAxis; + +export type PositionFieldDef = ScaleFieldDef & WithAxis; + +export type MarkPropChannelDef = + | MarkPropFieldDef + | ValueDef; + +export type TextChannelDef = TextFieldDef | ValueDef; + +export type ChannelDef = + | ValueDef + | XFieldDef + | YFieldDef + | MarkPropFieldDef + | TextFieldDef; + +/** Channel definitions that are not constant value */ +export type NonValueDef = Exclude< + ChannelDef, + ValueDef +>; + +/** Pattern for extracting output type from channel definition */ +export type ExtractChannelOutput = Def extends ChannelDef ? Output : never; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Data.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Data.ts new file mode 100644 index 0000000000000..0ce806c49dafa --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Data.ts @@ -0,0 +1,5 @@ +export type PlainObject = { + [key in Key]: Value; +}; + +export type Dataset = Partial>[]; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Legend.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Legend.ts new file mode 100644 index 0000000000000..407a8b26e0aa1 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Legend.ts @@ -0,0 +1,5 @@ +export type Legend = boolean | null; + +export interface WithLegend { + legend?: Legend; +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Scale.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Scale.ts new file mode 100644 index 0000000000000..324e6b5c6c518 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/Scale.ts @@ -0,0 +1,19 @@ +import { Value, DateTime, ScaleType, SchemeParams } from './VegaLite'; + +export interface Scale { + type?: ScaleType; + domain?: number[] | string[] | boolean[] | DateTime[]; + paddingInner?: number; + paddingOuter?: number; + range?: Output[]; + clamp?: boolean; + nice?: boolean; + /** color scheme name */ + scheme?: string | SchemeParams; + /** vega-lite does not have this */ + namespace?: string; +} + +export interface WithScale { + scale?: Scale; +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/VegaLite.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/VegaLite.ts new file mode 100644 index 0000000000000..4eaf4500d8011 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/types/VegaLite.ts @@ -0,0 +1,6 @@ +// Types directly imported from vega-lite + +export { ValueDef, Value } from 'vega-lite/build/src/channeldef'; +export { DateTime } from 'vega-lite/build/src/datetime'; +export { SchemeParams, ScaleType } from 'vega-lite/build/src/scale'; +export { Type } from 'vega-lite/build/src/type'; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/utils/identity.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/utils/identity.ts new file mode 100644 index 0000000000000..5538f47174439 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/utils/identity.ts @@ -0,0 +1,3 @@ +export default function identity(x: T) { + return x; +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/utils/isDisabled.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/utils/isDisabled.ts new file mode 100644 index 0000000000000..d5b87caf8f374 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/utils/isDisabled.ts @@ -0,0 +1,11 @@ +export default function isDisabled( + config: + | { + [key: string]: any; + } + | boolean + | null + | undefined, +) { + return config === false || config === null; +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/utils/isEnabled.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/utils/isEnabled.ts new file mode 100644 index 0000000000000..0397ed22a5e97 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/src/utils/isEnabled.ts @@ -0,0 +1,13 @@ +import isDisabled from './isDisabled'; + +export default function isEnabled( + config: + | { + [key: string]: any; + } + | boolean + | null + | undefined, +) { + return !isDisabled(config); +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/test/typeGuards/Base.test.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/test/typeGuards/Base.test.ts new file mode 100644 index 0000000000000..581ea3c1d66a1 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/test/typeGuards/Base.test.ts @@ -0,0 +1,40 @@ +import { isDefined, isArray, isNotArray } from '../../src/typeGuards/Base'; + +describe('type guards: Base', () => { + describe('isArray(maybeArray)', () => { + it('returns true and converts to type T[] if is array', () => { + const x: string | string[] = ['abc']; + if (isArray(x)) { + // x is now known to be an array + expect(x[0]).toEqual('abc'); + } + }); + it('returns false if not', () => { + expect(isArray('abc')).toBeFalsy(); + }); + }); + describe('isNotArray(maybeArray)', () => { + it('returns true and converts to type T if not array', () => { + const x: string | string[] = 'abc'; + if (isNotArray(x)) { + // x is now known to be a string + expect(x.startsWith('a')).toBeTruthy(); + } + }); + it('returns false if is array', () => { + expect(isNotArray(['def'])).toBeFalsy(); + }); + }); + describe('isDefined(value)', () => { + it('returns true and converts to type T if value is defined', () => { + const x: any = 'abc'; + if (isDefined(x)) { + expect(x.startsWith('a')).toBeTruthy(); + } + }); + it('returns false if not defined', () => { + expect(isDefined(null)).toBeFalsy(); + expect(isDefined(undefined)).toBeFalsy(); + }); + }); +}); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/test/typeGuards/ChannelDef.test.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/test/typeGuards/ChannelDef.test.ts new file mode 100644 index 0000000000000..10f78be8e7488 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/test/typeGuards/ChannelDef.test.ts @@ -0,0 +1,76 @@ +import { + isValueDef, + isNonValueDef, + isFieldDef, + isTypedFieldDef, + isScaleFieldDef, + isPositionFieldDef, +} from '../../src/typeGuards/ChannelDef'; + +describe('type guards: ChannelDef', () => { + describe('isValueDef()', () => { + it('returns true if is ValueDef', () => { + expect(isValueDef({ value: 'red' })).toBeTruthy(); + }); + it('return false otherwise', () => { + expect(isValueDef({ field: 'horsepower' })).toBeFalsy(); + }); + }); + describe('isNonValueDef', () => { + it('returns true if it is not a ValueDef', () => { + expect(isNonValueDef({ field: 'horsepower' })).toBeTruthy(); + }); + it('returns false otherwise', () => { + expect(isNonValueDef({ value: 'red' })).toBeFalsy(); + }); + }); + describe('isFieldDef', () => { + it('returns true if is FieldDef', () => { + expect(isFieldDef({ field: 'horsepower' })).toBeTruthy(); + }); + it('return false otherwise', () => { + expect(isFieldDef({ value: 'red' })).toBeFalsy(); + }); + }); + describe('isTypedFieldDef', () => { + it('returns true if is TypedFieldDef', () => { + expect(isTypedFieldDef({ type: 'quantitative', field: 'horsepower' })).toBeTruthy(); + }); + it('return false otherwise', () => { + expect(isTypedFieldDef({ value: 'red' })).toBeFalsy(); + expect(isTypedFieldDef({ field: 'make' })).toBeFalsy(); + }); + }); + describe('isScaleFieldDef', () => { + it('returns true if is ScaleFieldDef', () => { + expect( + isScaleFieldDef({ type: 'nominal', field: 'horsepower', scale: { type: 'linear' } }), + ).toBeTruthy(); + }); + it('return false otherwise', () => { + expect(isScaleFieldDef({ value: 'red' })).toBeFalsy(); + expect(isScaleFieldDef({ field: 'make' })).toBeFalsy(); + expect(isScaleFieldDef({ type: 'quantitative', field: 'make' })).toBeFalsy(); + }); + }); + describe('isPositionFieldDef', () => { + it('returns true if is ScaleFieldDef', () => { + expect( + isPositionFieldDef({ + type: 'nominal', + field: 'horsepower', + scale: { type: 'linear' }, + axis: { orient: 'bottom' }, + }), + ).toBeTruthy(); + }); + it('return false otherwise', () => { + expect(isPositionFieldDef({ value: 'red' })).toBeFalsy(); + expect(isPositionFieldDef({ field: 'make' })).toBeFalsy(); + expect(isPositionFieldDef({ type: 'quantitative', field: 'make' })).toBeFalsy(); + expect( + isPositionFieldDef({ type: 'quantitative', field: 'make', scale: { type: 'quantile' } }), + ).toBeFalsy(); + }); + }); +}); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/test/utils/identity.test.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/test/utils/identity.test.ts new file mode 100644 index 0000000000000..878e462f99a87 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/test/utils/identity.test.ts @@ -0,0 +1,9 @@ +import identity from '../../src/utils/identity'; + +describe('identity(value)', () => { + it('returns value', () => { + ['a', 1, null, undefined, { b: 2 }, ['d']].forEach(x => { + expect(identity(x)).toBe(x); + }); + }); +}); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/test/utils/isDisabled.test.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/test/utils/isDisabled.test.ts new file mode 100644 index 0000000000000..f0f552b54ae42 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/test/utils/isDisabled.test.ts @@ -0,0 +1,13 @@ +import isDisabled from '../../src/utils/isDisabled'; + +describe('isDisabled(value)', () => { + it('returns true when null or false', () => { + expect(isDisabled(false)).toBeTruthy(); + expect(isDisabled(null)).toBeTruthy(); + }); + it('returns false otherwise', () => { + expect(isDisabled(true)).toBeFalsy(); + expect(isDisabled(undefined)).toBeFalsy(); + expect(isDisabled({})).toBeFalsy(); + }); +}); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/test/utils/isEnabled.test.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/test/utils/isEnabled.test.ts new file mode 100644 index 0000000000000..8a4aeda613b60 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-encodeable/test/utils/isEnabled.test.ts @@ -0,0 +1,13 @@ +import isEnabled from '../../src/utils/isEnabled'; + +describe('isEnabled(value)', () => { + it('returns false when null or false', () => { + expect(isEnabled(false)).toBeFalsy(); + expect(isEnabled(null)).toBeFalsy(); + }); + it('returns true otherwise', () => { + expect(isEnabled(true)).toBeTruthy(); + expect(isEnabled(undefined)).toBeTruthy(); + expect(isEnabled({})).toBeTruthy(); + }); +});