From 3890d5b17cc6675d6ef9c845d4b66fe8f5f0d0f9 Mon Sep 17 00:00:00 2001 From: "harshmeet.singh" Date: Thu, 21 Nov 2024 13:18:59 +0530 Subject: [PATCH 01/14] Support for attribute columns in conditional formatting. --- example/custom-bar-chart/pnpm-lock.yaml | 220 +----------------------- src/main/custom-chart-context.spec.ts | 61 +++++++ src/main/custom-chart-context.ts | 31 +++- src/types/ts-to-chart-event.types.ts | 19 ++ 4 files changed, 111 insertions(+), 220 deletions(-) diff --git a/example/custom-bar-chart/pnpm-lock.yaml b/example/custom-bar-chart/pnpm-lock.yaml index aba1ad9..958bcb7 100644 --- a/example/custom-bar-chart/pnpm-lock.yaml +++ b/example/custom-bar-chart/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: dependencies: '@thoughtspot/ts-chart-sdk': - specifier: 0.0.2-alpha.6 - version: 0.0.2-alpha.6 + specifier: 0.0.2-alpha.20 + version: link:../.. chart.js: specifier: ^4.3.0 version: 4.3.0 @@ -227,147 +227,6 @@ packages: resolution: {integrity: sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==} dev: false - /@rollup/rollup-android-arm-eabi@4.18.1: - resolution: {integrity: sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==} - cpu: [arm] - os: [android] - requiresBuild: true - dev: false - optional: true - - /@rollup/rollup-android-arm64@4.18.1: - resolution: {integrity: sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: false - optional: true - - /@rollup/rollup-darwin-arm64@4.18.1: - resolution: {integrity: sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@rollup/rollup-darwin-x64@4.18.1: - resolution: {integrity: sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@rollup/rollup-linux-arm-gnueabihf@4.18.1: - resolution: {integrity: sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@rollup/rollup-linux-arm-musleabihf@4.18.1: - resolution: {integrity: sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@rollup/rollup-linux-arm64-gnu@4.18.1: - resolution: {integrity: sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@rollup/rollup-linux-arm64-musl@4.18.1: - resolution: {integrity: sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@rollup/rollup-linux-powerpc64le-gnu@4.18.1: - resolution: {integrity: sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@rollup/rollup-linux-riscv64-gnu@4.18.1: - resolution: {integrity: sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@rollup/rollup-linux-s390x-gnu@4.18.1: - resolution: {integrity: sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@rollup/rollup-linux-x64-gnu@4.18.1: - resolution: {integrity: sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@rollup/rollup-linux-x64-musl@4.18.1: - resolution: {integrity: sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@rollup/rollup-win32-arm64-msvc@4.18.1: - resolution: {integrity: sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@rollup/rollup-win32-ia32-msvc@4.18.1: - resolution: {integrity: sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@rollup/rollup-win32-x64-msvc@4.18.1: - resolution: {integrity: sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@thoughtspot/ts-chart-sdk@0.0.2-alpha.6: - resolution: {integrity: sha512-DAHkPq66gCUneBJttlexx3HsHoXvxgIB9UEz5xpESiUSRwIEv9qX5IcPhO7PCBOarsyvl+ZH40Zoth2s0V9egA==} - dependencies: - lodash: 4.17.21 - promise-postmessage: 3.5.1 - react: 17.0.2 - react-dom: 17.0.2(react@17.0.2) - dev: false - - /@types/estree@1.0.5: - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: false - /chart.js@4.3.0: resolution: {integrity: sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==} engines: {pnpm: '>=7'} @@ -418,34 +277,19 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true + dev: true optional: true - /js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: false - /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: false - /loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - dependencies: - js-tokens: 4.0.0 - dev: false - /nanoid@3.3.6: resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true dev: true - /object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - dev: false - /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true @@ -459,31 +303,6 @@ packages: source-map-js: 1.0.2 dev: true - /promise-postmessage@3.5.1: - resolution: {integrity: sha512-ytApGxdLLtv01O5iceqz7WttChm23obxTbrckMX4ZqWy5r8XM9PE0nzNOuChDLT13i62usOW1O0V2mJlBblA3g==} - dependencies: - rollup: 4.18.1 - dev: false - - /react-dom@17.0.2(react@17.0.2): - resolution: {integrity: sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==} - peerDependencies: - react: 17.0.2 - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react: 17.0.2 - scheduler: 0.20.2 - dev: false - - /react@17.0.2: - resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==} - engines: {node: '>=0.10.0'} - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - dev: false - /rollup@3.26.0: resolution: {integrity: sha512-YzJH0eunH2hr3knvF3i6IkLO/jTjAEwU4HoMUbQl4//Tnl3ou0e7P5SjxdDr8HQJdeUJShlbEHXrrnEHy1l7Yg==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} @@ -492,39 +311,6 @@ packages: fsevents: 2.3.2 dev: true - /rollup@4.18.1: - resolution: {integrity: sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - dependencies: - '@types/estree': 1.0.5 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.18.1 - '@rollup/rollup-android-arm64': 4.18.1 - '@rollup/rollup-darwin-arm64': 4.18.1 - '@rollup/rollup-darwin-x64': 4.18.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.18.1 - '@rollup/rollup-linux-arm-musleabihf': 4.18.1 - '@rollup/rollup-linux-arm64-gnu': 4.18.1 - '@rollup/rollup-linux-arm64-musl': 4.18.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.18.1 - '@rollup/rollup-linux-riscv64-gnu': 4.18.1 - '@rollup/rollup-linux-s390x-gnu': 4.18.1 - '@rollup/rollup-linux-x64-gnu': 4.18.1 - '@rollup/rollup-linux-x64-musl': 4.18.1 - '@rollup/rollup-win32-arm64-msvc': 4.18.1 - '@rollup/rollup-win32-ia32-msvc': 4.18.1 - '@rollup/rollup-win32-x64-msvc': 4.18.1 - fsevents: 2.3.2 - dev: false - - /scheduler@0.20.2: - resolution: {integrity: sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==} - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - dev: false - /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} diff --git a/src/main/custom-chart-context.spec.ts b/src/main/custom-chart-context.spec.ts index 5152a4f..0c59f2e 100644 --- a/src/main/custom-chart-context.spec.ts +++ b/src/main/custom-chart-context.spec.ts @@ -505,6 +505,67 @@ describe('CustomChartContext', () => { }); }); + test('should handle GetColumnData event with valid columnId', async () => { + // Set up mock data in the chart model + const mockData = { + data: [ + { + data: { + columns: ['col1', 'col2', 'col3'], + dataValue: [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + ], + }, + }, + ], + }; + + (customChartContext as any).chartModel = mockData; + + const response = await eventProcessor({ + payload: { + columnId: 'col2', + }, + eventType: TSToChartEvent.GetColumnData, + }); + + expect(response).toEqual({ + data: [2, 5, 8], + }); + }); + + test('should return empty array for non-existent columnId', async () => { + // Set up mock data in the chart model + const mockData = { + data: [ + { + data: { + columns: ['col1', 'col2', 'col3'], + dataValue: [ + [1, 2, 3], + [4, 5, 6], + ], + }, + }, + ], + }; + + (customChartContext as any).chartModel = mockData; + + const response = await eventProcessor({ + payload: { + columnId: 'non-existent-column', + }, + eventType: TSToChartEvent.GetColumnData, + }); + + expect(response).toEqual({ + data: [], + }); + }); + test('TSToChartEvent.validateVisualProps should be called with correct activeColumnId', () => { // Define initial context with object definitions const mockValidateVisualProps = jest diff --git a/src/main/custom-chart-context.ts b/src/main/custom-chart-context.ts index efb262c..2e50524 100644 --- a/src/main/custom-chart-context.ts +++ b/src/main/custom-chart-context.ts @@ -35,6 +35,8 @@ import { ChartModelUpdateEventPayload, ContextMenuCustomActionPayload, DataUpdateEventPayload, + GetColumnDataPayload, + GetColumnDataResponsePayload, GetDataQueryPayload, GetDataQueryResponsePayload, InitializeEventPayload, @@ -236,15 +238,16 @@ export type CustomChartContextProps = { | VisualPropEditorDefinition; /** - * Optional configuration to toggle native TS UI configurations, such as column number formatting - * and conditional formatting. + * Optional configuration to toggle native TS UI configurations, such as column number + * formatting and conditional formatting. * * @type {AllowedConfigurations} * @version SDK: 0.1 | ThoughtSpot: */ allowedConfigurations?: AllowedConfigurations; /** - * Optional parameters for configuring specific chart-related features, such as measure name and value columns. + * Optional parameters for configuring specific chart-related features, such as measure name + * and value columns. * * @type {ChartConfigParameters} * @version SDK: 0.1 | ThoughtSpot: @@ -815,6 +818,28 @@ export class CustomChartContext { }, ); + /** + * This event is triggered when the TS app asks for data in a specific columns + * for certain validations for settings and drop down options related to Conditional + * formatting and other advanced settings + */ + this.onInternal( + TSToChartEvent.GetColumnData, + (payload: GetColumnDataPayload): GetColumnDataResponsePayload => { + const parsedData = this.chartModel.data?.[0].data; + const dataIdx = parsedData?.columns.findIndex( + (columnId) => columnId === payload.columnId, + ); + const dataArray = + !_.isNil(dataIdx) && dataIdx > -1 + ? parsedData?.dataValue.map((it) => it[dataIdx]) + : []; + return { + data: dataArray, + }; + }, + ); + /** * This event is triggered when the TS app re-renders the chart */ diff --git a/src/types/ts-to-chart-event.types.ts b/src/types/ts-to-chart-event.types.ts index 3f707a4..6c1e60a 100644 --- a/src/types/ts-to-chart-event.types.ts +++ b/src/types/ts-to-chart-event.types.ts @@ -8,6 +8,7 @@ import { AppConfig, ChartConfig, ChartModel, + DataPointsArray, QueryData, ValidationResponse, VisualProps, @@ -72,6 +73,11 @@ export enum TSToChartEvent { * @version SDK: 0.1 | ThoughtSpot: */ AxisMenuActionClick = 'AxisMenuActionClick', + + /** + * @version SDK: 0.2 | ThoughtSpot: + */ + GetColumnData = 'GetColumnData', } /** @@ -110,6 +116,10 @@ export interface TSToChartInternalEventsPayloadMap { payload: GetDataQueryPayload, ) => GetDataQueryResponsePayload; + [TSToChartEvent.GetColumnData]: ( + payload: GetColumnDataPayload, + ) => GetColumnDataResponsePayload; + [TSToChartEvent.ChartConfigValidate]: ( payload: ChartConfigValidateEventPayload, ) => ValidationResponse; @@ -226,6 +236,10 @@ export interface GetDataQueryPayload { config: ChartConfig[]; } +export interface GetColumnDataPayload { + columnId: string; +} + /** * * @group ThoughtSpot to Chart Events @@ -260,6 +274,11 @@ export interface GetDataQueryResponsePayload { queries: Query[]; } +export interface GetColumnDataResponsePayload { + // Marked as any as the data from columns can be of any type + data?: any[]; +} + /** * * @group ThoughtSpot to Chart Events From 6cfd3997fb6e8e404f2a127bdbe5b9e5d00a55d3 Mon Sep 17 00:00:00 2001 From: utkarsh-rai Date: Wed, 6 Nov 2024 19:25:45 +0530 Subject: [PATCH 02/14] SCAL-229204 defined types for dateFormatConfig in appConfig --- src/types/common.types.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/types/common.types.ts b/src/types/common.types.ts index f9b637e..38eebff 100644 --- a/src/types/common.types.ts +++ b/src/types/common.types.ts @@ -250,9 +250,25 @@ export type ChartSdkCustomStylingConfig = { fontFaces?: Array; }; +/** + * Date Formats Config for Custom Chart SDK + * This is used to define the date formats for the custom chart SDK + * The date formats are used to format the date values in the chart + * @remarks + * The date formats are used to format the date values in the chart + */ + +export type ChartSdkDateFormatsConfig = { + tsLocaleBasedDateFormats?: Record; + tsLocaleBasedStringsFromats?: Record; + tsDateConstants?: Record; + calanders?: any; + DEFAULT_DATASOURCE_ID?: any; +}; + export interface AppConfig { styleConfig?: ChartSdkCustomStylingConfig; - + dateFormatsConfig?: ChartSdkDateFormatsConfig; appOptions?: { isMobile?: boolean; isPrintMode?: boolean; // export mode on/off From e06ab4ddc8c2700df1e2165ba3c9ad4ecbd93b94 Mon Sep 17 00:00:00 2001 From: utkarsh-rai Date: Wed, 6 Nov 2024 17:37:40 +0530 Subject: [PATCH 03/14] SCAL-229204 added date formatting utils --- example/custom-bar-chart/custom-chart.ts | 30 +- src/utils/date-formatting.ts | 415 +++++++++++++++++++++++ src/utils/date-utils.ts | 18 + src/utils/formatting-util.ts | 148 ++++++++ src/utils/translations/date-formatter.ts | 34 ++ 5 files changed, 641 insertions(+), 4 deletions(-) create mode 100644 src/utils/date-utils.ts create mode 100644 src/utils/formatting-util.ts create mode 100644 src/utils/translations/date-formatter.ts diff --git a/example/custom-bar-chart/custom-chart.ts b/example/custom-bar-chart/custom-chart.ts index 2d76f45..bb04338 100644 --- a/example/custom-bar-chart/custom-chart.ts +++ b/example/custom-bar-chart/custom-chart.ts @@ -20,6 +20,7 @@ import { dateFormatter, getCfForColumn, getChartContext, + getCustomCalendarGuidFromColumn, isDateColumn, isDateNumColumn, PointVal, @@ -29,6 +30,10 @@ import { VisualProps, } from '@thoughtspot/ts-chart-sdk'; import { ChartConfigEditorDefinition } from '@thoughtspot/ts-chart-sdk/src'; +import { + generateMapOptions, + getDataFormatter, +} from '@thoughtspot/ts-chart-sdk/src/utils/formatting-util'; import Chart from 'chart.js/auto'; import ChartDataLabels from 'chartjs-plugin-datalabels'; import _ from 'lodash'; @@ -47,6 +52,9 @@ Chart.register(ChartDataLabels); let globalChartReference: Chart; +let locale; +let quarterStartMonth; + const exampleClientState = { id: 'chart-id', name: 'custom-bar-chart', @@ -54,14 +62,26 @@ const exampleClientState = { }; function getDataForColumn(column: ChartColumn, dataArr: DataPointsArray) { + const formatter = getDataFormatter(column, { isMillisIncluded: false }); const idx = _.findIndex(dataArr.columns, (colId) => column.id === colId); - return _.map(dataArr.dataValue, (row) => { + const dataForCol = _.map(dataArr.dataValue, (row) => { const colValue = row[idx]; - if (isDateColumn(column) || isDateNumColumn(column)) { - return dateFormatter(colValue, column); - } return colValue; }); + const options = generateMapOptions( + locale, + quarterStartMonth, + column, + dataForCol, + ); + const formattedValuesForData = _.map(dataArr.dataValue, (row) => { + const colValue = row[idx]; + if (getCustomCalendarGuidFromColumn(column)) + return formatter(colValue.v.s, options); + return formatter(colValue, options); + }); + + return formattedValuesForData; } function getColumnDataModel( @@ -190,6 +210,8 @@ function insertCustomFont(customFontFaces) { function render(ctx: CustomChartContext) { const chartModel = ctx.getChartModel(); const appConfig = ctx.getAppConfig(); + locale = appConfig?.localeOptions?.locale; + quarterStartMonth = appConfig?.localeOptions?.quarterStartMonth; ctx.emitEvent(ChartToTSEvent.UpdateVisualProps, { visualProps: JSON.parse( diff --git a/src/utils/date-formatting.ts b/src/utils/date-formatting.ts index 76c8087..75cf28b 100644 --- a/src/utils/date-formatting.ts +++ b/src/utils/date-formatting.ts @@ -14,6 +14,7 @@ import { ColumnType, DataType, } from '../types/answer-column.types'; +import { dateFormats } from './translations/date-formatter'; export interface CustomCalendarDate { v: { @@ -23,12 +24,142 @@ export interface CustomCalendarDate { d: string; } +export const dateFormatPresets = { + DATE_SHORT: 'DATE_SHORT', + DATE_SHORT_WITH_HOUR: 'DATE_SHORT_WITH_HOUR', + DATE_SHORT_WITH_HOUR_WITHOUT_YEAR: 'DATE_SHORT_WITH_HOUR_WITHOUT_YEAR', + DATETIME_SHORT: 'DATETIME_SHORT', + DATETIME_SHORT_WITHOUT_YEAR: 'DATETIME_SHORT_WITHOUT_YEAR', + DATETIME_SHORT_WITH_SECONDS: 'DATETIME_SHORT_WITH_SECONDS', + DATETIME_SHORT_WITH_MILLIS: 'DATETIME_SHORT_WITH_MILLIS', + MONTH_WITH_YEAR: 'MONTH_WITH_YEAR', + QUARTER_WITH_YEAR: 'QUARTER_WITH_YEAR', + QUARTER_WITH_2_DIGIT_YEAR: 'QUARTER_WITH_2_DIGIT_YEAR', + DEFAULT_TIME_FORMAT: 'DEFAULT_TIME_FORMAT', + TIME_24_WITH_SECONDS: 'TIME_24_WITH_SECONDS', + DATE_SHORT_2_DIGIT_YEAR: 'DATE_SHORT_2_DIGIT_YEAR', + DATETIME_24_SHORT_WITHOUT_YEAR: 'DATETIME_24_SHORT_WITHOUT_YEAR', + DATETIME_24_SHORT: 'DATETIME_24_SHORT', + DATETIME_SHORT_WITH_SECONDS_WITHOUT_YEAR: + 'DATETIME_SHORT_WITH_SECONDS_WITHOUT_YEAR', + DATETIME_SHORT_WITH_MILLIS_WITHOUT_YEAR: + 'DATETIME_SHORT_WITH_MILLIS_WITHOUT_YEAR', + DATETIME_24_SHORT_WITH_MILLIS_WITHOUT_YEAR: + 'DATETIME_24_SHORT_WITH_MILLIS_WITHOUT_YEAR', + DATETIME_24_SHORT_WITH_MILLIS: 'DATETIME_24_SHORT_WITH_MILLIS', + MONTH_WITH_DAY_AND_YEAR: 'MONTH_WITH_DAY_AND_YEAR', + MONTH_WITH_2_DIGIT_YEAR: 'MONTH_WITH_2_DIGIT_YEAR', + DAY_WITH_MONTH_NUM: 'DAY_WITH_MONTH_NUM', + DATE_SHORT_WITH_HOUR_24_WITHOUT_YEAR: + 'DATE_SHORT_WITH_HOUR_24_WITHOUT_YEAR', + DATE_SHORT_WITH_HOUR_24: 'DATE_SHORT_WITH_HOUR_24', + QUARTER: 'QUARTER', + MONTH_ONLY: 'MONTH_ONLY', + DATETIME_WITH_SHORT_OFFSET: 'DATETIME_WITH_SHORT_OFFSET', +}; +const yearlessFormats = { + DATE_SHORT: 'DAY_WITH_MONTH', + DATE_SHORT_WITH_HOUR: 'DATE_SHORT_WITH_HOUR_WITHOUT_YEAR', + DATETIME_SHORT: 'DATETIME_SHORT_WITHOUT_YEAR', + DATETIME_SHORT_WITH_SECONDS: 'DATETIME_SHORT_WITH_SECONDS_WITHOUT_YEAR', + DATETIME_SHORT_WITH_MILLIS: 'DATETIME_SHORT_WITH_MILLIS_WITHOUT_YEAR', + MONTH_WITH_YEAR: 'MONTH_ONLY', + QUARTER_WITH_YEAR: 'QUARTER', + QUARTER_WITH_2_DIGIT_YEAR: 'QUARTER', + DATE_SHORT_2_DIGIT_YEAR: 'DAY_WITH_MONTH', + DATETIME_24_SHORT: 'DATETIME_24_SHORT_WITHOUT_YEAR', + DATETIME_24_SHORT_WITH_MILLIS: 'DATETIME_24_SHORT_WITH_MILLIS_WITHOUT_YEAR', + MONTH_WITH_DAY_AND_YEAR: 'DAY_WITH_MONTH', + MONTH_WITH_2_DIGIT_YEAR: 'DAY_WITH_MONTH', + DATE_SHORT_WITH_HOUR_24: 'DATE_SHORT_WITH_HOUR_24_WITHOUT_YEAR', +}; + +const dateNumTypes = { + DATE_NUM_ABS_DAY: 'DATE_NUM_ABS_DAY', + DATE_NUM_ABS_MONTH: 'DATE_NUM_ABS_MONTH', + DATE_NUM_ABS_QUARTER: 'DATE_NUM_ABS_QUARTER', + DATE_NUM_ABS_YEAR: 'DATE_NUM_ABS_YEAR', + DATE_NUM_DAY_IN_MONTH: 'DATE_NUM_DAY_IN_MONTH', + DATE_NUM_DAY_IN_QUARTER: 'DATE_NUM_DAY_IN_QUARTER', + DATE_NUM_DAY_IN_YEAR: 'DATE_NUM_DAY_IN_YEAR', + DATE_NUM_DAY_OF_WEEK: 'DATE_NUM_DAY_OF_WEEK', + DATE_NUM_MONTH_IN_QUARTER: 'DATE_NUM_MONTH_IN_QUARTER', + DATE_NUM_MONTH_IN_YEAR: 'DATE_NUM_MONTH_IN_YEAR', + DATE_NUM_QUARTER_IN_YEAR: 'DATE_NUM_QUARTER_IN_YEAR', + DATE_NUM_WEEK_IN_YEAR: 'DATE_NUM_WEEK_IN_YEAR', + DATE_NUM_WEEK_IN_QUARTER: 'DATE_NUM_WEEK_IN_QUARTER', + DATE_NUM_WEEK_IN_MONTH: 'DATE_NUM_WEEK_IN_MONTH', + DATE_NUM_HOUR_IN_DAY: 'DATE_NUM_HOUR_IN_DAY', +}; + +const weekdays = [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', +]; + +const months = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', +]; + +export const bucketizationToDatePreset = { + HOURLY: dateFormatPresets.DATE_SHORT_WITH_HOUR, // hourly + DAILY: dateFormatPresets.DATE_SHORT, // daily + WEEKLY: dateFormatPresets.DATE_SHORT, // weekly + MONTHLY: dateFormatPresets.MONTH_WITH_YEAR, + QUARTERLY: dateFormatPresets.QUARTER_WITH_YEAR, // quarterly + YEARLY: 'yyyy', // yearly +}; +const dateFormatPresetsToLuxonPresets = { + [dateFormatPresets.TIME_24_WITH_SECONDS]: DateTime.TIME_24_WITH_SECONDS, +}; + +export const timeBuckets = { + NO_BUCKET: 'ms', + HOURLY: 'h', + DAILY: 'd', + WEEKLY: 'w', + MONTHLY: 'M', + QUARTERLY: 'Q', + YEARLY: 'y', + DAY_OF_WEEK: 'dow', + DAY_OF_MONTH: 'dom', + DAY_OF_QUARTER: 'doq', + DAY_OF_YEAR: 'doy', + WEEK_OF_MONTH: 'wom', + WEEK_OF_QUARTER: 'woq', + WEEK_OF_YEAR: 'woy', + MONTH_OF_QUARTER: 'moq', + MONTH_OF_YEAR: 'moy', + QUARTER_OF_YEAR: 'qoy', +}; + +const DEFAULT_QUARTER_START_MONTH = 1; + export const isDateColumn = (col: ChartColumn) => DataType[col.dataType] === 'DATE' || DataType[col.dataType] === 'DATE_TIME'; export const isAttribute = (col: ChartColumn) => col.type === ColumnType.ATTRIBUTE; +export const isDateTimeColumn = (col: ChartColumn) => + DataType[col.dataType] === 'DATE_TIME'; + export const getCustomCalendarGuidFromColumn = (col: ChartColumn) => col.calenderGuid; @@ -59,6 +190,33 @@ export const isTimeColumn = (col: ChartColumn) => { return DataType[col.dataType] === 'TIME'; }; +export const getEffectiveDateNumDataType = (col: ChartColumn) => { + switch (col.timeBucket) { + case ColumnTimeBucket.DAY_OF_WEEK: + return dateNumTypes.DATE_NUM_DAY_OF_WEEK; + case ColumnTimeBucket.DAY_OF_MONTH: + return dateNumTypes.DATE_NUM_DAY_IN_MONTH; + case ColumnTimeBucket.DAY_OF_QUARTER: + return dateNumTypes.DATE_NUM_DAY_IN_QUARTER; + case ColumnTimeBucket.DAY_OF_YEAR: + return dateNumTypes.DATE_NUM_DAY_IN_YEAR; + case ColumnTimeBucket.WEEK_OF_MONTH: + return dateNumTypes.DATE_NUM_WEEK_IN_MONTH; + case ColumnTimeBucket.WEEK_OF_QUARTER: + return dateNumTypes.DATE_NUM_WEEK_IN_QUARTER; + case ColumnTimeBucket.WEEK_OF_YEAR: + return dateNumTypes.DATE_NUM_WEEK_IN_YEAR; + case ColumnTimeBucket.MONTH_OF_QUARTER: + return dateNumTypes.DATE_NUM_MONTH_IN_QUARTER; + case ColumnTimeBucket.MONTH_OF_YEAR: + return dateNumTypes.DATE_NUM_MONTH_IN_YEAR; + case ColumnTimeBucket.QUARTER_OF_YEAR: + return dateNumTypes.DATE_NUM_QUARTER_IN_YEAR; + default: + return undefined; + } +}; + /** * Determines if the given column has a custom calendar. * @@ -81,6 +239,13 @@ export function getStartEpoch(date: CustomCalendarDate): number | null { return null; } +function getMonthOfYear(num: any, options: any) { + let monthNum = num + options.quarterStartMonth - 1; + monthNum = monthNum > 12 ? monthNum - 12 : monthNum; + + return months[monthNum - 1]; // -1 as monthNum is 1 indexed +} + export function getDisplayString(date: CustomCalendarDate): string | null { if (_.has(date, 'd')) { return date.d; @@ -88,6 +253,256 @@ export function getDisplayString(date: CustomCalendarDate): string | null { return null; } +const useQuarterStart = (luxonDate: any, quarterStartMonth: any) => { + const newLuxonDate = luxonDate; + newLuxonDate.quarterStartMonth = quarterStartMonth; + return newLuxonDate; +}; + +export const getCustomCalendarValueFromEpoch = ( + col: ChartColumn, + dateEpoch: number, + displayToCustomCalendarValueMap: any, +) => { + if ( + hasCustomCalendar(col) && + _.has(displayToCustomCalendarValueMap, dateEpoch) + ) { + return displayToCustomCalendarValueMap[dateEpoch]; + } + return null; +}; +const parseDate = (dateString: string, format: string) => { + return DateTime.fromFormat( + dateString, + dateFormats[format] || format, + ).toJSDate(); +}; + +function sanitizeDate(inputDate: string | number, format: string) { + if (typeof inputDate === 'string') { + if (!_.isNaN(Number(inputDate))) { + return parseInt(inputDate, 10); + } + return parseDate(inputDate, format); + } + return inputDate; +} + +/** + * Get the formatted date based on the format tokens + * @param {number} epochMillis + * @param {string} format: use dateFormatPresets to get localized formatted date + * or pass the format pattern for non localized results + * @returns {string} + */ +const formatDateTime = ( + epochMillis: number, + format: string, + useSystemCalendar?: boolean, + options?: any, +) => { + let newFormat = format; + let luxonDate; + try { + luxonDate = DateTime.fromMillis(epochMillis * 1000); + } catch (e) { + return 'Invalid Date'; + } + if (dateFormats[newFormat]) { + if (_.get(options, 'omitYear')) { + if (yearlessFormats[newFormat]) { + newFormat = yearlessFormats[newFormat]; + } + } + newFormat = dateFormats[newFormat]; + } + const customCalendarOverridesFiscalOffset = _.get( + options, + 'customCalendarOverridesFiscalOffset', + ); + // if format preset is a luxon preset + if (!newFormat || dateFormatPresetsToLuxonPresets[newFormat]) { + // Note: this will not add FY to the year in case of custom + // quarterStartMonth but the year would be the correct fiscal year + return luxonDate.toLocaleString( + dateFormatPresetsToLuxonPresets[newFormat], + ); + } + // qqq is not supported in luxon + newFormat = newFormat.replace(/qqq/, "'Q'q"); + // support YYYY and YY + newFormat = newFormat.replace(/(YYYY|YY)/, _.lowerCase); + const quarterStartMonth = options.quarterStartMonth; + if ( + quarterStartMonth > 1 && + !useSystemCalendar && + !customCalendarOverridesFiscalOffset + ) { + newFormat = newFormat.replace(/[yyyy||yy]/, "'FY' $&"); + } + return useQuarterStart( + luxonDate, + useSystemCalendar ? DEFAULT_QUARTER_START_MONTH : quarterStartMonth, + ).toFormat(newFormat); +}; + +function getSpecialFormatData(value: any) { + if (value === '{Empty}' || value === '{Null}') { + return value; + } + if (value === null || value === undefined) { + return '{Null}'; + } + if (value === '') { + return '{Empty}'; + } + return null; +} + +function getDayOfWeek(num: any) { + const new_num = num % 7; + return weekdays[new_num]; +} + +function getOrdinalSuffixedValue(i: number | string): string { + let ni = i; + // eslint-disable-next-line radix + ni = parseInt(ni.toString()); + const j = ni % 10; + const k = ni % 100; + // eslint-disable-next-line eqeqeq + if (j == 1 && k != 11) { + return `${ni}st`; + } + // eslint-disable-next-line eqeqeq + if (j == 2 && k != 12) { + return `${ni}nd`; + } + // eslint-disable-next-line eqeqeq + if (j == 3 && k != 13) { + return `${ni}rd`; + } + return `${ni}th`; +} + +export function formatDateNum( + effectiveDataType: string | undefined, + value: number | string, + formatPattern: string, + options: any, +) { + let newValue = value; + if (_.isString(newValue)) { + // eslint-disable-next-line radix + newValue = parseInt(value.toString()); + } + + if (!value && value !== 0) { + return '{Null}'; + } + const specialVal = getSpecialFormatData(value); + if (specialVal) { + return specialVal; + } + switch (effectiveDataType) { + case dateNumTypes.DATE_NUM_ABS_DAY: + case dateNumTypes.DATE_NUM_ABS_MONTH: + case dateNumTypes.DATE_NUM_ABS_QUARTER: + case dateNumTypes.DATE_NUM_ABS_YEAR: + return `${value}`; + case dateNumTypes.DATE_NUM_DAY_IN_MONTH: + return formatPattern === 'e' + ? `${value}` + : `${getOrdinalSuffixedValue(value)} day of month`; + case dateNumTypes.DATE_NUM_DAY_IN_QUARTER: + return formatPattern === 'm' + ? `${value}` + : `${getOrdinalSuffixedValue(value)} day of quarter`; + case dateNumTypes.DATE_NUM_DAY_IN_YEAR: + return formatPattern === 'j' + ? `${value}` + : `${getOrdinalSuffixedValue(value)} day of year`; + case dateNumTypes.DATE_NUM_DAY_OF_WEEK: + return formatPattern === 'e' + ? // eslint-disable-next-line @typescript-eslint/no-use-before-define + getDayOfWeek(value) + : `${value}`; + case dateNumTypes.DATE_NUM_MONTH_IN_QUARTER: + return formatPattern === 'm' + ? `${value}` + : `${getOrdinalSuffixedValue(value)} month of quarter`; + case dateNumTypes.DATE_NUM_MONTH_IN_YEAR: + return formatPattern === 'm' + ? // eslint-disable-next-line @typescript-eslint/no-use-before-define + getMonthOfYear(value, options) + : `${value}`; + case dateNumTypes.DATE_NUM_WEEK_IN_YEAR: + // +1 to value as Falcon values start with 0. + return formatPattern === 'V' + ? `${value}` + : `${getOrdinalSuffixedValue(value)} week of year`; + case dateNumTypes.DATE_NUM_WEEK_IN_QUARTER: + case dateNumTypes.DATE_NUM_WEEK_IN_MONTH: + return `${value}`; + case dateNumTypes.DATE_NUM_HOUR_IN_DAY: + return `${value}`; + default: + console.log( + 'unknown effectiveDataType for date num', + effectiveDataType, + ); + return value; + } +} + +/** + * + * @param {number|string} inputDate Can be either a parseable format of date or an epoch value. + * @param {string} format a dateFormatPresets or a format pattern string. + * @param {boolean} useSystemCalendar If any custom calendar setting (e.g. quarterStartMonth) + * is to be ignored. + * @return {string} Returns the formatted date per the format. + */ +export function formatDate( + inputDate: number | string, + format: string, + useSystemCalendar: boolean, + options: any, +): string { + let formatPattern = format; + let newInputDate: any = inputDate; + if (newInputDate === undefined || newInputDate === null) { + return '{Null}'; + } + if (!formatPattern) { + formatPattern = dateFormatPresets.DATE_SHORT; + } + if ( + newInputDate === '{Null}' || + newInputDate === '{Empty}' || + newInputDate === '{Other}' + ) { + return newInputDate; + } + newInputDate = sanitizeDate(newInputDate, format); + if (_.isNaN(newInputDate)) { + return '{Null}'; + } + let epochMillis = newInputDate; + if (_.isDate(epochMillis)) { + epochMillis = newInputDate.getTime(); + } + if (!_.isNumber(epochMillis)) { + console.log( + 'formatDate could not convert input date to a timestamp', + inputDate, + ); + return `${inputDate}`; + } + return formatDateTime(epochMillis, format, useSystemCalendar, options); +} + /** * Formats the date value based on the column's properties and custom calendar settings. * diff --git a/src/utils/date-utils.ts b/src/utils/date-utils.ts new file mode 100644 index 0000000..59e59c3 --- /dev/null +++ b/src/utils/date-utils.ts @@ -0,0 +1,18 @@ +import _ from 'lodash'; +import { ChartColumn, ColumnTimeBucket } from '../types/answer-column.types'; +import { bucketizationToDatePreset, timeBuckets } from './date-formatting'; + +export function getFormatPatternForBucket(bucket: ColumnTimeBucket): any { + return bucketizationToDatePreset[ColumnTimeBucket[bucket]]; +} + +export const getTimeBucket = (col: ChartColumn): string => + _.get(timeBuckets, ColumnTimeBucket[col.timeBucket], timeBuckets.NO_BUCKET); + +export const showDateFinancialYearFormat = (col: ChartColumn) => { + const supportedBucketizations = [timeBuckets.QUARTERLY, timeBuckets.YEARLY]; + const currentBucketization = getTimeBucket(col); + return supportedBucketizations.some((supportedBucketization) => { + return supportedBucketization === currentBucketization; + }); +}; diff --git a/src/utils/formatting-util.ts b/src/utils/formatting-util.ts new file mode 100644 index 0000000..6d10734 --- /dev/null +++ b/src/utils/formatting-util.ts @@ -0,0 +1,148 @@ +import _ from 'lodash'; +import { CustomChartContext } from '../main/custom-chart-context'; +import { + ChartColumn, + ColumnAggregationType, + ColumnTimeBucket, + ColumnType, + DataType, +} from '../types/answer-column.types'; +import { + CustomCalendarDate, + dateFormatPresets, + formatDate, + formatDateNum, + getCustomCalendarGuidFromColumn, + getCustomCalendarValueFromEpoch, + getDisplayString, + getEffectiveDateNumDataType, + hasCustomCalendar, + isDateColumn, + isDateNumColumn, + isDateTimeColumn, +} from './date-formatting'; +import { + getFormatPatternForBucket, + showDateFinancialYearFormat, +} from './date-utils'; + +interface formatOptionsType { + isMillisIncluded: boolean; +} + +const getBucketization = (col: ChartColumn) => col.timeBucket; + +export const getFormatPattern = (col: ChartColumn): string => + getFormatPatternForBucket(getBucketization(col)) || col.format?.pattern; + +function getBaseTypeFormatterInstanceExpensive( + col: ChartColumn, + options: formatOptionsType, +): any { + let formatPattern = getFormatPattern(col); + // TODO: add numberic formatter if the col is numeric. + if (isDateColumn(col)) { + const showFinancialFormat = showDateFinancialYearFormat(col); + const isDateTime = isDateTimeColumn(col); + if (!formatPattern) { + if (isDateTime) { + if (options.isMillisIncluded) { + formatPattern = + dateFormatPresets.DATETIME_SHORT_WITH_MILLIS; + } else { + formatPattern = + dateFormatPresets.DATETIME_SHORT_WITH_SECONDS; + } + } + } + return (dataValue: any, options?: any) => { + const customCalendarValueFromEpoch: CustomCalendarDate = + getCustomCalendarValueFromEpoch( + col, + dataValue, + options?.displayToCustomCalendarValueMap, + ); + const customCalendarDisplayStr = getDisplayString( + customCalendarValueFromEpoch, + ); + if (customCalendarValueFromEpoch && customCalendarDisplayStr) { + return customCalendarDisplayStr; + } + const customCalendarOverridesFiscalOffset = + hasCustomCalendar(col) && + getCustomCalendarGuidFromColumn(col) !== 'FISCAL_CALENDER_GUID'; // TODO: replace with guid we get from ts-app; + const optionsWithFiscalOffset = { + ...options, + customCalendarOverridesFiscalOffset: + !!customCalendarOverridesFiscalOffset, + }; + return formatDate( + dataValue, + formatPattern, + !showFinancialFormat, + optionsWithFiscalOffset, + ); + }; + } + if (isDateNumColumn(col)) { + return (dataValue: any, options?: any) => { + const customCalendarValueFromEpoch: CustomCalendarDate = + getCustomCalendarValueFromEpoch( + col, + dataValue, + options?.displayToCustomCalendarValueMap, + ); + const customCalendarDisplayStr = getDisplayString( + customCalendarValueFromEpoch, + ); + if (customCalendarValueFromEpoch && customCalendarDisplayStr) { + return customCalendarDisplayStr; + } + return formatDateNum( + getEffectiveDateNumDataType(col), + dataValue, + formatPattern, + options, + ); + }; + } + return (dataValue: any, options?: any) => { + return dataValue; + }; +} +export const getDataFormatter = ( + col: ChartColumn, + options: formatOptionsType, + aggrTypeOverride?: ColumnAggregationType, +) => { + // TODO: number formatter for column based on column type based on + // aggregation type + + return getBaseTypeFormatterInstanceExpensive(col, options); +}; +export const generateMapOptions = ( + locale: string, + quarterStartMonth: number, + col: ChartColumn, + data: any, +): any => { + let customCalenderMap = {}; + + if ( + getCustomCalendarGuidFromColumn(col) !== null && + getCustomCalendarGuidFromColumn(col) !== undefined && + getCustomCalendarGuidFromColumn(col) !== '' + ) { + for (let i = 0; i < data.length; i++) { + customCalenderMap = { + ...customCalenderMap, + [data[i].v.s]: data[i], + }; + } + } + return { + locale, + quarterStartMonth, + displayToCustomCalendarValueMap: customCalenderMap, + }; +}; diff --git a/src/utils/translations/date-formatter.ts b/src/utils/translations/date-formatter.ts new file mode 100644 index 0000000..b27005e --- /dev/null +++ b/src/utils/translations/date-formatter.ts @@ -0,0 +1,34 @@ +export const dateFormats: any = { + DATE_SHORT: 'dd/MM/yyyy', + DATE_SHORT_2_DIGIT_YEAR: 'dd/MM/yy', + DATE_SHORT_WITH_HOUR: 'dd/MM/yyyy hh a', + DATE_SHORT_WITH_HOUR_WITHOUT_YEAR: 'dd/MM hh a', + DATE_SHORT_WITH_HOUR_24: 'dd/MM/yy HH', + DATE_SHORT_WITH_HOUR_24_WITHOUT_YEAR: 'dd/MM HH', + DATETIME_SHORT: 'dd/MM/yyyy hh:mm a', + DATETIME_SHORT_WITHOUT_YEAR: 'dd/MM hh:mm a', + DATETIME_24_SHORT: 'dd/MM/yy HH:mm', + DATETIME_24_SHORT_WITH_MILLIS: 'dd/MM/yyyy HH:mm:ss.S', + DATETIME_24_SHORT_WITH_MILLIS_WITHOUT_YEAR: 'dd/MM HH:mm:ss.S', + DATETIME_SHORT_WITH_SECONDS: 'dd/MM/yyyy HH:mm:ss', + DATETIME_SHORT_WITH_SECONDS_WITHOUT_YEAR: 'dd/MM HH:mm:ss', + DATETIME_SHORT_WITH_MILLIS: 'MM/dd/yyyy HH:mm:ss.S', + DATETIME_SHORT_WITH_MILLIS_WITHOUT_YEAR: 'MM/dd HH:mm:ss.S', + QUARTER_WITH_YEAR: "'Q'q yyyy", + QUARTER_WITH_2_DIGIT_YEAR: "'Q'q yy", + DEFAULT_TIME_FORMAT: 'dd MMM, yyyy hh:mm:ss a ZZZZ', + MONTH_WITH_YEAR: 'MMM yyyy', + MONTH_WITH_DAY_AND_YEAR: 'dd MMM, yyyy', + MONTH_WITH_2_DIGIT_YEAR: 'MMM yy', + DAY_WITH_MONTH: 'dd MMM', + DAY_WITH_MONTH_NUM: 'dd/MM', + QUARTER: "'Q'q", + MONTH_ONLY: 'MMM', + DATETIME_WITH_SHORT_OFFSET: 'dd/MM/yyyy HH:mm:ss ZZZZ', +}; + +// TODO: discuss the current working. + +export function updateDateFormats(obj: any) { + _.merge(dateFormats, obj); +} From ae83f0b3f86c9a3594004820d1ff55244202c710 Mon Sep 17 00:00:00 2001 From: utkarsh-rai Date: Wed, 6 Nov 2024 18:10:01 +0530 Subject: [PATCH 04/14] SCAL-229204 type checking fixed --- src/utils/date-formatting.ts | 5 +++-- src/utils/date-utils.ts | 4 +++- src/utils/translations/date-formatter.ts | 2 ++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/utils/date-formatting.ts b/src/utils/date-formatting.ts index 75cf28b..1fcf005 100644 --- a/src/utils/date-formatting.ts +++ b/src/utils/date-formatting.ts @@ -311,8 +311,9 @@ const formatDateTime = ( } if (dateFormats[newFormat]) { if (_.get(options, 'omitYear')) { - if (yearlessFormats[newFormat]) { - newFormat = yearlessFormats[newFormat]; + if (yearlessFormats[newFormat as keyof typeof yearlessFormats]) { + newFormat = + yearlessFormats[newFormat as keyof typeof yearlessFormats]; } } newFormat = dateFormats[newFormat]; diff --git a/src/utils/date-utils.ts b/src/utils/date-utils.ts index 59e59c3..de16543 100644 --- a/src/utils/date-utils.ts +++ b/src/utils/date-utils.ts @@ -3,7 +3,9 @@ import { ChartColumn, ColumnTimeBucket } from '../types/answer-column.types'; import { bucketizationToDatePreset, timeBuckets } from './date-formatting'; export function getFormatPatternForBucket(bucket: ColumnTimeBucket): any { - return bucketizationToDatePreset[ColumnTimeBucket[bucket]]; + return bucketizationToDatePreset[ + ColumnTimeBucket[bucket] as keyof typeof bucketizationToDatePreset + ]; } export const getTimeBucket = (col: ChartColumn): string => diff --git a/src/utils/translations/date-formatter.ts b/src/utils/translations/date-formatter.ts index b27005e..0af8af3 100644 --- a/src/utils/translations/date-formatter.ts +++ b/src/utils/translations/date-formatter.ts @@ -1,3 +1,5 @@ +import _ from 'lodash'; + export const dateFormats: any = { DATE_SHORT: 'dd/MM/yyyy', DATE_SHORT_2_DIGIT_YEAR: 'dd/MM/yy', From 367c0ce80ff67586e5b039c6416ff5056fe9fb68 Mon Sep 17 00:00:00 2001 From: utkarsh-rai Date: Thu, 7 Nov 2024 15:22:02 +0530 Subject: [PATCH 05/14] SCAL-229204-types added date formatting utils and tsFormatdateConfig from AppConfig --- example/custom-bar-chart/custom-chart.ts | 14 +-- src/types/common.types.ts | 2 +- src/utils/date-formatting.ts | 124 ++++++++++++++++------- src/utils/date-utils.ts | 10 ++ src/utils/formatting-util.ts | 24 ++++- 5 files changed, 120 insertions(+), 54 deletions(-) diff --git a/example/custom-bar-chart/custom-chart.ts b/example/custom-bar-chart/custom-chart.ts index bb04338..44a42bc 100644 --- a/example/custom-bar-chart/custom-chart.ts +++ b/example/custom-bar-chart/custom-chart.ts @@ -9,6 +9,7 @@ */ import { + AppConfig, ChartColumn, ChartConfig, ChartModel, @@ -52,8 +53,7 @@ Chart.register(ChartDataLabels); let globalChartReference: Chart; -let locale; -let quarterStartMonth; +let appConfigGlobal: AppConfig; const exampleClientState = { id: 'chart-id', @@ -68,12 +68,7 @@ function getDataForColumn(column: ChartColumn, dataArr: DataPointsArray) { const colValue = row[idx]; return colValue; }); - const options = generateMapOptions( - locale, - quarterStartMonth, - column, - dataForCol, - ); + const options = generateMapOptions(appConfigGlobal, column, dataForCol); const formattedValuesForData = _.map(dataArr.dataValue, (row) => { const colValue = row[idx]; if (getCustomCalendarGuidFromColumn(column)) @@ -210,8 +205,7 @@ function insertCustomFont(customFontFaces) { function render(ctx: CustomChartContext) { const chartModel = ctx.getChartModel(); const appConfig = ctx.getAppConfig(); - locale = appConfig?.localeOptions?.locale; - quarterStartMonth = appConfig?.localeOptions?.quarterStartMonth; + appConfigGlobal = appConfig; ctx.emitEvent(ChartToTSEvent.UpdateVisualProps, { visualProps: JSON.parse( diff --git a/src/types/common.types.ts b/src/types/common.types.ts index 38eebff..b4805cc 100644 --- a/src/types/common.types.ts +++ b/src/types/common.types.ts @@ -260,7 +260,7 @@ export type ChartSdkCustomStylingConfig = { export type ChartSdkDateFormatsConfig = { tsLocaleBasedDateFormats?: Record; - tsLocaleBasedStringsFromats?: Record; + tsLocaleBasedStringsFormats?: Record; tsDateConstants?: Record; calanders?: any; DEFAULT_DATASOURCE_ID?: any; diff --git a/src/utils/date-formatting.ts b/src/utils/date-formatting.ts index 1fcf005..ea46209 100644 --- a/src/utils/date-formatting.ts +++ b/src/utils/date-formatting.ts @@ -239,11 +239,15 @@ export function getStartEpoch(date: CustomCalendarDate): number | null { return null; } -function getMonthOfYear(num: any, options: any) { - let monthNum = num + options.quarterStartMonth - 1; +export const assign = (quarter_of_year: any, value: any) => { + return quarter_of_year.replace(/\{.*?\}/, value); +}; + +function getMonthOfYear(num: any, quarterStartMonth: any, monthOfYear: any) { + let monthNum = num + quarterStartMonth - 1; monthNum = monthNum > 12 ? monthNum - 12 : monthNum; - return months[monthNum - 1]; // -1 as monthNum is 1 indexed + return monthOfYear[months[monthNum - 1]]; // -1 as monthNum is 1 indexed } export function getDisplayString(date: CustomCalendarDate): string | null { @@ -279,7 +283,43 @@ const parseDate = (dateString: string, format: string) => { ).toJSDate(); }; -function sanitizeDate(inputDate: string | number, format: string) { +export function getSpecialFormatData(value: string | number, options: any) { + if ( + value === + options.tsLocaleBasedStringsFormats.null_value_placeholder_label || + value === + options.tsLocaleBasedStringsFormats.empty_value_placeholder_label + ) { + return value; + } + + if (value === options.tsDateConstants.special_value_unavailable) { + return options.tsLocaleBasedStringsFormats + .unavailabe_column_sample_value; + } + + // This (==) checks for both null and undefined + if (value === null || value === undefined) { + return options.tsLocaleBasedStringsFormats.null_value_placeholder_label; + } + // {Empty} placeholder is set for empty string or no characters + // other than spaces. + if (value === '') { + return options.tsLocaleBasedStringsFormats + .empty_value_placeholder_label; + } + return null; +} + +function sanitizeDate( + inputDate: string | number, + format: string, + options: any, +) { + const specialVal = getSpecialFormatData(inputDate, options); + if (specialVal) { + return specialVal; + } if (typeof inputDate === 'string') { if (!_.isNaN(Number(inputDate))) { return parseInt(inputDate, 10); @@ -309,14 +349,14 @@ const formatDateTime = ( } catch (e) { return 'Invalid Date'; } - if (dateFormats[newFormat]) { + if (options.tsLocaleBasedDateFormats[newFormat]) { if (_.get(options, 'omitYear')) { if (yearlessFormats[newFormat as keyof typeof yearlessFormats]) { newFormat = yearlessFormats[newFormat as keyof typeof yearlessFormats]; } } - newFormat = dateFormats[newFormat]; + newFormat = options.tsLocaleBasedDateFormats[newFormat]; } const customCalendarOverridesFiscalOffset = _.get( options, @@ -348,22 +388,9 @@ const formatDateTime = ( ).toFormat(newFormat); }; -function getSpecialFormatData(value: any) { - if (value === '{Empty}' || value === '{Null}') { - return value; - } - if (value === null || value === undefined) { - return '{Null}'; - } - if (value === '') { - return '{Empty}'; - } - return null; -} - -function getDayOfWeek(num: any) { +function getDayOfWeek(num: any, weekOfDay: any) { const new_num = num % 7; - return weekdays[new_num]; + return weekOfDay[weekdays[new_num]]; } function getOrdinalSuffixedValue(i: number | string): string { @@ -400,9 +427,9 @@ export function formatDateNum( } if (!value && value !== 0) { - return '{Null}'; + return options.tsLocaleBasedStringsFormats.null_value_placeholder_label; } - const specialVal = getSpecialFormatData(value); + const specialVal = getSpecialFormatData(value, options); if (specialVal) { return specialVal; } @@ -413,34 +440,52 @@ export function formatDateNum( case dateNumTypes.DATE_NUM_ABS_YEAR: return `${value}`; case dateNumTypes.DATE_NUM_DAY_IN_MONTH: - return formatPattern === 'e' + return formatPattern === options.tsDateConstants.day_in_month_format ? `${value}` : `${getOrdinalSuffixedValue(value)} day of month`; case dateNumTypes.DATE_NUM_DAY_IN_QUARTER: - return formatPattern === 'm' + return formatPattern === + options.tsDateConstants.day_in_quarter_format ? `${value}` : `${getOrdinalSuffixedValue(value)} day of quarter`; case dateNumTypes.DATE_NUM_DAY_IN_YEAR: - return formatPattern === 'j' + return formatPattern === options.tsDateConstants.day_in_year_format ? `${value}` : `${getOrdinalSuffixedValue(value)} day of year`; case dateNumTypes.DATE_NUM_DAY_OF_WEEK: - return formatPattern === 'e' + console.log(options); + return formatPattern === options.tsDateConstants.day_of_week_format ? // eslint-disable-next-line @typescript-eslint/no-use-before-define - getDayOfWeek(value) + getDayOfWeek( + value, + options.tsLocaleBasedStringsFormats.weekOfDay, + ) : `${value}`; case dateNumTypes.DATE_NUM_MONTH_IN_QUARTER: - return formatPattern === 'm' + return formatPattern === + options.tsDateConstants.month_in_quarter_format ? `${value}` : `${getOrdinalSuffixedValue(value)} month of quarter`; case dateNumTypes.DATE_NUM_MONTH_IN_YEAR: - return formatPattern === 'm' + return formatPattern === + options.tsDateConstants.month_in_year_format ? // eslint-disable-next-line @typescript-eslint/no-use-before-define - getMonthOfYear(value, options) + getMonthOfYear( + value, + options.quarterStartMonth, + options.tsLocaleBasedStringsFormats.monthOfYear, + ) : `${value}`; + case dateNumTypes.DATE_NUM_QUARTER_IN_YEAR: + // Falcon returns quarter as 1 indexed, which is passed on by + // callosum to blink, so we can use value instead of value + 1. + return assign( + options.tsLocaleBasedStringsFormats.quarter_of_year, + value, + ); case dateNumTypes.DATE_NUM_WEEK_IN_YEAR: // +1 to value as Falcon values start with 0. - return formatPattern === 'V' + return formatPattern === options.tsDateConstants.week_in_year_format ? `${value}` : `${getOrdinalSuffixedValue(value)} week of year`; case dateNumTypes.DATE_NUM_WEEK_IN_QUARTER: @@ -474,21 +519,24 @@ export function formatDate( let formatPattern = format; let newInputDate: any = inputDate; if (newInputDate === undefined || newInputDate === null) { - return '{Null}'; + return options.tsLocaleBasedStringsFormats.null_value_placeholder_label; } if (!formatPattern) { formatPattern = dateFormatPresets.DATE_SHORT; } if ( - newInputDate === '{Null}' || - newInputDate === '{Empty}' || - newInputDate === '{Other}' + newInputDate === + options.tsLocaleBasedStringsFormats.null_value_placeholder_label || + newInputDate === + options.tsLocaleBasedStringsFormats.empty_value_placeholder_label || + newInputDate === + options.tsLocaleBasedStringsFormats.other_value_placeholder_label ) { return newInputDate; } - newInputDate = sanitizeDate(newInputDate, format); + newInputDate = sanitizeDate(newInputDate, format, options); if (_.isNaN(newInputDate)) { - return '{Null}'; + return options.tsLocaleBasedStringsFormats.null_value_placeholder_label; } let epochMillis = newInputDate; if (_.isDate(epochMillis)) { diff --git a/src/utils/date-utils.ts b/src/utils/date-utils.ts index de16543..3652e3b 100644 --- a/src/utils/date-utils.ts +++ b/src/utils/date-utils.ts @@ -18,3 +18,13 @@ export const showDateFinancialYearFormat = (col: ChartColumn) => { return supportedBucketization === currentBucketization; }); }; + +export function getCustomCalendarGuid( + name: string, + datasourceId: string, + datsourceIdToCustomCalendarMap: any, +) { + return datsourceIdToCustomCalendarMap[datasourceId] + ? datsourceIdToCustomCalendarMap[datasourceId][name] + : undefined; +} diff --git a/src/utils/formatting-util.ts b/src/utils/formatting-util.ts index 6d10734..af7b614 100644 --- a/src/utils/formatting-util.ts +++ b/src/utils/formatting-util.ts @@ -22,6 +22,7 @@ import { isDateTimeColumn, } from './date-formatting'; import { + getCustomCalendarGuid, getFormatPatternForBucket, showDateFinancialYearFormat, } from './date-utils'; @@ -70,7 +71,12 @@ function getBaseTypeFormatterInstanceExpensive( } const customCalendarOverridesFiscalOffset = hasCustomCalendar(col) && - getCustomCalendarGuidFromColumn(col) !== 'FISCAL_CALENDER_GUID'; // TODO: replace with guid we get from ts-app; + getCustomCalendarGuidFromColumn(col) !== + getCustomCalendarGuid( + 'fiscal', + options.defaultDataSourceId, + options.calanders, + ); const optionsWithFiscalOffset = { ...options, customCalendarOverridesFiscalOffset: @@ -120,9 +126,9 @@ export const getDataFormatter = ( return getBaseTypeFormatterInstanceExpensive(col, options); }; + export const generateMapOptions = ( - locale: string, - quarterStartMonth: number, + appConfig: any, col: ChartColumn, data: any, ): any => { @@ -141,8 +147,16 @@ export const generateMapOptions = ( } } return { - locale, - quarterStartMonth, + locale: appConfig?.localeOptions?.locale, + quarterStartMonth: appConfig?.localeOptions?.quarterStartMonth, + tsLocaleBasedDateFormats: + appConfig?.dateFormatsConfig?.tsLocaleBasedDateFormats, + tsLocaleBasedStringsFormats: + appConfig?.dateFormatsConfig?.tsLocaleBasedStringsFormats, + tsDateConstants: appConfig?.dateFormatsConfig?.tsDateConstants, + calanders: appConfig?.dateFormatsConfig?.calanders, + defaultDataSourceId: + appConfig?.dateFormatsConfig?.DEFAULT_DATASOURCE_ID, displayToCustomCalendarValueMap: customCalenderMap, }; }; From f2c212dc808770914d922a548d7fbbd507b313e0 Mon Sep 17 00:00:00 2001 From: utkarsh-rai Date: Thu, 7 Nov 2024 15:24:45 +0530 Subject: [PATCH 06/14] SCAL-229204 version bumpping --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 94ea213..d5bad3e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@thoughtspot/ts-chart-sdk", "private": false, - "version": "0.0.2-alpha.20", + "version": "0.0.2-alpha.21", "module": "lib/index", "main": "lib/index", "types": "lib/index", From 2150c1248a8f596f26062512d1f4bb03ef882766 Mon Sep 17 00:00:00 2001 From: utkarsh-rai Date: Thu, 7 Nov 2024 16:11:55 +0530 Subject: [PATCH 07/14] SCAL-229204 added more strict types --- src/types/common.types.ts | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/types/common.types.ts b/src/types/common.types.ts index b4805cc..b4d8e6f 100644 --- a/src/types/common.types.ts +++ b/src/types/common.types.ts @@ -250,6 +250,35 @@ export type ChartSdkCustomStylingConfig = { fontFaces?: Array; }; +export interface DateFormats { + DATE_SHORT: string; + DATE_SHORT_2_DIGIT_YEAR: string; + DATE_SHORT_WITH_HOUR: string; + DATE_SHORT_WITH_HOUR_WITHOUT_YEAR: string; + DATE_SHORT_WITH_HOUR_24: string; + DATE_SHORT_WITH_HOUR_24_WITHOUT_YEAR: string; + DATETIME_SHORT: string; + DATETIME_SHORT_WITHOUT_YEAR: string; + DATETIME_24_SHORT: string; + DATETIME_24_SHORT_WITH_MILLIS: string; + DATETIME_24_SHORT_WITH_MILLIS_WITHOUT_YEAR: string; + DATETIME_SHORT_WITH_SECONDS: string; + DATETIME_SHORT_WITH_SECONDS_WITHOUT_YEAR: string; + DATETIME_SHORT_WITH_MILLIS: string; + DATETIME_SHORT_WITH_MILLIS_WITHOUT_YEAR: string; + QUARTER_WITH_YEAR: string; + QUARTER_WITH_2_DIGIT_YEAR: string; + DEFAULT_TIME_FORMAT: string; + MONTH_WITH_YEAR: string; + MONTH_WITH_DAY_AND_YEAR: string; + MONTH_WITH_2_DIGIT_YEAR: string; + DAY_WITH_MONTH: string; + DAY_WITH_MONTH_NUM: string; + QUARTER: string; + MONTH_ONLY: string; + DATETIME_WITH_SHORT_OFFSET: string; +} + /** * Date Formats Config for Custom Chart SDK * This is used to define the date formats for the custom chart SDK @@ -259,11 +288,11 @@ export type ChartSdkCustomStylingConfig = { */ export type ChartSdkDateFormatsConfig = { - tsLocaleBasedDateFormats?: Record; + tsLocaleBasedDateFormats?: Record; tsLocaleBasedStringsFormats?: Record; tsDateConstants?: Record; calanders?: any; - DEFAULT_DATASOURCE_ID?: any; + defaultDataSourceId?: string; }; export interface AppConfig { From 6434eada003626f5c841c661896ff02b88319ed6 Mon Sep 17 00:00:00 2001 From: utkarsh-rai Date: Thu, 7 Nov 2024 17:35:22 +0530 Subject: [PATCH 08/14] SCAL-229204 comments resolved --- src/types/common.types.ts | 2 +- src/utils/date-formatting.ts | 7 +++-- src/utils/formatting-util.ts | 5 ++-- src/utils/translations/date-formatter.ts | 36 ------------------------ 4 files changed, 9 insertions(+), 41 deletions(-) delete mode 100644 src/utils/translations/date-formatter.ts diff --git a/src/types/common.types.ts b/src/types/common.types.ts index b4d8e6f..5507db3 100644 --- a/src/types/common.types.ts +++ b/src/types/common.types.ts @@ -291,7 +291,7 @@ export type ChartSdkDateFormatsConfig = { tsLocaleBasedDateFormats?: Record; tsLocaleBasedStringsFormats?: Record; tsDateConstants?: Record; - calanders?: any; + tsDefinedCustomCalenders?: any; defaultDataSourceId?: string; }; diff --git a/src/utils/date-formatting.ts b/src/utils/date-formatting.ts index ea46209..b4a1cd4 100644 --- a/src/utils/date-formatting.ts +++ b/src/utils/date-formatting.ts @@ -239,7 +239,10 @@ export function getStartEpoch(date: CustomCalendarDate): number | null { return null; } -export const assign = (quarter_of_year: any, value: any) => { +export const assignQuarterValueToString = ( + quarter_of_year: any, + value: any, +) => { return quarter_of_year.replace(/\{.*?\}/, value); }; @@ -479,7 +482,7 @@ export function formatDateNum( case dateNumTypes.DATE_NUM_QUARTER_IN_YEAR: // Falcon returns quarter as 1 indexed, which is passed on by // callosum to blink, so we can use value instead of value + 1. - return assign( + return assignQuarterValueToString( options.tsLocaleBasedStringsFormats.quarter_of_year, value, ); diff --git a/src/utils/formatting-util.ts b/src/utils/formatting-util.ts index af7b614..b428fea 100644 --- a/src/utils/formatting-util.ts +++ b/src/utils/formatting-util.ts @@ -75,7 +75,7 @@ function getBaseTypeFormatterInstanceExpensive( getCustomCalendarGuid( 'fiscal', options.defaultDataSourceId, - options.calanders, + options.tsDefinedCustomCalenders, ); const optionsWithFiscalOffset = { ...options, @@ -154,7 +154,8 @@ export const generateMapOptions = ( tsLocaleBasedStringsFormats: appConfig?.dateFormatsConfig?.tsLocaleBasedStringsFormats, tsDateConstants: appConfig?.dateFormatsConfig?.tsDateConstants, - calanders: appConfig?.dateFormatsConfig?.calanders, + tsDefinedCustomCalenders: + appConfig?.dateFormatsConfig?.tsDefinedCustomCalenders, defaultDataSourceId: appConfig?.dateFormatsConfig?.DEFAULT_DATASOURCE_ID, displayToCustomCalendarValueMap: customCalenderMap, diff --git a/src/utils/translations/date-formatter.ts b/src/utils/translations/date-formatter.ts deleted file mode 100644 index 0af8af3..0000000 --- a/src/utils/translations/date-formatter.ts +++ /dev/null @@ -1,36 +0,0 @@ -import _ from 'lodash'; - -export const dateFormats: any = { - DATE_SHORT: 'dd/MM/yyyy', - DATE_SHORT_2_DIGIT_YEAR: 'dd/MM/yy', - DATE_SHORT_WITH_HOUR: 'dd/MM/yyyy hh a', - DATE_SHORT_WITH_HOUR_WITHOUT_YEAR: 'dd/MM hh a', - DATE_SHORT_WITH_HOUR_24: 'dd/MM/yy HH', - DATE_SHORT_WITH_HOUR_24_WITHOUT_YEAR: 'dd/MM HH', - DATETIME_SHORT: 'dd/MM/yyyy hh:mm a', - DATETIME_SHORT_WITHOUT_YEAR: 'dd/MM hh:mm a', - DATETIME_24_SHORT: 'dd/MM/yy HH:mm', - DATETIME_24_SHORT_WITH_MILLIS: 'dd/MM/yyyy HH:mm:ss.S', - DATETIME_24_SHORT_WITH_MILLIS_WITHOUT_YEAR: 'dd/MM HH:mm:ss.S', - DATETIME_SHORT_WITH_SECONDS: 'dd/MM/yyyy HH:mm:ss', - DATETIME_SHORT_WITH_SECONDS_WITHOUT_YEAR: 'dd/MM HH:mm:ss', - DATETIME_SHORT_WITH_MILLIS: 'MM/dd/yyyy HH:mm:ss.S', - DATETIME_SHORT_WITH_MILLIS_WITHOUT_YEAR: 'MM/dd HH:mm:ss.S', - QUARTER_WITH_YEAR: "'Q'q yyyy", - QUARTER_WITH_2_DIGIT_YEAR: "'Q'q yy", - DEFAULT_TIME_FORMAT: 'dd MMM, yyyy hh:mm:ss a ZZZZ', - MONTH_WITH_YEAR: 'MMM yyyy', - MONTH_WITH_DAY_AND_YEAR: 'dd MMM, yyyy', - MONTH_WITH_2_DIGIT_YEAR: 'MMM yy', - DAY_WITH_MONTH: 'dd MMM', - DAY_WITH_MONTH_NUM: 'dd/MM', - QUARTER: "'Q'q", - MONTH_ONLY: 'MMM', - DATETIME_WITH_SHORT_OFFSET: 'dd/MM/yyyy HH:mm:ss ZZZZ', -}; - -// TODO: discuss the current working. - -export function updateDateFormats(obj: any) { - _.merge(dateFormats, obj); -} From cd816a558f965005f3c932d48b8825935d38a3dd Mon Sep 17 00:00:00 2001 From: utkarsh-rai Date: Fri, 8 Nov 2024 17:30:52 +0530 Subject: [PATCH 09/14] SCAL-229204 added test for utils --- src/utils/date-formatting.spec.ts | 407 +++++++++++++++++++++++++++--- src/utils/date-formatting.ts | 44 +--- src/utils/date-utils.spec.ts | 93 +++++++ src/utils/formatting-util.spec.ts | 325 ++++++++++++++++++++++++ src/utils/formatting-util.ts | 16 +- 5 files changed, 814 insertions(+), 71 deletions(-) create mode 100644 src/utils/date-utils.spec.ts create mode 100644 src/utils/formatting-util.spec.ts diff --git a/src/utils/date-formatting.spec.ts b/src/utils/date-formatting.spec.ts index ddf0e86..601ae96 100644 --- a/src/utils/date-formatting.spec.ts +++ b/src/utils/date-formatting.spec.ts @@ -6,6 +6,7 @@ * Copyright: ThoughtSpot Inc. 2023 */ +import { DateTime } from 'luxon'; import { ChartColumn, ChartSpecificColumnType, @@ -14,18 +15,31 @@ import { DataType, } from '../types/answer-column.types'; import { + assignQuarterValueToString, CustomCalendarDate, - dateFormatter, + dateNumTypes, + formatDate, + formatDateNum, + formatDateTime, getCustomCalendarGuidFromColumn, + getCustomCalendarValueFromEpoch, getDisplayString, + getEffectiveDateNumDataType, + getMonthOfYear, + getOrdinalSuffixedValue, + getSpecialFormatData, getStartEpoch, hasCustomCalendar, isAttribute, isDateColumn, isDateFamilyColumn, isDateNumColumn, + isDateTimeColumn, isTimeColumn, + sanitizeDate, + useQuarterStart, } from './date-formatting'; +import * as DateFormatting from './date-formatting'; describe('date-formatting', () => { let col: ChartColumn; @@ -40,6 +54,60 @@ describe('date-formatting', () => { calenderGuid: '12345', }; }); + const options = { + quarterStartMonth: 1, + tsLocaleBasedStringsFormats: { + null_value_placeholder_label: '{Null}', + empty_value_placeholder_label: '{Empty}', + other_value_placeholder_label: '{Other}', + unavailabe_column_sample_value: '{Unavailable}', + weekOfDay: { + Friday: 'Friday', + Monday: 'Monday', + Saturday: 'Saturday', + Sunday: 'Sunday', + Thursday: 'Thursday', + Tuesday: 'Tuesday', + Wednesday: 'Wednesday', + }, + monthOfYear: { + April: 'April', + August: 'August', + December: 'December', + February: 'February', + January: 'January', + July: 'July', + June: 'June', + March: 'March', + May: 'May', + November: 'November', + October: 'October', + September: 'September', + }, + quarter_of_year: 'Q{1}', + }, + tsDateConstants: { + day_in_month_format: 'e', + day_in_quarter_format: 'e', + day_in_year_format: 'j', + day_of_week_format: 'e', + month_in_quarter_format: 'm', + month_in_year_format: 'm', + special_value_unavailable: 'N/A', + week_in_year_format: 'V', + }, + tsLocaleBasedDateFormats: { + DATE_SHORT: 'MM/dd/yyyy', + }, + quarter_of_year: 'Q{1}', + tsDefinedCustomCalenders: { + '43121d86-347a-4dbb-bea8-5e5bb899e427': { + calendar: '7573c08b-753b-478b-84fd-6e702d481ff6', + fiscal: 'bfa39848-ba4f-46d8-80fd-b695064e61b7', + french: 'a7316e8d-d4dd-4eaf-9294-396db951b422', + }, + }, + }; test('isDateColumn should return true for DATE data type', () => { col.dataType = DataType.DATE; expect(isDateColumn(col)).toBe(true); @@ -124,45 +192,320 @@ describe('date-formatting', () => { expect(getDisplayString(date)).toBe(null); }); - test('dateFormatter should return custom calendar display string if present', () => { - const dataValue = { - v: { s: 1625097600, e: 1625184000 }, - d: '01-07-2021', - } as CustomCalendarDate; + test('should return false for columns with dataType other than DATE_TIME', () => { col.dataType = DataType.DATE; - col.calenderGuid = '12345'; - expect(dateFormatter(dataValue, col)).toBe('01-07-2021'); + expect(isDateTimeColumn(col)).toBe(false); + col.dataType = DataType.INT64; + expect(isDateTimeColumn(col)).toBe(false); + col.dataType = DataType.DATE_TIME; + expect(isDateTimeColumn(col)).toBe(true); + }); + test('should return DATE_NUM_DAY_OF_WEEK for ColumnTimeBucket.DAY_OF_WEEK', () => { + const column = { + timeBucket: ColumnTimeBucket.DAY_OF_WEEK, + } as ChartColumn; + expect(getEffectiveDateNumDataType(column)).toBe( + dateNumTypes.DATE_NUM_DAY_OF_WEEK, + ); }); - test('dateFormatter should return formatted date from start epoch if custom calendar display string is not present', () => { - const dataValue = { - v: { s: 1625097600, e: 1625184000 }, - } as CustomCalendarDate; - col.dataType = DataType.DATE; - col.calenderGuid = '12345'; - expect(dateFormatter(dataValue, col)).toBe('01-07-2021'); + test('should return DATE_NUM_DAY_IN_MONTH for ColumnTimeBucket.DAY_OF_MONTH', () => { + const column = { + timeBucket: ColumnTimeBucket.DAY_OF_MONTH, + } as ChartColumn; + expect(getEffectiveDateNumDataType(column)).toBe( + dateNumTypes.DATE_NUM_DAY_IN_MONTH, + ); }); - test('dateFormatter should return null if start epoch is not present', () => { - const dataValue = { v: { e: 1625184000 } } as CustomCalendarDate; - col.dataType = DataType.DATE; - col.calenderGuid = '12345'; - expect(dateFormatter(dataValue, col)).toBe(null); + test('should return DATE_NUM_DAY_IN_QUARTER for ColumnTimeBucket.DAY_OF_QUARTER', () => { + const column = { + timeBucket: ColumnTimeBucket.DAY_OF_QUARTER, + } as ChartColumn; + expect(getEffectiveDateNumDataType(column)).toBe( + dateNumTypes.DATE_NUM_DAY_IN_QUARTER, + ); }); - test('dateFormatter should return formatted date for non-custom calendar column', () => { - const dataValue = 1625097600; - col.dataType = DataType.DATE; - col.calenderGuid = undefined; - expect(dateFormatter(dataValue, col)).toBe('01-07-2021'); + test('should return DATE_NUM_DAY_IN_YEAR for ColumnTimeBucket.DAY_OF_YEAR', () => { + const column = { + timeBucket: ColumnTimeBucket.DAY_OF_YEAR, + } as ChartColumn; + expect(getEffectiveDateNumDataType(column)).toBe( + dateNumTypes.DATE_NUM_DAY_IN_YEAR, + ); + }); + + test('should return DATE_NUM_WEEK_IN_MONTH for ColumnTimeBucket.WEEK_OF_MONTH', () => { + const column = { + timeBucket: ColumnTimeBucket.WEEK_OF_MONTH, + } as ChartColumn; + expect(getEffectiveDateNumDataType(column)).toBe( + dateNumTypes.DATE_NUM_WEEK_IN_MONTH, + ); + }); + + test('should return DATE_NUM_WEEK_IN_QUARTER for ColumnTimeBucket.WEEK_OF_QUARTER', () => { + const column = { + timeBucket: ColumnTimeBucket.WEEK_OF_QUARTER, + } as ChartColumn; + expect(getEffectiveDateNumDataType(column)).toBe( + dateNumTypes.DATE_NUM_WEEK_IN_QUARTER, + ); + }); + + test('should return DATE_NUM_WEEK_IN_YEAR for ColumnTimeBucket.WEEK_OF_YEAR', () => { + const column = { + timeBucket: ColumnTimeBucket.WEEK_OF_YEAR, + } as ChartColumn; + expect(getEffectiveDateNumDataType(column)).toBe( + dateNumTypes.DATE_NUM_WEEK_IN_YEAR, + ); }); - test('dateFormatter should format the date based on custom calendar', () => { + + test('should return DATE_NUM_MONTH_IN_QUARTER for ColumnTimeBucket.MONTH_OF_QUARTER', () => { + const column = { + timeBucket: ColumnTimeBucket.MONTH_OF_QUARTER, + } as ChartColumn; + expect(getEffectiveDateNumDataType(column)).toBe( + dateNumTypes.DATE_NUM_MONTH_IN_QUARTER, + ); + }); + + test('should return DATE_NUM_MONTH_IN_YEAR for ColumnTimeBucket.MONTH_OF_YEAR', () => { + const column = { + timeBucket: ColumnTimeBucket.MONTH_OF_YEAR, + } as ChartColumn; + expect(getEffectiveDateNumDataType(column)).toBe( + dateNumTypes.DATE_NUM_MONTH_IN_YEAR, + ); + }); + + test('should return DATE_NUM_QUARTER_IN_YEAR for ColumnTimeBucket.QUARTER_OF_YEAR', () => { + const column = { + timeBucket: ColumnTimeBucket.QUARTER_OF_YEAR, + } as ChartColumn; + expect(getEffectiveDateNumDataType(column)).toBe( + dateNumTypes.DATE_NUM_QUARTER_IN_YEAR, + ); + }); + test('should replace placeholder with value', () => { + const result = assignQuarterValueToString('Q{1}', '1'); + expect(result).toBe('Q1'); + }); + test('should return correct month for normal case', () => { + const monthOfYear = { + January: 'Jan', + February: 'Feb', + March: 'Mar', + April: 'Apr', + May: 'May', + June: 'Jun', + July: 'Jul', + August: 'Aug', + September: 'Sep', + October: 'oct', + November: 'Nov', + December: 'Dec', + }; + const result = getMonthOfYear(1, 1, monthOfYear); + expect(result).toBe('Jan'); + }); + test('should return the value from displayToCustomCalendarValueMap if custom calendar exists and dateEpoch is present', () => { + const dateEpoch = 1234567890; + const displayToCustomCalendarValueMap = { + 1234567890: { + v: { + s: '1234567890', + e: '1234567890', + }, + d: 'Custom Date Value', + }, + }; col.dataType = DataType.DATE; - col.calenderGuid = '12345'; - const date = { - v: { s: 12345, e: 12346 }, - d: 'Custom_Date', - } as CustomCalendarDate; - expect(dateFormatter(date, col)).toBe(date.d); + const result = getCustomCalendarValueFromEpoch( + col, + dateEpoch, + displayToCustomCalendarValueMap, + ); + expect(result).toEqual({ + v: { s: '1234567890', e: '1234567890' }, + d: 'Custom Date Value', + }); + }); + test('should return null_value_placeholder_label if value matches null_value_placeholder_label', () => { + const result = getSpecialFormatData('{Null}', options); + expect(result).toBe('{Null}'); + }); + + test('should return empty_value_placeholder_label if value matches empty_value_placeholder_label', () => { + const result = getSpecialFormatData('{Empty}', options); + expect(result).toBe('{Empty}'); + }); + + test('should return unavailabe_column_sample_value if value matches special_value_unavailable', () => { + const result = getSpecialFormatData('N/A', options); + expect(result).toBe('{Unavailable}'); + }); + + test('should return empty_value_placeholder_label if value is an empty string', () => { + const result = getSpecialFormatData('', options); + expect(result).toBe('{Empty}'); + }); + + test('should return null if value does not match any special condtestion', () => { + const result = getSpecialFormatData('Some other value', options); + expect(result).toBeNull(); + }); + test('should return special value if getSpecialFormatData provides it', () => { + const result = sanitizeDate('{Null}', 'yyyy', options); + expect(result).toBe('{Null}'); + }); + test('should return parsed integer if inputDate is a numeric string', () => { + const result = sanitizeDate('12345', 'MM/dd/yyyy', options); + expect(result).toBe(12345); + }); + test('should parse date if inputDate is a non-numeric string', () => { + const result = sanitizeDate('12/25/2022', 'DATE_SHORT', options); + expect(result).toEqual(new Date(2022, 11, 25)); + }); + test('should return "Invalid Date" for an invalid epochMillis', () => { + const result = formatDateTime(NaN, 'MM-dd-yyyy', false, options); + expect(result).toBe('Invalid DateTime'); + }); + test('should return a formatted date string for a valid epochMillis with default options', () => { + const epochMillis = 1043452800; // January 25, 2003 + const expectedDate = '01-25-2003'; + const luxonDate = DateTime.fromMillis(epochMillis * 1000); + + jest.spyOn(DateTime, 'fromMillis').mockReturnValue(luxonDate); + jest.spyOn(luxonDate, 'toLocaleString').mockReturnValue(expectedDate); + + const result = formatDateTime( + epochMillis, + 'MM-dd-yyyy', + false, + options, + ); + expect(result).toBe(expectedDate); + }); + test('should apply fiscal year format when quarterStartMonth is not default and useSystemCalendar is false', () => { + const epochMillis = 1043452800; // January 25, 2003 + options.quarterStartMonth = 4; // Custom fiscal year + const expectedDate = 'FY 2003'; + const luxonDate = DateTime.fromMillis(epochMillis * 1000); + jest.spyOn(DateFormatting, 'useQuarterStart').mockReturnValue( + luxonDate, + ); + jest.spyOn(luxonDate, 'toFormat').mockReturnValue(expectedDate); + const result = formatDateTime(epochMillis, 'yyyy', false, options); + expect(result).toBe(expectedDate); + expect(useQuarterStart).toHaveBeenCalledWith( + luxonDate, + options.quarterStartMonth, + ); + }); + test('should replace unsupported "qqq" with fiscal quarter format', () => { + const epochMillis = 1043452800; // January 25, 2003 + const format = 'qqq yyyy'; + const expectedFormat = "'Q'q yyyy"; + const luxonDate = DateTime.fromMillis(epochMillis * 1000); + jest.spyOn(DateFormatting, 'useQuarterStart').mockReturnValue( + luxonDate, + ); + jest.spyOn(luxonDate, 'toFormat').mockReturnValue('Q1 2003'); + + const result = formatDateTime(epochMillis, format, true, options); + expect(result).toBe('Q1 2003'); + expect(luxonDate.toFormat).toHaveBeenCalledWith(expectedFormat); + }); + test('should return the correct ordinal suffix for numbers ending in 1', () => { + expect(getOrdinalSuffixedValue(1)).toBe('1st'); + expect(getOrdinalSuffixedValue(21)).toBe('21st'); + expect(getOrdinalSuffixedValue('1')).toBe('1st'); // Test with string input + expect(getOrdinalSuffixedValue('21')).toBe('21st'); + }); + + test('should return the correct ordinal suffix for numbers ending in 2', () => { + expect(getOrdinalSuffixedValue(2)).toBe('2nd'); + expect(getOrdinalSuffixedValue(22)).toBe('22nd'); + expect(getOrdinalSuffixedValue('2')).toBe('2nd'); // Test with string input + expect(getOrdinalSuffixedValue('22')).toBe('22nd'); + }); + + test('should return the correct ordinal suffix for numbers ending in 3', () => { + expect(getOrdinalSuffixedValue(3)).toBe('3rd'); + expect(getOrdinalSuffixedValue(23)).toBe('23rd'); + expect(getOrdinalSuffixedValue('3')).toBe('3rd'); // Test with string input + expect(getOrdinalSuffixedValue('23')).toBe('23rd'); + }); + + test('should return the correct ordinal suffix for all other numbers', () => { + expect(getOrdinalSuffixedValue(4)).toBe('4th'); + expect(getOrdinalSuffixedValue(11)).toBe('11th'); + expect(getOrdinalSuffixedValue(100)).toBe('100th'); + expect(getOrdinalSuffixedValue(112)).toBe('112th'); + expect(getOrdinalSuffixedValue('4')).toBe('4th'); + expect(getOrdinalSuffixedValue('11')).toBe('11th'); + expect(getOrdinalSuffixedValue('100')).toBe('100th'); + expect(getOrdinalSuffixedValue('112')).toBe('112th'); + }); + + test('should handle negative numbers correctly', () => { + expect(getOrdinalSuffixedValue(-1)).toBe('-1th'); + expect(getOrdinalSuffixedValue(-11)).toBe('-11th'); + expect(getOrdinalSuffixedValue('-1')).toBe('-1th'); + expect(getOrdinalSuffixedValue('-11')).toBe('-11th'); + }); + test('should return special format data if applicable', () => { + jest.spyOn(DateFormatting, 'getSpecialFormatData').mockReturnValue( + 'Special Format', + ); + const result = formatDateNum(undefined, '{Null}', '', options); + expect(result).toBe('{Null}'); + }); + test('should return value as string for absolute day, month, quarter, and year types', () => { + const result = formatDateNum('DATE_NUM_ABS_DAY', 123, '', options); + expect(result).toBe('123'); + }); + test("should return ordinal suffixed value for day of month when format doesn't match", () => { + const result = formatDateNum('DATE_NUM_DAY_IN_MONTH', 2, '', options); + expect(result).toBe('2nd day of month'); + }); + test('should return formatted date for day of week', () => { + const result = formatDateNum('DATE_NUM_DAY_OF_WEEK', 1, 'e', options); + expect(result).toBe('Monday'); + }); + test('should return formatted month name for month in year', () => { + const result = formatDateNum('DATE_NUM_MONTH_IN_YEAR', 1, 'm', options); + expect(result).toBe('April'); + }); + test('should return ordinal suffixed value for week of year', () => { + const result = formatDateNum('DATE_NUM_WEEK_IN_YEAR', 2, '', options); + expect(result).toBe('2nd week of year'); + }); + test('should handle special cases for quarter value', () => { + const result = formatDateNum( + 'DATE_NUM_QUARTER_IN_YEAR', + 1, + '', + options, + ); + expect(result).toBe('Q1'); + }); + test('should return value for unhandled cases', () => { + const result = formatDateNum('UNKNOWN_TYPE', 999, '', options); + expect(result).toBe(999); + }); + test('should return special placeholder values if input matches', () => { + const resultEmpty = formatDate('{Empty}', 'DD-MM-YYYY', true, options); + expect(resultEmpty).toBe('{Empty}'); + + const resultOther = formatDate('{Other}', 'DD-MM-YYYY', true, options); + expect(resultOther).toBe('{Other}'); + }); + it('should return null placeholder if the sanitized date is invalid', () => { + const result = formatDate('{Null}', 'DD-MM-YYYY', true, options); + expect(result).toBe('{Null}'); }); }); diff --git a/src/utils/date-formatting.ts b/src/utils/date-formatting.ts index b4a1cd4..1fa647c 100644 --- a/src/utils/date-formatting.ts +++ b/src/utils/date-formatting.ts @@ -14,7 +14,6 @@ import { ColumnType, DataType, } from '../types/answer-column.types'; -import { dateFormats } from './translations/date-formatter'; export interface CustomCalendarDate { v: { @@ -74,7 +73,7 @@ const yearlessFormats = { DATE_SHORT_WITH_HOUR_24: 'DATE_SHORT_WITH_HOUR_24_WITHOUT_YEAR', }; -const dateNumTypes = { +export const dateNumTypes = { DATE_NUM_ABS_DAY: 'DATE_NUM_ABS_DAY', DATE_NUM_ABS_MONTH: 'DATE_NUM_ABS_MONTH', DATE_NUM_ABS_QUARTER: 'DATE_NUM_ABS_QUARTER', @@ -246,7 +245,11 @@ export const assignQuarterValueToString = ( return quarter_of_year.replace(/\{.*?\}/, value); }; -function getMonthOfYear(num: any, quarterStartMonth: any, monthOfYear: any) { +export function getMonthOfYear( + num: any, + quarterStartMonth: any, + monthOfYear: any, +) { let monthNum = num + quarterStartMonth - 1; monthNum = monthNum > 12 ? monthNum - 12 : monthNum; @@ -260,7 +263,7 @@ export function getDisplayString(date: CustomCalendarDate): string | null { return null; } -const useQuarterStart = (luxonDate: any, quarterStartMonth: any) => { +export const useQuarterStart = (luxonDate: any, quarterStartMonth: any) => { const newLuxonDate = luxonDate; newLuxonDate.quarterStartMonth = quarterStartMonth; return newLuxonDate; @@ -279,10 +282,10 @@ export const getCustomCalendarValueFromEpoch = ( } return null; }; -const parseDate = (dateString: string, format: string) => { +const parseDate = (dateString: string, format: string, options: any) => { return DateTime.fromFormat( dateString, - dateFormats[format] || format, + options.tsLocaleBasedDateFormats[format] || format, ).toJSDate(); }; @@ -314,7 +317,7 @@ export function getSpecialFormatData(value: string | number, options: any) { return null; } -function sanitizeDate( +export function sanitizeDate( inputDate: string | number, format: string, options: any, @@ -327,7 +330,7 @@ function sanitizeDate( if (!_.isNaN(Number(inputDate))) { return parseInt(inputDate, 10); } - return parseDate(inputDate, format); + return parseDate(inputDate, format, options); } return inputDate; } @@ -339,7 +342,7 @@ function sanitizeDate( * or pass the format pattern for non localized results * @returns {string} */ -const formatDateTime = ( +export const formatDateTime = ( epochMillis: number, format: string, useSystemCalendar?: boolean, @@ -396,7 +399,7 @@ function getDayOfWeek(num: any, weekOfDay: any) { return weekOfDay[weekdays[new_num]]; } -function getOrdinalSuffixedValue(i: number | string): string { +export function getOrdinalSuffixedValue(i: number | string): string { let ni = i; // eslint-disable-next-line radix ni = parseInt(ni.toString()); @@ -456,7 +459,6 @@ export function formatDateNum( ? `${value}` : `${getOrdinalSuffixedValue(value)} day of year`; case dateNumTypes.DATE_NUM_DAY_OF_WEEK: - console.log(options); return formatPattern === options.tsDateConstants.day_of_week_format ? // eslint-disable-next-line @typescript-eslint/no-use-before-define getDayOfWeek( @@ -554,23 +556,3 @@ export function formatDate( } return formatDateTime(epochMillis, format, useSystemCalendar, options); } - -/** - * Formats the date value based on the column's properties and custom calendar settings. - * - * @param dataValue - The date value to format. - * @param col - The chart column. - * @returns The formatted date string. - */ -export function dateFormatter(dataValue: any, col: ChartColumn) { - if (hasCustomCalendar(col)) { - if (getDisplayString(dataValue)) { - return getDisplayString(dataValue); - } - const startEpoch = getStartEpoch(dataValue); - return startEpoch !== null - ? DateTime.fromMillis(startEpoch * 1000).toFormat('dd-MM-yyyy') - : null; - } - return DateTime.fromMillis(dataValue * 1000).toFormat('dd-MM-yyyy'); -} diff --git a/src/utils/date-utils.spec.ts b/src/utils/date-utils.spec.ts new file mode 100644 index 0000000..61e34dd --- /dev/null +++ b/src/utils/date-utils.spec.ts @@ -0,0 +1,93 @@ +import { ChartColumn, ColumnTimeBucket } from '../types/answer-column.types'; +import { + getCustomCalendarGuid, + getFormatPatternForBucket, + showDateFinancialYearFormat, +} from './date-utils'; // Adjust path + +describe('getFormatPatternForBucket', () => { + const mockDatasourceIdToCustomCalendarMap = { + datasource1: { + fiscal: 'guid-123', + custom: 'guid-456', + }, + datasource2: { + fiscal: 'guid-789', + }, + }; + + test('should return correct format pattern for YEARLY bucket', () => { + const result = getFormatPatternForBucket(ColumnTimeBucket.YEARLY); + expect(result).toBe('yyyy'); + }); + + test('should return correct format pattern for MONTHLY bucket', () => { + const result = getFormatPatternForBucket(ColumnTimeBucket.MONTHLY); + expect(result).toBe('MONTH_WITH_YEAR'); + }); + + test('should return correct format pattern for WEEKLY bucket', () => { + const result = getFormatPatternForBucket(ColumnTimeBucket.WEEKLY); + expect(result).toBe('DATE_SHORT'); + }); + + test('should handle undefined input gracefully', () => { + const result = getFormatPatternForBucket(undefined as any); + expect(result).toBeUndefined(); + }); + + test('should return true for QUARTERLY time bucket', () => { + const col = { timeBucket: ColumnTimeBucket.QUARTERLY } as ChartColumn; + const result = showDateFinancialYearFormat(col); + + expect(result).toBe(true); + }); + + test('should return true for YEARLY time bucket', () => { + const col = { timeBucket: ColumnTimeBucket.YEARLY } as ChartColumn; + const result = showDateFinancialYearFormat(col); + expect(result).toBe(true); + }); + + test('should return true for MONTLY time bucket', () => { + const col = { timeBucket: ColumnTimeBucket.MONTHLY } as ChartColumn; + const result = showDateFinancialYearFormat(col); + expect(result).toBe(false); + }); + test('should return the custom calendar GUID when datasourceId and name are valid', () => { + const datasourceId = 'datasource1'; + const name = 'fiscal'; + + const result = getCustomCalendarGuid( + name, + datasourceId, + mockDatasourceIdToCustomCalendarMap, + ); + + expect(result).toBe('guid-123'); + }); + test('should return undefined when the datasourceId is valid but the name does not exist', () => { + const datasourceId = 'datasource1'; + const name = 'nonexistent'; + + const result = getCustomCalendarGuid( + name, + datasourceId, + mockDatasourceIdToCustomCalendarMap, + ); + + expect(result).toBeUndefined(); + }); + test('should return undefined when the datasourceId does not exist in the map', () => { + const datasourceId = 'nonexistentDatasource'; + const name = 'fiscal'; + + const result = getCustomCalendarGuid( + name, + datasourceId, + mockDatasourceIdToCustomCalendarMap, + ); + + expect(result).toBeUndefined(); + }); +}); diff --git a/src/utils/formatting-util.spec.ts b/src/utils/formatting-util.spec.ts new file mode 100644 index 0000000..3e94138 --- /dev/null +++ b/src/utils/formatting-util.spec.ts @@ -0,0 +1,325 @@ +import { + ChartColumn, + ChartSpecificColumnType, + ColumnTimeBucket, + ColumnType, + DataType, +} from '../types/answer-column.types'; +import * as DateFormatting from './date-formatting'; +import { + getFormatPatternForBucket, + showDateFinancialYearFormat, +} from './date-utils'; +import { + generateMapOptions, + getBaseTypeFormatterInstanceExpensive, + getDataFormatter, + getFormatPattern, +} from './formatting-util'; + +jest.mock('./date-utils', () => ({ + getFormatPatternForBucket: jest.fn(), + showDateFinancialYearFormat: jest.fn(), + getCustomCalendarGuid: jest + .fn() + .mockReturnValue('7573c08b-753b-478b-84fd-6e702d481ff6'), +})); + +describe('formatting utils', () => { + const mockOptions = { + quarterStartMonth: 1, + tsLocaleBasedStringsFormats: { + null_value_placeholder_label: '{Null}', + empty_value_placeholder_label: '{Empty}', + other_value_placeholder_label: '{Other}', + unavailabe_column_sample_value: '{Unavailable}', + weekOfDay: { + Friday: 'Friday', + Monday: 'Monday', + Saturday: 'Saturday', + Sunday: 'Sunday', + Thursday: 'Thursday', + Tuesday: 'Tuesday', + Wednesday: 'Wednesday', + }, + monthOfYear: { + April: 'April', + August: 'August', + December: 'December', + February: 'February', + January: 'January', + July: 'July', + June: 'June', + March: 'March', + May: 'May', + November: 'November', + October: 'October', + September: 'September', + }, + quarter_of_year: 'Q{1}', + }, + tsDateConstants: { + day_in_month_format: 'e', + day_in_quarter_format: 'e', + day_in_year_format: 'j', + day_of_week_format: 'e', + month_in_quarter_format: 'm', + month_in_year_format: 'm', + special_value_unavailable: 'N/A', + week_in_year_format: 'V', + }, + tsLocaleBasedDateFormats: { + DATE_SHORT: 'MM/dd/yyyy', + }, + quarter_of_year: 'Q{1}', + tsDefinedCustomCalenders: { + '43121d86-347a-4dbb-bea8-5e5bb899e427': { + calendar: '7573c08b-753b-478b-84fd-6e702d481ff6', + fiscal: 'bfa39848-ba4f-46d8-80fd-b695064e61b7', + french: 'a7316e8d-d4dd-4eaf-9294-396db951b422', + }, + }, + displayToCustomCalendarValueMap: { + '1234567891': { + v: { + s: 'start', + e: 'end', + }, + d: 'custom Date', + }, + '1234567892': { + v: { + s: '1234567890', + e: 'end', + }, + }, + }, + }; + test('getFormatPattern should call getFormatPatternForBucket with bucket and return pattern', () => { + const column = { + id: 'testId', + name: 'test', + type: ColumnType.MEASURE, + chartSpecificColumnType: ChartSpecificColumnType.UNKNOWN, + timeBucket: ColumnTimeBucket.YEARLY, + dataType: DataType.DATE, + calenderGuid: '12345', + } as ChartColumn; + + expect(getFormatPattern(column)).toBe(undefined); + expect(getFormatPatternForBucket).toHaveBeenCalledWith( + column.timeBucket, + ); + }); + test('should return date formatter if column is date', () => { + const column = { + id: 'testId', + name: 'test', + type: ColumnType.MEASURE, + chartSpecificColumnType: ChartSpecificColumnType.UNKNOWN, + timeBucket: ColumnTimeBucket.YEARLY, + dataType: DataType.DATE, + calenderGuid: '12345', + } as ChartColumn; + const options = { isMillisIncluded: true }; + + const formatter = getBaseTypeFormatterInstanceExpensive( + column, + options, + ); + + expect(typeof formatter).toBe('function'); + const formattedDate = formatter(1234567890, mockOptions); + expect(formattedDate).toBe('2/14/2009'); + const customFormattedDate = formatter(1234567891, mockOptions); + const customFormattedDateWithoutDisplayValue = formatter( + 1234567892, + mockOptions, + ); + expect(customFormattedDateWithoutDisplayValue).toBe('2/14/2009'); + expect(customFormattedDate).toBe('custom Date'); + expect(showDateFinancialYearFormat).toHaveBeenCalledWith(column); + }); + test('should return dateNum formatter if column is dateNum', () => { + const column = { + id: 'testId', + name: 'test', + type: ColumnType.ATTRIBUTE, + chartSpecificColumnType: ChartSpecificColumnType.UNKNOWN, + timeBucket: ColumnTimeBucket.HOUR_OF_DAY, + dataType: DataType.INT64, + calenderGuid: '12345', + } as ChartColumn; + const options = { isMillisIncluded: true }; + const formatter = getBaseTypeFormatterInstanceExpensive( + column, + options, + ); + + expect(typeof formatter).toBe('function'); + const formattedDate = formatter(1234567890, mockOptions); + expect(formattedDate).toBe(1234567890); + const customFormattedDate = formatter(1234567891, mockOptions); + const customFormattedDateWithoutDisplayValue = formatter( + 1234567892, + mockOptions, + ); + expect(customFormattedDateWithoutDisplayValue).toBe(1234567892); + expect(customFormattedDate).toBe('custom Date'); + }); + test('should return a default formatter for non-date types', () => { + const column = { + id: 'testId', + name: 'test', + type: ColumnType.MEASURE, + chartSpecificColumnType: ChartSpecificColumnType.UNKNOWN, + timeBucket: ColumnTimeBucket.HOUR_OF_DAY, + dataType: DataType.INT64, + calenderGuid: '12345', + } as ChartColumn; + const options = { isMillisIncluded: true }; + const formatter = getBaseTypeFormatterInstanceExpensive( + column, + options, + ); + + expect(formatter('test')).toBe('test'); + }); + test('getDataFormatter should return appropriate formatter based on column type', () => { + const column = { + id: 'testId', + name: 'test', + type: ColumnType.ATTRIBUTE, + chartSpecificColumnType: ChartSpecificColumnType.UNKNOWN, + timeBucket: ColumnTimeBucket.YEARLY, + dataType: DataType.DATE, + calenderGuid: '12345', + } as ChartColumn; + const options = { isMillisIncluded: true }; + + const formatter = getDataFormatter(column, options); + + expect(typeof formatter).toBe('function'); + const formattedDate = formatter(1234567890, mockOptions); + expect(formattedDate).toBe('2/14/2009'); + const customFormattedDate = formatter(1234567891, mockOptions); + const customFormattedDateWithoutDisplayValue = formatter( + 1234567892, + mockOptions, + ); + expect(customFormattedDateWithoutDisplayValue).toBe('2/14/2009'); + expect(customFormattedDate).toBe('custom Date'); + expect(showDateFinancialYearFormat).toHaveBeenCalledWith(column); + }); + test('generateMapOptions should return map options based on appConfig and column', () => { + const column = { + id: 'testId', + name: 'test', + type: ColumnType.ATTRIBUTE, + chartSpecificColumnType: ChartSpecificColumnType.UNKNOWN, + timeBucket: ColumnTimeBucket.YEARLY, + dataType: DataType.DATE, + calenderGuid: '12345', + } as ChartColumn; + + const appConfig = { + localeOptions: { locale: 'en-US', quarterStartMonth: 1 }, + dateFormatsConfig: { + tsLocaleBasedDateFormats: 'MM-DD-YYYY', + tsLocaleBasedStringsFormats: 'YYYY/MM/DD', + tsDateConstants: {}, + tsDefinedCustomCalenders: {}, + DEFAULT_DATASOURCE_ID: 'source1', + }, + }; + const data = [{ v: { s: 'key1' } }, { v: { s: 'key2' } }]; + + const mapOptions = generateMapOptions(appConfig, column, data); + + expect(mapOptions.locale).toBe('en-US'); + expect(mapOptions.quarterStartMonth).toBe(1); + expect(mapOptions.displayToCustomCalendarValueMap).toEqual({ + key1: data[0], + key2: data[1], + }); + }); + test('should have format pattern as DATETIME_SHORT_WITH_MILLIS when format pattern is null', () => { + const column = { + id: 'testId', + name: 'test', + type: ColumnType.VIRTUAL, + chartSpecificColumnType: ChartSpecificColumnType.UNKNOWN, + timeBucket: ColumnTimeBucket.MONTHLY, + dataType: DataType.DATE_TIME, + calenderGuid: '12345', + } as ChartColumn; + const options = { isMillisIncluded: true }; + const formatter = getBaseTypeFormatterInstanceExpensive( + column, + options, + ); + expect(typeof formatter).toBe('function'); + }); + test('should correctly set format pattern for date-time column with and without milliseconds', () => { + jest.spyOn(DateFormatting, 'formatDate').mockImplementation(jest.fn()); + const column = { + id: 'testId', + name: 'test', + type: ColumnType.MEASURE, + chartSpecificColumnType: ChartSpecificColumnType.UNKNOWN, + timeBucket: ColumnTimeBucket.MONTHLY, + dataType: DataType.DATE_TIME, // Indicates it's a date-time column + calenderGuid: '12345', + } as ChartColumn; + + const optionsWithMillis = { isMillisIncluded: true }; + const optionsWithoutMillis = { isMillisIncluded: false }; + + const formatterWithMillis = getBaseTypeFormatterInstanceExpensive( + column, + optionsWithMillis, + ); + const formatterWithoutMillis = getBaseTypeFormatterInstanceExpensive( + column, + optionsWithoutMillis, + ); + + expect(typeof formatterWithMillis).toBe('function'); + expect(typeof formatterWithoutMillis).toBe('function'); + + // Mock dateFormatPresets to test the format patterns + const dateFormatPresetsMock = { + DATETIME_SHORT_WITH_MILLIS: 'DATETIME_SHORT_WITH_MILLIS', + DATETIME_SHORT_WITH_SECONDS: 'DATETIME_SHORT_WITH_SECONDS', + }; + + // Mock formatted dates based on dateFormatPresets + const formattedDateWithMillis = formatterWithMillis( + 1234567890, + mockOptions, + ); + expect(DateFormatting.formatDate).toHaveBeenCalledWith( + 1234567890, + dateFormatPresetsMock.DATETIME_SHORT_WITH_MILLIS, + true, + { + ...mockOptions, + customCalendarOverridesFiscalOffset: true, + }, + ); + const formattedDateWithoutMillis = formatterWithoutMillis( + 1234567890, + mockOptions, + ); + expect(DateFormatting.formatDate).toHaveBeenCalledWith( + 1234567890, + dateFormatPresetsMock.DATETIME_SHORT_WITH_SECONDS, + true, + { + ...mockOptions, + customCalendarOverridesFiscalOffset: true, + }, + ); + // expect(formattedDateWithoutMillis).toBe('MM/DD/YYYY HH:mm:ss'); // Expect pattern without millis + }); +}); diff --git a/src/utils/formatting-util.ts b/src/utils/formatting-util.ts index b428fea..b08ef01 100644 --- a/src/utils/formatting-util.ts +++ b/src/utils/formatting-util.ts @@ -27,7 +27,7 @@ import { showDateFinancialYearFormat, } from './date-utils'; -interface formatOptionsType { +interface FormatOptionsType { isMillisIncluded: boolean; } @@ -36,15 +36,16 @@ const getBucketization = (col: ChartColumn) => col.timeBucket; export const getFormatPattern = (col: ChartColumn): string => getFormatPatternForBucket(getBucketization(col)) || col.format?.pattern; -function getBaseTypeFormatterInstanceExpensive( +export function getBaseTypeFormatterInstanceExpensive( col: ChartColumn, - options: formatOptionsType, + options: FormatOptionsType, ): any { let formatPattern = getFormatPattern(col); // TODO: add numberic formatter if the col is numeric. if (isDateColumn(col)) { const showFinancialFormat = showDateFinancialYearFormat(col); const isDateTime = isDateTimeColumn(col); + if (!formatPattern) { if (isDateTime) { if (options.isMillisIncluded) { @@ -66,7 +67,7 @@ function getBaseTypeFormatterInstanceExpensive( const customCalendarDisplayStr = getDisplayString( customCalendarValueFromEpoch, ); - if (customCalendarValueFromEpoch && customCalendarDisplayStr) { + if (customCalendarDisplayStr) { return customCalendarDisplayStr; } const customCalendarOverridesFiscalOffset = @@ -112,13 +113,13 @@ function getBaseTypeFormatterInstanceExpensive( ); }; } - return (dataValue: any, options?: any) => { + return (dataValue: any) => { return dataValue; }; } export const getDataFormatter = ( col: ChartColumn, - options: formatOptionsType, + options: FormatOptionsType, aggrTypeOverride?: ColumnAggregationType, ) => { // TODO: number formatter for column based on column type based on @@ -156,8 +157,7 @@ export const generateMapOptions = ( tsDateConstants: appConfig?.dateFormatsConfig?.tsDateConstants, tsDefinedCustomCalenders: appConfig?.dateFormatsConfig?.tsDefinedCustomCalenders, - defaultDataSourceId: - appConfig?.dateFormatsConfig?.DEFAULT_DATASOURCE_ID, + defaultDataSourceId: appConfig?.dateFormatsConfig?.defaultDataSourceId, displayToCustomCalendarValueMap: customCalenderMap, }; }; From d2432337d242cd9b6cde93f225577bdace88b9ce Mon Sep 17 00:00:00 2001 From: utkarsh-rai Date: Fri, 8 Nov 2024 19:25:26 +0530 Subject: [PATCH 10/14] SCAL-229204 fixed test failures --- src/utils/date-formatting.ts | 10 ++++++++-- src/utils/formatting-util.spec.ts | 10 +++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/utils/date-formatting.ts b/src/utils/date-formatting.ts index 1fa647c..09bf33b 100644 --- a/src/utils/date-formatting.ts +++ b/src/utils/date-formatting.ts @@ -539,7 +539,7 @@ export function formatDate( ) { return newInputDate; } - newInputDate = sanitizeDate(newInputDate, format, options); + newInputDate = sanitizeDate(newInputDate, formatPattern, options); if (_.isNaN(newInputDate)) { return options.tsLocaleBasedStringsFormats.null_value_placeholder_label; } @@ -554,5 +554,11 @@ export function formatDate( ); return `${inputDate}`; } - return formatDateTime(epochMillis, format, useSystemCalendar, options); + console.log(formatPattern); + return formatDateTime( + epochMillis, + formatPattern, + useSystemCalendar, + options, + ); } diff --git a/src/utils/formatting-util.spec.ts b/src/utils/formatting-util.spec.ts index 3e94138..a8d1795 100644 --- a/src/utils/formatting-util.spec.ts +++ b/src/utils/formatting-util.spec.ts @@ -69,7 +69,7 @@ describe('formatting utils', () => { week_in_year_format: 'V', }, tsLocaleBasedDateFormats: { - DATE_SHORT: 'MM/dd/yyyy', + DATE_SHORT: 'y', }, quarter_of_year: 'Q{1}', tsDefinedCustomCalenders: { @@ -130,13 +130,13 @@ describe('formatting utils', () => { expect(typeof formatter).toBe('function'); const formattedDate = formatter(1234567890, mockOptions); - expect(formattedDate).toBe('2/14/2009'); + expect(formattedDate).toBe('2009'); const customFormattedDate = formatter(1234567891, mockOptions); const customFormattedDateWithoutDisplayValue = formatter( 1234567892, mockOptions, ); - expect(customFormattedDateWithoutDisplayValue).toBe('2/14/2009'); + expect(customFormattedDateWithoutDisplayValue).toBe('2009'); expect(customFormattedDate).toBe('custom Date'); expect(showDateFinancialYearFormat).toHaveBeenCalledWith(column); }); @@ -201,13 +201,13 @@ describe('formatting utils', () => { expect(typeof formatter).toBe('function'); const formattedDate = formatter(1234567890, mockOptions); - expect(formattedDate).toBe('2/14/2009'); + expect(formattedDate).toBe('2009'); const customFormattedDate = formatter(1234567891, mockOptions); const customFormattedDateWithoutDisplayValue = formatter( 1234567892, mockOptions, ); - expect(customFormattedDateWithoutDisplayValue).toBe('2/14/2009'); + expect(customFormattedDateWithoutDisplayValue).toBe('2009'); expect(customFormattedDate).toBe('custom Date'); expect(showDateFinancialYearFormat).toHaveBeenCalledWith(column); }); From 201af0825f9af99716342aafe174ce03e4d3b6fc Mon Sep 17 00:00:00 2001 From: utkarsh-rai Date: Sun, 10 Nov 2024 13:34:20 +0530 Subject: [PATCH 11/14] SCAL-229204 added typeDocs --- src/types/common.types.ts | 26 +++++-- src/utils/date-formatting.ts | 118 +++++++++++++++++++++++++++++- src/utils/date-utils.ts | 39 ++++++++++ src/utils/formatting-util.spec.ts | 26 ++----- src/utils/formatting-util.ts | 78 +++++++++++++++++++- 5 files changed, 259 insertions(+), 28 deletions(-) diff --git a/src/types/common.types.ts b/src/types/common.types.ts index 5507db3..50f629e 100644 --- a/src/types/common.types.ts +++ b/src/types/common.types.ts @@ -250,6 +250,12 @@ export type ChartSdkCustomStylingConfig = { fontFaces?: Array; }; +/** + * Defines a set of standardized date and datetime format strings used for + * displaying dates and times in various formats. Each format string represents + * a specific date or time pattern that can be used for localized display purposes. + */ + export interface DateFormats { DATE_SHORT: string; DATE_SHORT_2_DIGIT_YEAR: string; @@ -280,11 +286,21 @@ export interface DateFormats { } /** - * Date Formats Config for Custom Chart SDK - * This is used to define the date formats for the custom chart SDK - * The date formats are used to format the date values in the chart - * @remarks - * The date formats are used to format the date values in the chart + * Configuration object for date formats and settings in the Chart SDK. + * Provides locale-specific date and string formats, constants, and custom calendars. + * + * @type ChartSdkDateFormatsConfig + * + * @property tsLocaleBasedDateFormats - Optional. A record mapping locale identifiers to date format settings, + * each represented by a `DateFormats` object. + * @property tsLocaleBasedStringsFormats - Optional. A record mapping locale identifiers to localized string + * formats, where each format is represented by a string. + * @property tsDateConstants - Optional. A record mapping string keys to date-related constants, often used + * for standard date patterns or other fixed date-related values(mostly used to identify the case + * for backend proccessed date in case of MONTH_OF_YEAR,DAY_OF_WEEK). + * @property tsDefinedCustomCalenders - Optional. Custom calendar configurations, which have GUID of TS defined Custom calenders. + * @property defaultDataSourceId - Optional. The data source identifier for the date formats, used + * to specify the primary data source for TS defined custom calenders. */ export type ChartSdkDateFormatsConfig = { diff --git a/src/utils/date-formatting.ts b/src/utils/date-formatting.ts index 09bf33b..20bbebe 100644 --- a/src/utils/date-formatting.ts +++ b/src/utils/date-formatting.ts @@ -150,18 +150,57 @@ export const timeBuckets = { const DEFAULT_QUARTER_START_MONTH = 1; +/** + * Checks if a specified column has a data type of 'DATE' or 'DATE_TIME'. + * + * @param col - The column to check, represented as a `ChartColumn`. + * @returns True if the column's data type is either 'DATE' or 'DATE_TIME'; otherwise, false. + */ + export const isDateColumn = (col: ChartColumn) => DataType[col.dataType] === 'DATE' || DataType[col.dataType] === 'DATE_TIME'; +/** + * Determines if a specified column is of type 'ATTRIBUTE'. + * + * @param col - The column to check, represented as a `ChartColumn`. + * @returns True if the column's type is `ATTRIBUTE`; otherwise, false. + */ + export const isAttribute = (col: ChartColumn) => col.type === ColumnType.ATTRIBUTE; +/** + * Checks if a specified column has a data type of 'DATE_TIME'. + * + * @param col - The column to check, represented as a `ChartColumn`. + * @returns True if the column's data type is `DATE_TIME`; otherwise, false. + */ + export const isDateTimeColumn = (col: ChartColumn) => DataType[col.dataType] === 'DATE_TIME'; +/** + * Retrieves the custom calendar GUID associated with a specified column. + * + * @param col - The column from which to retrieve the calendar GUID, represented as a `ChartColumn`. + * @returns The custom calendar GUID of the column, or undefined if not available. + */ export const getCustomCalendarGuidFromColumn = (col: ChartColumn) => col.calenderGuid; +/** + * Determines if a specified column's time bucket is one of the predefined numeric time buckets. + * + * This function checks if the column's `timeBucket` is one of the following: + * `HOUR_OF_DAY`, `DAY_OF_WEEK`, `DAY_OF_MONTH`, `DAY_OF_QUARTER`, `DAY_OF_YEAR`, + * `WEEK_OF_MONTH`, `WEEK_OF_QUARTER`, `WEEK_OF_YEAR`, `MONTH_OF_QUARTER`, + * `MONTH_OF_YEAR`, or `QUARTER_OF_YEAR`. + * + * @param col - The column to check, represented as a `ChartColumn`. + * @returns True if the column's time bucket is one of the specified date-related numeric time buckets; otherwise, false. + */ + const isDateNumTimeBucket = (col: ChartColumn): boolean => { return [ ColumnTimeBucket.HOUR_OF_DAY, @@ -177,17 +216,56 @@ const isDateNumTimeBucket = (col: ChartColumn): boolean => { ColumnTimeBucket.QUARTER_OF_YEAR, ].includes(col.timeBucket); }; + +/** + * Determines if a specified column is both an attribute and has a date-related numeric time bucket. + * + * This function checks if the column is of type `ATTRIBUTE` and has a time bucket that is one of + * the predefined date-related numeric time buckets, as determined by `isDateNumTimeBucket`. + * + * @param col - The column to check, represented as a `ChartColumn`. + * @returns True if the column is an attribute and has a date-related numeric time bucket; otherwise, false. + */ + export const isDateNumColumn = (col: ChartColumn): boolean => { return isAttribute(col) && isDateNumTimeBucket(col); }; +/** + * Determines if a specified column belongs to the "date family," meaning it is either a date column + * or a date-related numeric column. + * + * This function checks if the column is a date column, as determined by `isDateColumn`, or a + * date-related numeric column, as determined by `isDateNumColumn`. + * + * @param col - The column to check, represented as a `ChartColumn`. + * @returns True if the column is either a date column or a date-related numeric column; otherwise, false. + */ + export const isDateFamilyColumn = (col: ChartColumn): boolean => { return isDateColumn(col) || isDateNumColumn(col); }; +/** + * Checks if a specified column has a data type of 'TIME'. + * + * @param col - The column to check, represented as a `ChartColumn`. + * @returns True if the column's data type is `TIME`; otherwise, false. + */ + export const isTimeColumn = (col: ChartColumn) => { return DataType[col.dataType] === 'TIME'; }; +/** + * Retrieves the effective date numeric data type based on the column's time bucket. + * + * This function maps a column's `timeBucket` to a corresponding date numeric data type, such as + * `DATE_NUM_DAY_OF_WEEK`, `DATE_NUM_DAY_IN_MONTH`, etc. If the `timeBucket` does not match any + * predefined value, the function returns `undefined`. + * + * @param col - The column to check, represented as a `ChartColumn`. + * @returns The corresponding date numeric data type, or `undefined` if the `timeBucket` is not recognized. + */ export const getEffectiveDateNumDataType = (col: ChartColumn) => { switch (col.timeBucket) { @@ -398,7 +476,17 @@ function getDayOfWeek(num: any, weekOfDay: any) { const new_num = num % 7; return weekOfDay[weekdays[new_num]]; } - +/** + * Converts a number or string into its corresponding ordinal suffixed value. + * + * This function takes a number (or string that can be parsed as a number) and returns it as a + * string with the appropriate ordinal suffix (`st`, `nd`, `rd`, or `th`). The function accounts for + * exceptions in English grammar, such as 11th, 12th, and 13th, which do not follow the standard + * suffix rules. + * + * @param i - The number (or string) to convert into an ordinal suffixed value. + * @returns A string representing the number with its ordinal suffix (e.g., "1st", "2nd", "3rd", "4th"). + */ export function getOrdinalSuffixedValue(i: number | string): string { let ni = i; // eslint-disable-next-line radix @@ -419,6 +507,34 @@ export function getOrdinalSuffixedValue(i: number | string): string { } return `${ni}th`; } +/** + * Formats a date-related numeric value based on its effective data type, a given format pattern, + * and options. + * + * This function is used to format date-related numeric values (such as days of the month, weeks of + * the year, or hours of the day) according to the specified `effectiveDataType`, `formatPattern`, + * and other options. It supports custom formatting for various date-related values and can handle + * special cases like null values. + * + * @param effectiveDataType - The effective data type to format, which determines how the value will be interpreted + * (e.g., `DATE_NUM_ABS_DAY`, `DATE_NUM_DAY_IN_MONTH`). + * @param value - The numeric value (or string) to format. This value is typically a date-related number. + * @param formatPattern - The pattern to use for formatting the value (e.g., for day or month formatting). + * @param options - An object containing various formatting options, such as locale-based string formats, date constants, + * and settings for the quarter start month and special value handling. + * + * @returns A string representing the formatted date-related numeric value, or a placeholder if the value is null. + * + * @example + * // Example usage + * const formattedDate = formatDateNum( + * dateNumTypes.DATE_NUM_DAY_IN_MONTH, + * 15, + * 'm', + * options // get from {@link generateMapOptions} + * ); + * console.log(formattedDate); // Output could be '15th day of month' depending on the format + */ export function formatDateNum( effectiveDataType: string | undefined, diff --git a/src/utils/date-utils.ts b/src/utils/date-utils.ts index 3652e3b..cd146c7 100644 --- a/src/utils/date-utils.ts +++ b/src/utils/date-utils.ts @@ -2,6 +2,22 @@ import _ from 'lodash'; import { ChartColumn, ColumnTimeBucket } from '../types/answer-column.types'; import { bucketizationToDatePreset, timeBuckets } from './date-formatting'; +/** + * Retrieves the date format pattern associated with a specific time bucket. + * + * This function maps a `ColumnTimeBucket` value to its corresponding date format + * pattern using a predefined configuration (`bucketizationToDatePreset`). It is used + * to ensure that date formatting aligns with the granularity defined by the bucket type. + * + * @param bucket - The time bucket (`ColumnTimeBucket`) for which the format pattern is needed. + * + * @returns The date format pattern associated with the provided bucket, or `undefined` + * if no format pattern exists for the specified bucket. + * + * @example + * const formatPattern = getFormatPatternForBucket(ColumnTimeBucket.DAY_OF_WEEK); + */ + export function getFormatPatternForBucket(bucket: ColumnTimeBucket): any { return bucketizationToDatePreset[ ColumnTimeBucket[bucket] as keyof typeof bucketizationToDatePreset @@ -19,6 +35,29 @@ export const showDateFinancialYearFormat = (col: ChartColumn) => { }); }; +/** + * Retrieves the GUID (Globally Unique Identifier) for a custom calendar based on + * the calendar name and data source ID. + * + * This function looks up a custom calendar GUID from a mapping that associates + * data source IDs with calendar names. If the specified data source ID and calendar + * name exist in the provided map, it returns the corresponding GUID; otherwise, + * it returns `undefined`. This is used because we have some TS defined custom calender + * such as fiscal, geogrian,etc. We apply some custom formatting for those. + * to see where we are getting this refer- + * @link AppConfig + * @param name - The name of the custom calendar to look up. + * @param datasourceId - The ID of the data source associated with the custom calendar. + * @param datsourceIdToCustomCalendarMap - A mapping object where each data source ID + * maps to another object that associates calendar names with their GUIDs. + * + * @returns The GUID for the custom calendar associated with the given `name` and + * `datasourceId`, or `undefined` if no matching GUID is found. + * + * @example + * const calendarGuid = getCustomCalendarGuid('fiscal', 'dataSource1', calendarMap); + */ + export function getCustomCalendarGuid( name: string, datasourceId: string, diff --git a/src/utils/formatting-util.spec.ts b/src/utils/formatting-util.spec.ts index a8d1795..1b9fcfa 100644 --- a/src/utils/formatting-util.spec.ts +++ b/src/utils/formatting-util.spec.ts @@ -12,7 +12,7 @@ import { } from './date-utils'; import { generateMapOptions, - getBaseTypeFormatterInstanceExpensive, + getBaseTypeFormatterInstance, getDataFormatter, getFormatPattern, } from './formatting-util'; @@ -123,10 +123,7 @@ describe('formatting utils', () => { } as ChartColumn; const options = { isMillisIncluded: true }; - const formatter = getBaseTypeFormatterInstanceExpensive( - column, - options, - ); + const formatter = getBaseTypeFormatterInstance(column, options); expect(typeof formatter).toBe('function'); const formattedDate = formatter(1234567890, mockOptions); @@ -151,10 +148,7 @@ describe('formatting utils', () => { calenderGuid: '12345', } as ChartColumn; const options = { isMillisIncluded: true }; - const formatter = getBaseTypeFormatterInstanceExpensive( - column, - options, - ); + const formatter = getBaseTypeFormatterInstance(column, options); expect(typeof formatter).toBe('function'); const formattedDate = formatter(1234567890, mockOptions); @@ -178,10 +172,7 @@ describe('formatting utils', () => { calenderGuid: '12345', } as ChartColumn; const options = { isMillisIncluded: true }; - const formatter = getBaseTypeFormatterInstanceExpensive( - column, - options, - ); + const formatter = getBaseTypeFormatterInstance(column, options); expect(formatter('test')).toBe('test'); }); @@ -254,10 +245,7 @@ describe('formatting utils', () => { calenderGuid: '12345', } as ChartColumn; const options = { isMillisIncluded: true }; - const formatter = getBaseTypeFormatterInstanceExpensive( - column, - options, - ); + const formatter = getBaseTypeFormatterInstance(column, options); expect(typeof formatter).toBe('function'); }); test('should correctly set format pattern for date-time column with and without milliseconds', () => { @@ -275,11 +263,11 @@ describe('formatting utils', () => { const optionsWithMillis = { isMillisIncluded: true }; const optionsWithoutMillis = { isMillisIncluded: false }; - const formatterWithMillis = getBaseTypeFormatterInstanceExpensive( + const formatterWithMillis = getBaseTypeFormatterInstance( column, optionsWithMillis, ); - const formatterWithoutMillis = getBaseTypeFormatterInstanceExpensive( + const formatterWithoutMillis = getBaseTypeFormatterInstance( column, optionsWithoutMillis, ); diff --git a/src/utils/formatting-util.ts b/src/utils/formatting-util.ts index b08ef01..00c9b01 100644 --- a/src/utils/formatting-util.ts +++ b/src/utils/formatting-util.ts @@ -32,11 +32,43 @@ interface FormatOptionsType { } const getBucketization = (col: ChartColumn) => col.timeBucket; - +/** + * Retrieves the format pattern for a given column based on its bucketization or custom format. + * + * This function first checks the column's bucketization and attempts to retrieve a format pattern + * using `getFormatPatternForBucket`. If no format pattern is found for the bucketization, it falls + * back to the column's custom format pattern (if available). + * + * @param col - The column to retrieve the format pattern for, represented as a `ChartColumn`. + * @returns The format pattern as a string, either derived from the bucketization or the column's custom format. + */ export const getFormatPattern = (col: ChartColumn): string => getFormatPatternForBucket(getBucketization(col)) || col.format?.pattern; -export function getBaseTypeFormatterInstanceExpensive( +/** + * Retrieves a formatter function for a column based on its type (e.g., date, date_number). + * + * This function returns a specialized formatter based on the column's type and format pattern. + * It first checks if the column is a date column and applies the appropriate date format, + * considering options like whether the fiscal year or milliseconds are included. If the column is + * a date number column, it applies date number formatting. For other types of columns, it returns + * the raw data value without formatting. + * + * @param col - The column for which the formatter is to be created, represented as a `ChartColumn`. + * @param options - The formatting options, including locale, fiscal settings, and custom calendar information. + * + * @returns A function that formats a data value based on the column type and format pattern. + * This returned function takes in the data value and optional additional options. + * + * @example + * + * const dateFormatter = getBaseTypeFormatterInstance(column, options); + * const formattedValue = dateFormatter(dataValue, options); + * For more info in column + * @link ChartColumn + * To see how options are feched see src/example/custom-bar-chart + */ +export function getBaseTypeFormatterInstance( col: ChartColumn, options: FormatOptionsType, ): any { @@ -117,6 +149,23 @@ export function getBaseTypeFormatterInstanceExpensive( return dataValue; }; } +/** + * Retrieves the appropriate data formatter function for a specified column, + * based on its type, format options, and an optional aggregation type override. + * + * + * @param col - The column for which the formatter is to be created, represented as a `ChartColumn`. + * @param options - The formatting options, including settings such as locale, custom calendars, and date constants. + * @param aggrTypeOverride - Optional override for the column's aggregation type (e.g., `SUM`, `AVERAGE`), + * which could affect formatting behavior in future implementations. + * + * @returns A function to format data values based on the column type, format pattern, and options. + * This returned function can be used to format individual data values within the column. + * + * @example + * const formatter = getDataFormatter(column, formatOptions, 'SUM'); + * const formattedValue = formatter(dataValue); + */ export const getDataFormatter = ( col: ChartColumn, options: FormatOptionsType, @@ -125,9 +174,32 @@ export const getDataFormatter = ( // TODO: number formatter for column based on column type based on // aggregation type - return getBaseTypeFormatterInstanceExpensive(col, options); + return getBaseTypeFormatterInstance(col, options); }; +/** + * Generates a configuration object with formatting and locale-based options for date handling, + * including custom calendar mapping for a specific column. + * + * This function builds an options object that includes locale settings, quarter start month, + * date formats, and a custom calendar map, which is populated when the column contains a + * custom calendar GUID. The custom calendar map links display values to custom calendar data + * for date formatting needs. + * + * @param appConfig - The application configuration containing locale, date format settings, + * and custom calendar definitions. + * @param col - The column to check for a custom calendar GUID, represented as a `ChartColumn`. + * @param data - The dataset, used to populate the custom calendar map if a custom calendar + * GUID is associated with the column. + * + * @returns An options object with locale and date formatting details, including a custom + * calendar map if applicable. + * + * @example + * const mapOptions = generateMapOptions(appConfig, column, data); + * const locale = mapOptions.locale; + */ + export const generateMapOptions = ( appConfig: any, col: ChartColumn, From 9bfcb6651a9acc032bc17f3b4d1dcdceb138dac4 Mon Sep 17 00:00:00 2001 From: utkarsh-rai Date: Tue, 19 Nov 2024 03:52:20 +0530 Subject: [PATCH 12/14] SCAL-229204 code refactoring --- example/custom-bar-chart/custom-chart.ts | 1 - src/utils/date-formatting.ts | 1 - src/utils/date-utils.spec.ts | 2 +- src/utils/formatting-util.spec.ts | 1 - src/utils/formatting-util.ts | 21 ++++++++++----------- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/example/custom-bar-chart/custom-chart.ts b/example/custom-bar-chart/custom-chart.ts index 44a42bc..80d9fc4 100644 --- a/example/custom-bar-chart/custom-chart.ts +++ b/example/custom-bar-chart/custom-chart.ts @@ -18,7 +18,6 @@ import { ColumnType, CustomChartContext, DataPointsArray, - dateFormatter, getCfForColumn, getChartContext, getCustomCalendarGuidFromColumn, diff --git a/src/utils/date-formatting.ts b/src/utils/date-formatting.ts index 20bbebe..60dd8be 100644 --- a/src/utils/date-formatting.ts +++ b/src/utils/date-formatting.ts @@ -670,7 +670,6 @@ export function formatDate( ); return `${inputDate}`; } - console.log(formatPattern); return formatDateTime( epochMillis, formatPattern, diff --git a/src/utils/date-utils.spec.ts b/src/utils/date-utils.spec.ts index 61e34dd..0eadd25 100644 --- a/src/utils/date-utils.spec.ts +++ b/src/utils/date-utils.spec.ts @@ -3,7 +3,7 @@ import { getCustomCalendarGuid, getFormatPatternForBucket, showDateFinancialYearFormat, -} from './date-utils'; // Adjust path +} from './date-utils'; describe('getFormatPatternForBucket', () => { const mockDatasourceIdToCustomCalendarMap = { diff --git a/src/utils/formatting-util.spec.ts b/src/utils/formatting-util.spec.ts index 1b9fcfa..e568ec1 100644 --- a/src/utils/formatting-util.spec.ts +++ b/src/utils/formatting-util.spec.ts @@ -308,6 +308,5 @@ describe('formatting utils', () => { customCalendarOverridesFiscalOffset: true, }, ); - // expect(formattedDateWithoutMillis).toBe('MM/DD/YYYY HH:mm:ss'); // Expect pattern without millis }); }); diff --git a/src/utils/formatting-util.ts b/src/utils/formatting-util.ts index 00c9b01..f836843 100644 --- a/src/utils/formatting-util.ts +++ b/src/utils/formatting-util.ts @@ -207,17 +207,16 @@ export const generateMapOptions = ( ): any => { let customCalenderMap = {}; - if ( - getCustomCalendarGuidFromColumn(col) !== null && - getCustomCalendarGuidFromColumn(col) !== undefined && - getCustomCalendarGuidFromColumn(col) !== '' - ) { - for (let i = 0; i < data.length; i++) { - customCalenderMap = { - ...customCalenderMap, - [data[i].v.s]: data[i], - }; - } + if (!_.isEmpty(getCustomCalendarGuidFromColumn(col))) { + customCalenderMap = data.reduce( + (customCalenderMapAcc: any, dataValue: any) => { + return { + ...customCalenderMapAcc, + [dataValue.v.s]: dataValue, + }; + }, + {}, + ); } return { locale: appConfig?.localeOptions?.locale, From a09ad6c49f3099e02e1e9a772ead80b48861d9bd Mon Sep 17 00:00:00 2001 From: "harshmeet.singh" Date: Thu, 21 Nov 2024 13:20:15 +0530 Subject: [PATCH 13/14] update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d5bad3e..6c3f6b4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@thoughtspot/ts-chart-sdk", "private": false, - "version": "0.0.2-alpha.21", + "version": "0.0.2-alpha.22", "module": "lib/index", "main": "lib/index", "types": "lib/index", From 18fdd360fd0178c6c888d6884ea27ef27444ee1f Mon Sep 17 00:00:00 2001 From: "harshmeet.singh" Date: Thu, 21 Nov 2024 13:30:59 +0530 Subject: [PATCH 14/14] revert lock file --- example/custom-bar-chart/pnpm-lock.yaml | 1071 ++++++++++++++++------- 1 file changed, 732 insertions(+), 339 deletions(-) diff --git a/example/custom-bar-chart/pnpm-lock.yaml b/example/custom-bar-chart/pnpm-lock.yaml index 958bcb7..efc8601 100644 --- a/example/custom-bar-chart/pnpm-lock.yaml +++ b/example/custom-bar-chart/pnpm-lock.yaml @@ -1,349 +1,742 @@ lockfileVersion: '6.0' settings: - autoInstallPeers: true - excludeLinksFromLockfile: false + autoInstallPeers: true + excludeLinksFromLockfile: false dependencies: - '@thoughtspot/ts-chart-sdk': - specifier: 0.0.2-alpha.20 - version: link:../.. - chart.js: - specifier: ^4.3.0 - version: 4.3.0 - chartjs-plugin-datalabels: - specifier: ^2.2.0 - version: 2.2.0(chart.js@4.3.0) - lodash: - specifier: ^4.17.21 - version: 4.17.21 + '@thoughtspot/ts-chart-sdk': + specifier: 0.0.2-alpha.6 + version: 0.0.2-alpha.6 + chart.js: + specifier: ^4.3.0 + version: 4.3.0 + chartjs-plugin-datalabels: + specifier: ^2.2.0 + version: 2.2.0(chart.js@4.3.0) + lodash: + specifier: ^4.17.21 + version: 4.17.21 devDependencies: - vite: - specifier: ^4.3.5 - version: 4.3.9 + vite: + specifier: ^4.3.5 + version: 4.3.9 packages: + /@esbuild/android-arm64@0.17.19: + resolution: + { + integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==, + } + engines: { node: '>=12' } + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.17.19: + resolution: + { + integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==, + } + engines: { node: '>=12' } + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.17.19: + resolution: + { + integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.17.19: + resolution: + { + integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==, + } + engines: { node: '>=12' } + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.17.19: + resolution: + { + integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.17.19: + resolution: + { + integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==, + } + engines: { node: '>=12' } + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.17.19: + resolution: + { + integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.17.19: + resolution: + { + integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==, + } + engines: { node: '>=12' } + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.17.19: + resolution: + { + integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==, + } + engines: { node: '>=12' } + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.17.19: + resolution: + { + integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==, + } + engines: { node: '>=12' } + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.17.19: + resolution: + { + integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==, + } + engines: { node: '>=12' } + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.17.19: + resolution: + { + integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==, + } + engines: { node: '>=12' } + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.17.19: + resolution: + { + integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==, + } + engines: { node: '>=12' } + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.17.19: + resolution: + { + integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==, + } + engines: { node: '>=12' } + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.17.19: + resolution: + { + integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==, + } + engines: { node: '>=12' } + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.17.19: + resolution: + { + integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.17.19: + resolution: + { + integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.17.19: + resolution: + { + integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.17.19: + resolution: + { + integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.17.19: + resolution: + { + integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==, + } + engines: { node: '>=12' } + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.17.19: + resolution: + { + integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==, + } + engines: { node: '>=12' } + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.17.19: + resolution: + { + integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@kurkle/color@0.3.2: + resolution: + { + integrity: sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==, + } + dev: false + + /@rollup/rollup-android-arm-eabi@4.18.1: + resolution: + { + integrity: sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==, + } + cpu: [arm] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-android-arm64@4.18.1: + resolution: + { + integrity: sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==, + } + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-darwin-arm64@4.18.1: + resolution: + { + integrity: sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==, + } + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-darwin-x64@4.18.1: + resolution: + { + integrity: sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==, + } + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.18.1: + resolution: + { + integrity: sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==, + } + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-linux-arm-musleabihf@4.18.1: + resolution: + { + integrity: sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==, + } + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.18.1: + resolution: + { + integrity: sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==, + } + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-linux-arm64-musl@4.18.1: + resolution: + { + integrity: sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==, + } + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-linux-powerpc64le-gnu@4.18.1: + resolution: + { + integrity: sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==, + } + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.18.1: + resolution: + { + integrity: sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==, + } + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-linux-s390x-gnu@4.18.1: + resolution: + { + integrity: sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==, + } + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-linux-x64-gnu@4.18.1: + resolution: + { + integrity: sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==, + } + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-linux-x64-musl@4.18.1: + resolution: + { + integrity: sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==, + } + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.18.1: + resolution: + { + integrity: sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==, + } + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.18.1: + resolution: + { + integrity: sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==, + } + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-win32-x64-msvc@4.18.1: + resolution: + { + integrity: sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==, + } + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@thoughtspot/ts-chart-sdk@0.0.2-alpha.6: + resolution: + { + integrity: sha512-DAHkPq66gCUneBJttlexx3HsHoXvxgIB9UEz5xpESiUSRwIEv9qX5IcPhO7PCBOarsyvl+ZH40Zoth2s0V9egA==, + } + dependencies: + lodash: 4.17.21 + promise-postmessage: 3.5.1 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + dev: false + + /@types/estree@1.0.5: + resolution: + { + integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==, + } + dev: false + + /chart.js@4.3.0: + resolution: + { + integrity: sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==, + } + engines: { pnpm: '>=7' } + dependencies: + '@kurkle/color': 0.3.2 + dev: false + + /chartjs-plugin-datalabels@2.2.0(chart.js@4.3.0): + resolution: + { + integrity: sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==, + } + peerDependencies: + chart.js: '>=3.0.0' + dependencies: + chart.js: 4.3.0 + dev: false + + /esbuild@0.17.19: + resolution: + { + integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==, + } + engines: { node: '>=12' } + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.17.19 + '@esbuild/android-arm64': 0.17.19 + '@esbuild/android-x64': 0.17.19 + '@esbuild/darwin-arm64': 0.17.19 + '@esbuild/darwin-x64': 0.17.19 + '@esbuild/freebsd-arm64': 0.17.19 + '@esbuild/freebsd-x64': 0.17.19 + '@esbuild/linux-arm': 0.17.19 + '@esbuild/linux-arm64': 0.17.19 + '@esbuild/linux-ia32': 0.17.19 + '@esbuild/linux-loong64': 0.17.19 + '@esbuild/linux-mips64el': 0.17.19 + '@esbuild/linux-ppc64': 0.17.19 + '@esbuild/linux-riscv64': 0.17.19 + '@esbuild/linux-s390x': 0.17.19 + '@esbuild/linux-x64': 0.17.19 + '@esbuild/netbsd-x64': 0.17.19 + '@esbuild/openbsd-x64': 0.17.19 + '@esbuild/sunos-x64': 0.17.19 + '@esbuild/win32-arm64': 0.17.19 + '@esbuild/win32-ia32': 0.17.19 + '@esbuild/win32-x64': 0.17.19 + dev: true + + /fsevents@2.3.2: + resolution: + { + integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==, + } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + os: [darwin] + requiresBuild: true + optional: true - /@esbuild/android-arm64@0.17.19: - resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/android-arm@0.17.19: - resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/android-x64@0.17.19: - resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/darwin-arm64@0.17.19: - resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@esbuild/darwin-x64@0.17.19: - resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@esbuild/freebsd-arm64@0.17.19: - resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/freebsd-x64@0.17.19: - resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-arm64@0.17.19: - resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-arm@0.17.19: - resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-ia32@0.17.19: - resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-loong64@0.17.19: - resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-mips64el@0.17.19: - resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-ppc64@0.17.19: - resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-riscv64@0.17.19: - resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-s390x@0.17.19: - resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-x64@0.17.19: - resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/netbsd-x64@0.17.19: - resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/openbsd-x64@0.17.19: - resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/sunos-x64@0.17.19: - resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-arm64@0.17.19: - resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-ia32@0.17.19: - resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-x64@0.17.19: - resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@kurkle/color@0.3.2: - resolution: {integrity: sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==} - dev: false - - /chart.js@4.3.0: - resolution: {integrity: sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==} - engines: {pnpm: '>=7'} - dependencies: - '@kurkle/color': 0.3.2 - dev: false - - /chartjs-plugin-datalabels@2.2.0(chart.js@4.3.0): - resolution: {integrity: sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==} - peerDependencies: - chart.js: '>=3.0.0' - dependencies: - chart.js: 4.3.0 - dev: false - - /esbuild@0.17.19: - resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/android-arm': 0.17.19 - '@esbuild/android-arm64': 0.17.19 - '@esbuild/android-x64': 0.17.19 - '@esbuild/darwin-arm64': 0.17.19 - '@esbuild/darwin-x64': 0.17.19 - '@esbuild/freebsd-arm64': 0.17.19 - '@esbuild/freebsd-x64': 0.17.19 - '@esbuild/linux-arm': 0.17.19 - '@esbuild/linux-arm64': 0.17.19 - '@esbuild/linux-ia32': 0.17.19 - '@esbuild/linux-loong64': 0.17.19 - '@esbuild/linux-mips64el': 0.17.19 - '@esbuild/linux-ppc64': 0.17.19 - '@esbuild/linux-riscv64': 0.17.19 - '@esbuild/linux-s390x': 0.17.19 - '@esbuild/linux-x64': 0.17.19 - '@esbuild/netbsd-x64': 0.17.19 - '@esbuild/openbsd-x64': 0.17.19 - '@esbuild/sunos-x64': 0.17.19 - '@esbuild/win32-arm64': 0.17.19 - '@esbuild/win32-ia32': 0.17.19 - '@esbuild/win32-x64': 0.17.19 - dev: true - - /fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: false - - /nanoid@3.3.6: - resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: true - - /picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: true - - /postcss@8.4.24: - resolution: {integrity: sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.6 - picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: true - - /rollup@3.26.0: - resolution: {integrity: sha512-YzJH0eunH2hr3knvF3i6IkLO/jTjAEwU4HoMUbQl4//Tnl3ou0e7P5SjxdDr8HQJdeUJShlbEHXrrnEHy1l7Yg==} - engines: {node: '>=14.18.0', npm: '>=8.0.0'} - hasBin: true - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /source-map-js@1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} - engines: {node: '>=0.10.0'} - dev: true - - /vite@4.3.9: - resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==} - engines: {node: ^14.18.0 || >=16.0.0} - hasBin: true - peerDependencies: - '@types/node': '>= 14' - less: '*' - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - dependencies: - esbuild: 0.17.19 - postcss: 8.4.24 - rollup: 3.26.0 - optionalDependencies: - fsevents: 2.3.2 - dev: true + /js-tokens@4.0.0: + resolution: + { + integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, + } + dev: false + + /lodash@4.17.21: + resolution: + { + integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, + } + dev: false + + /loose-envify@1.4.0: + resolution: + { + integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, + } + hasBin: true + dependencies: + js-tokens: 4.0.0 + dev: false + + /nanoid@3.3.6: + resolution: + { + integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==, + } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + hasBin: true + dev: true + + /object-assign@4.1.1: + resolution: + { + integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==, + } + engines: { node: '>=0.10.0' } + dev: false + + /picocolors@1.0.0: + resolution: + { + integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==, + } + dev: true + + /postcss@8.4.24: + resolution: + { + integrity: sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==, + } + engines: { node: ^10 || ^12 || >=14 } + dependencies: + nanoid: 3.3.6 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + + /promise-postmessage@3.5.1: + resolution: + { + integrity: sha512-ytApGxdLLtv01O5iceqz7WttChm23obxTbrckMX4ZqWy5r8XM9PE0nzNOuChDLT13i62usOW1O0V2mJlBblA3g==, + } + dependencies: + rollup: 4.18.1 + dev: false + + /react-dom@17.0.2(react@17.0.2): + resolution: + { + integrity: sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==, + } + peerDependencies: + react: 17.0.2 + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react: 17.0.2 + scheduler: 0.20.2 + dev: false + + /react@17.0.2: + resolution: + { + integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==, + } + engines: { node: '>=0.10.0' } + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + dev: false + + /rollup@3.26.0: + resolution: + { + integrity: sha512-YzJH0eunH2hr3knvF3i6IkLO/jTjAEwU4HoMUbQl4//Tnl3ou0e7P5SjxdDr8HQJdeUJShlbEHXrrnEHy1l7Yg==, + } + engines: { node: '>=14.18.0', npm: '>=8.0.0' } + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /rollup@4.18.1: + resolution: + { + integrity: sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==, + } + engines: { node: '>=18.0.0', npm: '>=8.0.0' } + hasBin: true + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.18.1 + '@rollup/rollup-android-arm64': 4.18.1 + '@rollup/rollup-darwin-arm64': 4.18.1 + '@rollup/rollup-darwin-x64': 4.18.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.18.1 + '@rollup/rollup-linux-arm-musleabihf': 4.18.1 + '@rollup/rollup-linux-arm64-gnu': 4.18.1 + '@rollup/rollup-linux-arm64-musl': 4.18.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.18.1 + '@rollup/rollup-linux-riscv64-gnu': 4.18.1 + '@rollup/rollup-linux-s390x-gnu': 4.18.1 + '@rollup/rollup-linux-x64-gnu': 4.18.1 + '@rollup/rollup-linux-x64-musl': 4.18.1 + '@rollup/rollup-win32-arm64-msvc': 4.18.1 + '@rollup/rollup-win32-ia32-msvc': 4.18.1 + '@rollup/rollup-win32-x64-msvc': 4.18.1 + fsevents: 2.3.2 + dev: false + + /scheduler@0.20.2: + resolution: + { + integrity: sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==, + } + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + dev: false + + /source-map-js@1.0.2: + resolution: + { + integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==, + } + engines: { node: '>=0.10.0' } + dev: true + + /vite@4.3.9: + resolution: + { + integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==, + } + engines: { node: ^14.18.0 || >=16.0.0 } + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.17.19 + postcss: 8.4.24 + rollup: 3.26.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true