From b870159f37b7832ed1e95fb0acafbbbee87e2e66 Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Thu, 29 Nov 2018 13:01:57 -0800 Subject: [PATCH] Add @superset-ui/time-format (#38) feat: Add @superset-ui/time-format * update unit test (+6 squashed commits) Squashed commits: [5fae1ed] fix lint [7672544] support local and utc [c33fae7] update unit tests [97fdc0d] fix all unit tests [88e8029] add formatter code [a33c76c] initialize * update readme * add formatDate and formatDateVerbose equivalent * add unit test * extract constants * get to 100 coverage * update readme * Update preview test * explicitly disable magic number --- .../superset-ui/README.md | 1 + .../superset-ui-time-format/README.md | 57 +++++++++ .../superset-ui-time-format/package.json | 34 +++++ .../src/TimeFormats.js | 8 ++ .../src/TimeFormatter.js | 35 ++++++ .../src/TimeFormatterRegistry.js | 34 +++++ .../src/TimeFormatterRegistrySingleton.js | 14 +++ .../src/factories/createD3TimeFormatter.js | 23 ++++ .../src/factories/createMultiFormatter.js | 69 +++++++++++ .../src/formatters/smartDate.js | 20 +++ .../src/formatters/smartDateVerbose.js | 20 +++ .../superset-ui-time-format/src/index.js | 10 ++ .../superset-ui-time-format/src/utils.js | 116 ++++++++++++++++++ .../test/TimeFormatter.test.js | 58 +++++++++ .../test/TimeFormatterRegistry.test.js | 32 +++++ .../TimeFormatterRegistrySingleton.test.js | 26 ++++ .../factories/createD3TimeFormatter.test.js | 31 +++++ .../factories/createMultiFormatter.test.js | 34 +++++ .../test/formatters/smartDate.test.js | 24 ++++ .../test/formatters/smartDateVerbose.test.js | 23 ++++ .../test/index.test.js | 21 ++++ .../test/utils.test.js | 92 ++++++++++++++ 22 files changed, 782 insertions(+) create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/README.md create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/package.json create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/TimeFormats.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/TimeFormatter.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/TimeFormatterRegistry.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/TimeFormatterRegistrySingleton.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/factories/createD3TimeFormatter.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/factories/createMultiFormatter.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/formatters/smartDate.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/formatters/smartDateVerbose.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/index.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/utils.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/TimeFormatter.test.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/TimeFormatterRegistry.test.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/TimeFormatterRegistrySingleton.test.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/factories/createD3TimeFormatter.test.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/factories/createMultiFormatter.test.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/formatters/smartDate.test.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/formatters/smartDateVerbose.test.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/index.test.js create mode 100644 superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/utils.test.js diff --git a/superset-frontend/temporary_superset_ui/superset-ui/README.md b/superset-frontend/temporary_superset_ui/superset-ui/README.md index 9d3409ca0d383..d4dac26aa59ec 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/README.md +++ b/superset-frontend/temporary_superset_ui/superset-ui/README.md @@ -18,6 +18,7 @@ applications that leverage a Superset backend :chart_with_upwards_trend: | [@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) | | [@superset-ui/generator-superset](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-generator-superset) | [![Version](https://img.shields.io/npm/v/@superset-ui/generator-superset.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/generator-superset.svg?style=flat-square) | | [@superset-ui/number-format](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-number-format) | [![Version](https://img.shields.io/npm/v/@superset-ui/number-format.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/number-format.svg?style=flat-square) | +| [@superset-ui/time-format](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-time-format) | [![Version](https://img.shields.io/npm/v/@superset-ui/time-format.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/time-format.svg?style=flat-square) | | [@superset-ui/translation](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-translation) | [![Version](https://img.shields.io/npm/v/@superset-ui/translation.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/translation.svg?style=flat-square) | #### Coming :soon: diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/README.md b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/README.md new file mode 100644 index 0000000000000..537b968aa42b5 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/README.md @@ -0,0 +1,57 @@ +## @superset-ui/time-format + +[![Version](https://img.shields.io/npm/v/@superset-ui/time-format.svg?style=flat)](https://img.shields.io/npm/v/@superset-ui/time-format.svg?style=flat) +[![David (path)](https://img.shields.io/david/apache-superset/superset-ui.svg?path=packages%2Fsuperset-ui-time-format&style=flat-square)](https://david-dm.org/apache-superset/superset-ui?path=packages/superset-ui-time-format) + +Description + +#### Example usage + +Add `@superset-ui/time-format`, a module for formatting time. Functions `getTimeFormatter` and `formatTime` should be used instead of calling `d3.utcFormat` or `d3.timeFormat` directly. + +```js +import { getTimeFormatter } from '@superset-ui/time-format'; +const formatter = getTimeFormatter('%Y-%m-d'); +console.log(formatter(new Date())); +``` + +or + +```js +import { formatTime } from '@superset-ui/time-format'; +console.log(formatTime('%Y-%m-d', new Date())); +``` + +It is powered by a registry to support registration of custom formatting, with fallback to `d3.utcFormat` or `d3.timeFormat` (if the formatId starts with `local!`) + +```js +import { getTimeFormatterRegistry, formatTime, TimeFormatter } from '@superset-ui/time-format'; + +getTimeFormatterRegistry().registerValue('my_format', new TimeFormatter({ + id: 'my_format', + formatFunc: v => `my special format of ${utcFormat('%Y')(v)}` +}); + +console.log(formatTime('my_format', new Date(2018))); +// prints 'my special format of 2018' +``` + +It also define constants for common d3 time formats. See [TimeFormats.js](https://github.com/apache-superset/superset-ui/blob/master/packages/superset-ui-time-format/src/TimeFormats.js). + +```js +import { TimeFormats } from '@superset-ui/time-format'; + +TimeFormats.DATABASE_DATETIME // '%Y-%m-%d %H:%M:%S' +TimeFormats.US_DATE // '%m/%d/%Y' +``` + +#### 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-time-format/package.json b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/package.json new file mode 100644 index 0000000000000..73068133d2d76 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/package.json @@ -0,0 +1,34 @@ +{ + "name": "@superset-ui/time-format", + "version": "0.0.0", + "description": "Superset UI time-format", + "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.6.0", + "d3-time": "^1.0.10", + "d3-time-format": "^2.1.3", + "lodash": "^4.17.11" + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/TimeFormats.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/TimeFormats.js new file mode 100644 index 0000000000000..873da756d7d41 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/TimeFormats.js @@ -0,0 +1,8 @@ +export const LOCAL_PREFIX = 'local!'; + +export const DATABASE_DATETIME = '%Y-%m-%d %H:%M:%S'; +export const DATABASE_DATETIME_REVERSE = '%d-%m-%Y %H:%M:%S'; +export const US_DATE = '%m/%d/%Y'; +export const INTERNATIONAL_DATE = '%d/%m/%Y'; +export const DATABASE_DATE = '%Y-%m-%d'; +export const TIME = '%H:%M:%S'; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/TimeFormatter.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/TimeFormatter.js new file mode 100644 index 0000000000000..2aa1da49e6527 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/TimeFormatter.js @@ -0,0 +1,35 @@ +/* eslint-disable no-magic-numbers */ + +import { ExtensibleFunction, isRequired } from '@superset-ui/core'; + +export const PREVIEW_TIME = new Date(Date.UTC(2017, 1, 14, 11, 22, 33)); + +export default class TimeFormatter extends ExtensibleFunction { + constructor({ + id = isRequired('config.id'), + label, + description = '', + formatFunc = isRequired('config.formatFunc'), + useLocalTime = false, + } = {}) { + super((...args) => this.format(...args)); + + this.id = id; + this.label = label || id; + this.description = description; + this.formatFunc = formatFunc; + this.useLocalTime = useLocalTime; + } + + format(value) { + if (value === null || value === undefined) { + return value; + } + + return this.formatFunc(value); + } + + preview(value = PREVIEW_TIME) { + return `${value.toUTCString()} => ${this.format(value)}`; + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/TimeFormatterRegistry.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/TimeFormatterRegistry.js new file mode 100644 index 0000000000000..e92ffe8065ea2 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/TimeFormatterRegistry.js @@ -0,0 +1,34 @@ +import { RegistryWithDefaultKey } from '@superset-ui/core'; +import { DATABASE_DATETIME, LOCAL_PREFIX } from './TimeFormats'; +import createD3TimeFormatter from './factories/createD3TimeFormatter'; + +const DEFAULT_FORMAT = DATABASE_DATETIME; + +export default class TimeFormatterRegistry extends RegistryWithDefaultKey { + constructor() { + super({ + initialDefaultKey: DEFAULT_FORMAT, + name: 'TimeFormatter', + }); + } + + get(format) { + const targetFormat = format || this.defaultKey; + + if (this.has(targetFormat)) { + return super.get(targetFormat); + } + + // Create new formatter if does not exist + const useLocalTime = targetFormat.startsWith(LOCAL_PREFIX); + const formatString = targetFormat.replace(LOCAL_PREFIX, ''); + const formatter = createD3TimeFormatter({ formatString, useLocalTime }); + this.registerValue(targetFormat, formatter); + + return formatter; + } + + format(format, value) { + return this.get(format)(value); + } +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/TimeFormatterRegistrySingleton.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/TimeFormatterRegistrySingleton.js new file mode 100644 index 0000000000000..b426fa0c0e01e --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/TimeFormatterRegistrySingleton.js @@ -0,0 +1,14 @@ +import { makeSingleton } from '@superset-ui/core'; +import TimeFormatterRegistry from './TimeFormatterRegistry'; + +const getInstance = makeSingleton(TimeFormatterRegistry); + +export default getInstance; + +export function getTimeFormatter(formatId) { + return getInstance().get(formatId); +} + +export function formatTime(formatId, value) { + return getInstance().format(formatId, value); +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/factories/createD3TimeFormatter.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/factories/createD3TimeFormatter.js new file mode 100644 index 0000000000000..aec422f32f53f --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/factories/createD3TimeFormatter.js @@ -0,0 +1,23 @@ +import { utcFormat, timeFormat } from 'd3-time-format'; +import { isRequired } from '@superset-ui/core'; +import TimeFormatter from '../TimeFormatter'; +import { LOCAL_PREFIX } from '../TimeFormats'; + +export default function createD3TimeFormatter({ + description, + formatString = isRequired('formatString'), + label, + useLocalTime = false, +}) { + const id = useLocalTime ? `${LOCAL_PREFIX}${formatString}` : formatString; + const format = useLocalTime ? timeFormat : utcFormat; + const formatFunc = format(formatString); + + return new TimeFormatter({ + description, + formatFunc, + id, + label, + useLocalTime, + }); +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/factories/createMultiFormatter.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/factories/createMultiFormatter.js new file mode 100644 index 0000000000000..dffb8765f6e16 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/factories/createMultiFormatter.js @@ -0,0 +1,69 @@ +import { utcFormat, timeFormat } from 'd3-time-format'; +import { utcUtils, localTimeUtils } from '../utils'; +import TimeFormatter from '../TimeFormatter'; + +export default function createMultiFormatter({ + id, + label, + description, + formats = {}, + useLocalTime = false, +}) { + const { + millisecond = '.%L', + second = ':%S', + minute = '%I:%M', + hour = '%I %p', + day = '%a %d', + week = '%b %d', + month = '%B', + year = '%Y', + } = formats; + + const format = useLocalTime ? timeFormat : utcFormat; + + const formatMillisecond = format(millisecond); + const formatSecond = format(second); + const formatMinute = format(minute); + const formatHour = format(hour); + const formatDay = format(day); + const formatFirstDayOfWeek = format(week); + const formatMonth = format(month); + const formatYear = format(year); + + const { + hasMillisecond, + hasSecond, + hasMinute, + hasHour, + isNotFirstDayOfMonth, + isNotFirstDayOfWeek, + isNotFirstMonth, + } = useLocalTime ? localTimeUtils : utcUtils; + + function multiFormatFunc(date) { + if (hasMillisecond(date)) { + return formatMillisecond; + } else if (hasSecond(date)) { + return formatSecond; + } else if (hasMinute(date)) { + return formatMinute; + } else if (hasHour(date)) { + return formatHour; + } else if (isNotFirstDayOfMonth(date)) { + return isNotFirstDayOfWeek(date) ? formatDay : formatFirstDayOfWeek; + } else if (isNotFirstMonth(date)) { + return formatMonth; + } + + return formatYear; + } + + return new TimeFormatter({ + description, + formatFunc: date => multiFormatFunc(date)(date), + id, + label, + useLocalTime, + }); +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/formatters/smartDate.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/formatters/smartDate.js new file mode 100644 index 0000000000000..4c76a97956f21 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/formatters/smartDate.js @@ -0,0 +1,20 @@ +/* eslint-disable sort-keys */ + +import createMultiFormatter from '../factories/createMultiFormatter'; + +const smartDateFormatter = createMultiFormatter({ + id: 'smart_date', + label: 'Adaptative Formatting', + formats: { + millisecond: '.%Lms', + second: ':%Ss', + minute: '%I:%M', + hour: '%I %p', + day: '%a %d', + week: '%b %d', + month: '%B', + year: '%Y', + }, +}); + +export default smartDateFormatter; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/formatters/smartDateVerbose.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/formatters/smartDateVerbose.js new file mode 100644 index 0000000000000..f7fb0b8370869 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/formatters/smartDateVerbose.js @@ -0,0 +1,20 @@ +/* eslint-disable sort-keys */ + +import createMultiFormatter from '../factories/createMultiFormatter'; + +const smartDateFormatter = createMultiFormatter({ + id: 'smart_date_verbose', + label: 'Verbose Adaptative Formatting', + formats: { + millisecond: '.%L', + second: ':%S', + minute: '%a %b %d, %I:%M %p', + hour: '%a %b %d, %I %p', + day: '%a %b %-e', + week: '%a %b %-e', + month: '%b %Y', + year: '%Y', + }, +}); + +export default smartDateFormatter; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/index.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/index.js new file mode 100644 index 0000000000000..a34bfa6b67c4f --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/index.js @@ -0,0 +1,10 @@ +import * as TimeFormats from './TimeFormats'; + +export { + default as getTimeFormatterRegistry, + formatTime, + getTimeFormatter, +} from './TimeFormatterRegistrySingleton'; + +export { default as TimeFormatter, PREVIEW_TIME } from './TimeFormatter'; +export { TimeFormats }; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/utils.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/utils.js new file mode 100644 index 0000000000000..ce0d1927a5eeb --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/src/utils.js @@ -0,0 +1,116 @@ +/* eslint-disable sort-keys */ + +import { + timeSecond, + timeMinute, + timeHour, + timeDay, + timeWeek, + timeSunday, + timeMonday, + timeTuesday, + timeWednesday, + timeThursday, + timeFriday, + timeSaturday, + timeMonth, + timeYear, + utcSecond, + utcMinute, + utcHour, + utcDay, + utcWeek, + utcSunday, + utcMonday, + utcTuesday, + utcWednesday, + utcThursday, + utcFriday, + utcSaturday, + utcMonth, + utcYear, +} from 'd3-time'; + +function createUtils(useLocalTime = false) { + let floorSecond; + let floorMinute; + let floorHour; + let floorDay; + let floorWeek; + let floorWeekStartOnSunday; + let floorWeekStartOnMonday; + let floorWeekStartOnTuesday; + let floorWeekStartOnWednesday; + let floorWeekStartOnThursday; + let floorWeekStartOnFriday; + let floorWeekStartOnSaturday; + let floorMonth; + let floorYear; + if (useLocalTime) { + floorSecond = timeSecond; + floorMinute = timeMinute; + floorHour = timeHour; + floorDay = timeDay; + floorWeek = timeWeek; + floorWeekStartOnSunday = timeSunday; + floorWeekStartOnMonday = timeMonday; + floorWeekStartOnTuesday = timeTuesday; + floorWeekStartOnWednesday = timeWednesday; + floorWeekStartOnThursday = timeThursday; + floorWeekStartOnFriday = timeFriday; + floorWeekStartOnSaturday = timeSaturday; + floorMonth = timeMonth; + floorYear = timeYear; + } else { + floorSecond = utcSecond; + floorMinute = utcMinute; + floorHour = utcHour; + floorDay = utcDay; + floorWeek = utcWeek; + floorWeekStartOnSunday = utcSunday; + floorWeekStartOnMonday = utcMonday; + floorWeekStartOnTuesday = utcTuesday; + floorWeekStartOnWednesday = utcWednesday; + floorWeekStartOnThursday = utcThursday; + floorWeekStartOnFriday = utcFriday; + floorWeekStartOnSaturday = utcSaturday; + floorMonth = utcMonth; + floorYear = utcYear; + } + + return { + floorSecond, + floorMinute, + floorHour, + floorDay, + floorWeek, + floorWeekStartOnSunday, + floorWeekStartOnMonday, + floorWeekStartOnTuesday, + floorWeekStartOnWednesday, + floorWeekStartOnThursday, + floorWeekStartOnFriday, + floorWeekStartOnSaturday, + floorMonth, + floorYear, + hasMillisecond: date => floorSecond(date) < date, + hasSecond: date => floorMinute(date) < date, + hasMinute: date => floorHour(date) < date, + hasHour: date => floorDay(date) < date, + isNotFirstDayOfMonth: date => floorMonth(date) < date, + isNotFirstDayOfWeek: date => floorWeek(date) < date, + isNotFirstDayOfWeekStartOnSunday: date => floorWeekStartOnSunday(date) < date, + isNotFirstDayOfWeekStartOnMonday: date => floorWeekStartOnMonday(date) < date, + isNotFirstDayOfWeekStartOnTuesday: date => floorWeekStartOnTuesday(date) < date, + isNotFirstDayOfWeekStartOnWednesday: date => floorWeekStartOnWednesday(date) < date, + isNotFirstDayOfWeekStartOnThursday: date => floorWeekStartOnThursday(date) < date, + isNotFirstDayOfWeekStartOnFriday: date => floorWeekStartOnFriday(date) < date, + isNotFirstDayOfWeekStartOnSaturday: date => floorWeekStartOnSaturday(date) < date, + isNotFirstMonth: date => floorYear(date) < date, + }; +} + +const utcUtils = createUtils(); +const localTimeUtils = createUtils(true); + +export { utcUtils, localTimeUtils }; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/TimeFormatter.test.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/TimeFormatter.test.js new file mode 100644 index 0000000000000..3cc45062af68a --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/TimeFormatter.test.js @@ -0,0 +1,58 @@ +import TimeFormatter, { PREVIEW_TIME } from '../src/TimeFormatter'; + +describe('TimeFormatter', () => { + describe('new TimeFormatter(config)', () => { + it('requires config.id', () => { + expect(() => new TimeFormatter()).toThrow(); + }); + it('requires config.formatFunc', () => { + expect( + () => + new TimeFormatter({ + id: 'my_format', + }), + ).toThrow(); + }); + }); + describe('formatter is also a format function itself', () => { + const formatter = new TimeFormatter({ + id: 'year_only', + formatFunc: value => `${value.getFullYear()}`, + }); + it('returns formatted value', () => { + expect(formatter(PREVIEW_TIME)).toEqual('2017'); + }); + it('formatter(value) is the same with formatter.format(value)', () => { + const value = PREVIEW_TIME; + expect(formatter(value)).toEqual(formatter.format(value)); + }); + }); + describe('.format(value)', () => { + const formatter = new TimeFormatter({ + id: 'year_only', + formatFunc: value => `${value.getFullYear()}`, + }); + it('handles null', () => { + expect(formatter.format(null)).toBeNull(); + }); + it('handles undefined', () => { + expect(formatter.format(undefined)).toBeUndefined(); + }); + it('otherwise returns formatted value', () => { + expect(formatter.format(PREVIEW_TIME)).toEqual('2017'); + }); + }); + describe('.preview(value)', () => { + const formatter = new TimeFormatter({ + id: 'year_only', + formatFunc: value => `${value.getFullYear()}`, + }); + it('returns string comparing value before and after formatting', () => { + const time = new Date(Date.UTC(2018, 10, 21, 22, 11, 44)); + expect(formatter.preview(time)).toEqual('Wed, 21 Nov 2018 22:11:44 GMT => 2018'); + }); + it('uses the default preview value if not specified', () => { + expect(formatter.preview()).toEqual('Tue, 14 Feb 2017 11:22:33 GMT => 2017'); + }); + }); +}); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/TimeFormatterRegistry.test.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/TimeFormatterRegistry.test.js new file mode 100644 index 0000000000000..39d7b06dc8666 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/TimeFormatterRegistry.test.js @@ -0,0 +1,32 @@ +import TimeFormatterRegistry from '../src/TimeFormatterRegistry'; +import { TimeFormats, TimeFormatter, PREVIEW_TIME } from '../src'; + +describe('TimeFormatterRegistry', () => { + let registry; + beforeEach(() => { + registry = new TimeFormatterRegistry(); + }); + describe('.get(format)', () => { + it('creates and returns a new formatter if does not exist', () => { + const formatter = registry.get(TimeFormats.DATABASE_DATETIME); + expect(formatter).toBeInstanceOf(TimeFormatter); + expect(formatter.format(PREVIEW_TIME)).toEqual('2017-02-14 11:22:33'); + }); + it('returns an existing formatter if already exists', () => { + const formatter = registry.get(TimeFormats.TIME); + const formatter2 = registry.get(TimeFormats.TIME); + expect(formatter).toBe(formatter2); + }); + it('falls back to default format if format is not specified', () => { + registry.setDefaultKey(TimeFormats.INTERNATIONAL_DATE); + const formatter = registry.get(); + expect(formatter.format(PREVIEW_TIME)).toEqual('14/02/2017'); + }); + }); + describe('.format(format, value)', () => { + it('return the value with the specified format', () => { + expect(registry.format(TimeFormats.US_DATE, PREVIEW_TIME)).toEqual('02/14/2017'); + expect(registry.format(TimeFormats.TIME, PREVIEW_TIME)).toEqual('11:22:33'); + }); + }); +}); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/TimeFormatterRegistrySingleton.test.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/TimeFormatterRegistrySingleton.test.js new file mode 100644 index 0000000000000..72d471b80149d --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/TimeFormatterRegistrySingleton.test.js @@ -0,0 +1,26 @@ +import getTimeFormatterRegistry, { + getTimeFormatter, + formatTime, +} from '../src/TimeFormatterRegistrySingleton'; +import TimeFormatterRegistry from '../src/TimeFormatterRegistry'; +import { PREVIEW_TIME } from '../src/TimeFormatter'; + +describe('TimeFormatterRegistrySingleton', () => { + describe('getTimeFormatterRegistry()', () => { + it('returns a TimeFormatterRegisry', () => { + expect(getTimeFormatterRegistry()).toBeInstanceOf(TimeFormatterRegistry); + }); + }); + describe('getTimeFormatter(format)', () => { + it('returns a format function', () => { + const format = getTimeFormatter('%d/%m/%Y'); + expect(format(PREVIEW_TIME)).toEqual('14/02/2017'); + }); + }); + describe('formatTime(format, value)', () => { + it('format the given time using the specified format', () => { + const output = formatTime('%Y-%m-%d', PREVIEW_TIME); + expect(output).toEqual('2017-02-14'); + }); + }); +}); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/factories/createD3TimeFormatter.test.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/factories/createD3TimeFormatter.test.js new file mode 100644 index 0000000000000..6e47b0c984d0f --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/factories/createD3TimeFormatter.test.js @@ -0,0 +1,31 @@ +import createD3TimeFormatter from '../../src/factories/createD3TimeFormatter'; +import { PREVIEW_TIME } from '../../src/TimeFormatter'; +import { TimeFormats } from '../../src'; + +describe('createD3TimeFormatter(config)', () => { + it('requires config.formatString', () => { + expect(() => createD3TimeFormatter()).toThrow(); + }); + describe('if config.useLocalTime is true', () => { + it('formats in local time', () => { + const formatter = createD3TimeFormatter({ + formatString: TimeFormats.DATABASE_DATETIME, + useLocalTime: true, + }); + const offset = new Date().getTimezoneOffset(); + if (offset === 0) { + expect(formatter.format(PREVIEW_TIME)).toEqual('2017-02-14 11:22:33'); + } else { + expect(formatter.format(PREVIEW_TIME)).not.toEqual('2017-02-14 11:22:33'); + } + }); + }); + describe('if config.useLocalTime is false', () => { + it('formats in UTC time', () => { + const formatter = createD3TimeFormatter({ + formatString: TimeFormats.DATABASE_DATETIME, + }); + expect(formatter.format(PREVIEW_TIME)).toEqual('2017-02-14 11:22:33'); + }); + }); +}); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/factories/createMultiFormatter.test.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/factories/createMultiFormatter.test.js new file mode 100644 index 0000000000000..8bd781397e0c3 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/factories/createMultiFormatter.test.js @@ -0,0 +1,34 @@ +import createMultiFormatter from '../../src/factories/createMultiFormatter'; + +describe('createMultiFormatter', () => { + describe('creates a multi-step formatter', () => { + const formatter = createMultiFormatter({ + id: 'my_format', + useLocalTime: true, + }); + it('formats millisecond', () => { + expect(formatter(new Date(2018, 10, 20, 11, 22, 33, 100))).toEqual('.100'); + }); + it('formats second', () => { + expect(formatter(new Date(2018, 10, 20, 11, 22, 33))).toEqual(':33'); + }); + it('format minutes', () => { + expect(formatter(new Date(2018, 10, 20, 11, 22))).toEqual('11:22'); + }); + it('format hours', () => { + expect(formatter(new Date(2018, 10, 20, 11))).toEqual('11 AM'); + }); + it('format first day of week', () => { + expect(formatter(new Date(2018, 10, 18))).toEqual('Nov 18'); + }); + it('format other day of week', () => { + expect(formatter(new Date(2018, 10, 20))).toEqual('Tue 20'); + }); + it('format month', () => { + expect(formatter(new Date(2018, 10))).toEqual('November'); + }); + it('format year', () => { + expect(formatter(new Date(2018, 0))).toEqual('2018'); + }); + }); +}); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/formatters/smartDate.test.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/formatters/smartDate.test.js new file mode 100644 index 0000000000000..020d4bbe3e46c --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/formatters/smartDate.test.js @@ -0,0 +1,24 @@ +import TimeFormatter from '../../src/TimeFormatter'; +import smartDateFormatter from '../../src/formatters/smartDate'; + +describe('smartDateFormatter', () => { + it('is a function', () => { + expect(smartDateFormatter).toBeInstanceOf(TimeFormatter); + }); + + it('shows only year when 1st day of the year', () => { + expect(smartDateFormatter(new Date('2020-01-01'))).toBe('2020'); + }); + + it('shows only month when 1st of month', () => { + expect(smartDateFormatter(new Date('2020-03-01'))).toBe('March'); + }); + + it('does not show day of week when it is Sunday', () => { + expect(smartDateFormatter(new Date('2020-03-15'))).toBe('Mar 15'); + }); + + it('shows weekday when it is not Sunday (and no ms/sec/min/hr)', () => { + expect(smartDateFormatter(new Date('2020-03-03'))).toBe('Tue 03'); + }); +}); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/formatters/smartDateVerbose.test.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/formatters/smartDateVerbose.test.js new file mode 100644 index 0000000000000..4cdbaae20c440 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/formatters/smartDateVerbose.test.js @@ -0,0 +1,23 @@ +import TimeFormatter from '../../src/TimeFormatter'; +import smartDateVerboseFormatter from '../../src/formatters/smartDateVerbose'; + +describe('smartDateVerboseFormatter', () => { + const formatter = smartDateVerboseFormatter; + + it('is a function', () => { + expect(formatter).toBeInstanceOf(TimeFormatter); + }); + + it('shows only year when 1st day of the year', () => { + expect(formatter(new Date('2020-01-01'))).toBe('2020'); + }); + + it('shows month and year when 1st of month', () => { + expect(formatter(new Date('2020-03-01'))).toBe('Mar 2020'); + }); + + it('shows weekday when any day of the month', () => { + expect(formatter(new Date('2020-03-03'))).toBe('Tue Mar 3'); + expect(formatter(new Date('2020-03-15'))).toBe('Sun Mar 15'); + }); +}); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/index.test.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/index.test.js new file mode 100644 index 0000000000000..9df5372c1ecc5 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/index.test.js @@ -0,0 +1,21 @@ +import { + formatTime, + TimeFormats, + getTimeFormatter, + getTimeFormatterRegistry, + TimeFormatter, + PREVIEW_TIME, +} from '../src/index'; + +describe('index', () => { + it('exports modules', () => { + [ + formatTime, + TimeFormats, + getTimeFormatter, + getTimeFormatterRegistry, + TimeFormatter, + PREVIEW_TIME, + ].forEach(x => expect(x).toBeDefined()); + }); +}); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/utils.test.js b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/utils.test.js new file mode 100644 index 0000000000000..fba4bf652cfee --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-time-format/test/utils.test.js @@ -0,0 +1,92 @@ +import { utcUtils, localTimeUtils } from '../src/utils'; + +describe('utils', () => { + describe('utcUtils', () => { + it('has isNotFirstDayOfWeekStartOnSunday', () => { + const date = new Date(Date.UTC(2018, 10, 19)); + expect(utcUtils.isNotFirstDayOfWeekStartOnSunday(date)).toBeTruthy(); + const date2 = new Date(Date.UTC(2018, 10, 18)); + expect(utcUtils.isNotFirstDayOfWeekStartOnSunday(date2)).toBeFalsy(); + }); + it('has isNotFirstDayOfWeekStartOnMonday', () => { + const date = new Date(Date.UTC(2018, 10, 20)); + expect(utcUtils.isNotFirstDayOfWeekStartOnMonday(date)).toBeTruthy(); + const date2 = new Date(Date.UTC(2018, 10, 19)); + expect(utcUtils.isNotFirstDayOfWeekStartOnMonday(date2)).toBeFalsy(); + }); + it('has isNotFirstDayOfWeekStartOnTuesday', () => { + const date = new Date(Date.UTC(2018, 10, 21)); + expect(utcUtils.isNotFirstDayOfWeekStartOnTuesday(date)).toBeTruthy(); + const date2 = new Date(Date.UTC(2018, 10, 20)); + expect(utcUtils.isNotFirstDayOfWeekStartOnTuesday(date2)).toBeFalsy(); + }); + it('has isNotFirstDayOfWeekStartOnWednesday', () => { + const date = new Date(Date.UTC(2018, 10, 22)); + expect(utcUtils.isNotFirstDayOfWeekStartOnWednesday(date)).toBeTruthy(); + const date2 = new Date(Date.UTC(2018, 10, 21)); + expect(utcUtils.isNotFirstDayOfWeekStartOnWednesday(date2)).toBeFalsy(); + }); + it('has isNotFirstDayOfWeekStartOnThursday', () => { + const date = new Date(Date.UTC(2018, 10, 23)); + expect(utcUtils.isNotFirstDayOfWeekStartOnThursday(date)).toBeTruthy(); + const date2 = new Date(Date.UTC(2018, 10, 22)); + expect(utcUtils.isNotFirstDayOfWeekStartOnThursday(date2)).toBeFalsy(); + }); + it('has isNotFirstDayOfWeekStartOnFriday', () => { + const date = new Date(Date.UTC(2018, 10, 24)); + expect(utcUtils.isNotFirstDayOfWeekStartOnFriday(date)).toBeTruthy(); + const date2 = new Date(Date.UTC(2018, 10, 23)); + expect(utcUtils.isNotFirstDayOfWeekStartOnFriday(date2)).toBeFalsy(); + }); + it('has isNotFirstDayOfWeekStartOnSaturday', () => { + const date = new Date(Date.UTC(2018, 10, 25)); + expect(utcUtils.isNotFirstDayOfWeekStartOnSaturday(date)).toBeTruthy(); + const date2 = new Date(Date.UTC(2018, 10, 24)); + expect(utcUtils.isNotFirstDayOfWeekStartOnSaturday(date2)).toBeFalsy(); + }); + }); + describe('localTimeUtils', () => { + it('has isNotFirstDayOfWeekStartOnSunday', () => { + const date = new Date(2018, 10, 19); + expect(localTimeUtils.isNotFirstDayOfWeekStartOnSunday(date)).toBeTruthy(); + const date2 = new Date(2018, 10, 18); + expect(localTimeUtils.isNotFirstDayOfWeekStartOnSunday(date2)).toBeFalsy(); + }); + it('has isNotFirstDayOfWeekStartOnMonday', () => { + const date = new Date(2018, 10, 20); + expect(localTimeUtils.isNotFirstDayOfWeekStartOnMonday(date)).toBeTruthy(); + const date2 = new Date(2018, 10, 19); + expect(localTimeUtils.isNotFirstDayOfWeekStartOnMonday(date2)).toBeFalsy(); + }); + it('has isNotFirstDayOfWeekStartOnTuesday', () => { + const date = new Date(2018, 10, 21); + expect(localTimeUtils.isNotFirstDayOfWeekStartOnTuesday(date)).toBeTruthy(); + const date2 = new Date(2018, 10, 20); + expect(localTimeUtils.isNotFirstDayOfWeekStartOnTuesday(date2)).toBeFalsy(); + }); + it('has isNotFirstDayOfWeekStartOnWednesday', () => { + const date = new Date(2018, 10, 22); + expect(localTimeUtils.isNotFirstDayOfWeekStartOnWednesday(date)).toBeTruthy(); + const date2 = new Date(2018, 10, 21); + expect(localTimeUtils.isNotFirstDayOfWeekStartOnWednesday(date2)).toBeFalsy(); + }); + it('has isNotFirstDayOfWeekStartOnThursday', () => { + const date = new Date(2018, 10, 23); + expect(localTimeUtils.isNotFirstDayOfWeekStartOnThursday(date)).toBeTruthy(); + const date2 = new Date(2018, 10, 22); + expect(localTimeUtils.isNotFirstDayOfWeekStartOnThursday(date2)).toBeFalsy(); + }); + it('has isNotFirstDayOfWeekStartOnFriday', () => { + const date = new Date(2018, 10, 24); + expect(localTimeUtils.isNotFirstDayOfWeekStartOnFriday(date)).toBeTruthy(); + const date2 = new Date(2018, 10, 23); + expect(localTimeUtils.isNotFirstDayOfWeekStartOnFriday(date2)).toBeFalsy(); + }); + it('has isNotFirstDayOfWeekStartOnSaturday', () => { + const date = new Date(2018, 10, 25); + expect(localTimeUtils.isNotFirstDayOfWeekStartOnSaturday(date)).toBeTruthy(); + const date2 = new Date(2018, 10, 24); + expect(localTimeUtils.isNotFirstDayOfWeekStartOnSaturday(date2)).toBeFalsy(); + }); + }); +});