diff --git a/.prettierrc b/.prettierrc index 3e9abf30e..9e62e9b65 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,6 +3,6 @@ "jsxSingleQuote": true, "singleQuote": true, "bracketSpacing": true, - "printWidth": 120, + "printWidth": 80, "trailingComma": "es5" } diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index 564d608fe..0f7650790 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -19,7 +19,9 @@ export namespace Components { "filterEnabled": boolean; "filterTexts"?: FilterTexts; "loadingText"?: string; - "onSelectionChange": (event: NonCancelableCustomEvent>) => void; + "onSelectionChange": ( + event: NonCancelableCustomEvent> + ) => void; "paginationEnabled": boolean; "query": TreeQuery; "selectionType"?: TableProps.SelectionType; @@ -43,8 +45,12 @@ export namespace Components { "loading": boolean; "loadingText": string; "onExpandChildren": (node: ITreeNode) => void; - "onSelectionChange": (event: NonCancelableCustomEvent>) => void; - "onSortingChange": (event: NonCancelableCustomEvent>) => void; + "onSelectionChange": ( + event: NonCancelableCustomEvent> + ) => void; + "onSortingChange": ( + event: NonCancelableCustomEvent> + ) => void; "resizableColumns": boolean; "selectionType": TableProps.SelectionType; "sortingDisabled": boolean; @@ -100,7 +106,9 @@ declare namespace LocalJSX { "filterEnabled"?: boolean; "filterTexts"?: FilterTexts; "loadingText"?: string; - "onSelectionChange"?: (event: NonCancelableCustomEvent>) => void; + "onSelectionChange"?: ( + event: NonCancelableCustomEvent> + ) => void; "paginationEnabled"?: boolean; "query"?: TreeQuery; "selectionType"?: TableProps.SelectionType; @@ -124,8 +132,12 @@ declare namespace LocalJSX { "loading"?: boolean; "loadingText"?: string; "onExpandChildren"?: (node: ITreeNode) => void; - "onSelectionChange"?: (event: NonCancelableCustomEvent>) => void; - "onSortingChange"?: (event: NonCancelableCustomEvent>) => void; + "onSelectionChange"?: ( + event: NonCancelableCustomEvent> + ) => void; + "onSortingChange"?: ( + event: NonCancelableCustomEvent> + ) => void; "resizableColumns"?: boolean; "selectionType"?: TableProps.SelectionType; "sortingDisabled"?: boolean; diff --git a/packages/components/src/components/iot-resource-explorer/iot-resource-explorer.spec.ts b/packages/components/src/components/iot-resource-explorer/iot-resource-explorer.spec.ts index 1394a42a3..33e0c29e6 100644 --- a/packages/components/src/components/iot-resource-explorer/iot-resource-explorer.spec.ts +++ b/packages/components/src/components/iot-resource-explorer/iot-resource-explorer.spec.ts @@ -7,7 +7,10 @@ import { update } from '../../testing/update'; import flushPromises from 'flush-promises'; import { mocklistAssetsResponse } from '../../testing/mocks/data/listAssetsResponse'; import { IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; -import { createMockIoTEventsSDK, createMockSiteWiseSDK } from '@iot-app-kit/testing-util'; +import { + createMockIoTEventsSDK, + createMockSiteWiseSDK, +} from '@iot-app-kit/testing-util'; jest.useFakeTimers(); diff --git a/packages/components/src/components/iot-resource-explorer/iot-resource-explorer.tsx b/packages/components/src/components/iot-resource-explorer/iot-resource-explorer.tsx index d27549546..a056a80b1 100644 --- a/packages/components/src/components/iot-resource-explorer/iot-resource-explorer.tsx +++ b/packages/components/src/components/iot-resource-explorer/iot-resource-explorer.tsx @@ -1,8 +1,15 @@ import { Component, h, Prop, State, Watch } from '@stencil/core'; import { ErrorDetails, TreeProvider, TreeQuery } from '@iot-app-kit/core'; -import { BranchReference, SiteWiseAssetTreeNode } from '@iot-app-kit/source-iotsitewise'; +import { + BranchReference, + SiteWiseAssetTreeNode, +} from '@iot-app-kit/source-iotsitewise'; import { SiteWiseAssetResource, FilterTexts, ColumnDefinition } from './types'; -import { EmptyStateProps, ITreeNode, UseTreeCollection } from '@iot-app-kit/related-table'; +import { + EmptyStateProps, + ITreeNode, + UseTreeCollection, +} from '@iot-app-kit/related-table'; import { parseSitewiseAssetTree } from './utils'; import { TableProps } from '@awsui/components-react/table'; import { NonCancelableCustomEvent } from '@awsui/components-react'; @@ -25,13 +32,15 @@ const DEFAULT_COLUMNS: ColumnDefinition[] = [ sortingField: 'creationDate', id: 'creationDate', header: 'Created', - cell: ({ creationDate }: SiteWiseAssetResource) => creationDate?.toUTCString(), + cell: ({ creationDate }: SiteWiseAssetResource) => + creationDate?.toUTCString(), }, { sortingField: 'lastUpdateDate', id: 'lastUpdateDate', header: 'Updated', - cell: ({ lastUpdateDate }: SiteWiseAssetResource) => lastUpdateDate?.toUTCString(), + cell: ({ lastUpdateDate }: SiteWiseAssetResource) => + lastUpdateDate?.toUTCString(), }, ]; @Component({ @@ -50,7 +59,9 @@ export class IotResourceExplorer { @Prop() paginationEnabled = true; @Prop() wrapLines = false; @Prop() widgetId: string = uuidv4(); - @Prop() onSelectionChange: (event: NonCancelableCustomEvent>) => void; + @Prop() onSelectionChange: ( + event: NonCancelableCustomEvent> + ) => void; @Prop() expanded?: boolean = false; @State() provider: TreeProvider; @@ -95,7 +106,9 @@ export class IotResourceExplorer { expandNode = (node: ITreeNode) => { node.hierarchies?.forEach((hierarchy: any) => { - this.provider.expand(new BranchReference(node.id, hierarchy.id as string)); + this.provider.expand( + new BranchReference(node.id, hierarchy.id as string) + ); }); }; @@ -107,7 +120,9 @@ export class IotResourceExplorer { newItems.forEach(({ id, hierarchies, hasChildren }) => { if (!this.expandedItems[id] && hasChildren) { hierarchies?.forEach((hierarchy: any) => { - this.provider.expand(new BranchReference(id, hierarchy.id as string)); + this.provider.expand( + new BranchReference(id, hierarchy.id as string) + ); }); newExpandedItems[id] = true; @@ -119,7 +134,9 @@ export class IotResourceExplorer { } render() { - const filtering = this.filterEnabled ? this.filterTexts || this.defaults.filterText : undefined; + const filtering = this.filterEnabled + ? this.filterTexts || this.defaults.filterText + : undefined; const collectionOptions: UseTreeCollection = { columnDefinitions: this.columnDefinitions, keyPropertyName: 'id', @@ -149,7 +166,10 @@ export class IotResourceExplorer { if (this.errors.length > 0) { // TODO: Make use of all the errors - empty = { header: 'Error', description: this.errors[this.errors.length - 1]?.msg }; + empty = { + header: 'Error', + description: this.errors[this.errors.length - 1]?.msg, + }; } return ( diff --git a/packages/components/src/components/iot-resource-explorer/iot-tree-table.spec.ts b/packages/components/src/components/iot-resource-explorer/iot-tree-table.spec.ts index 3cbe1d6bd..ad20b3d9d 100644 --- a/packages/components/src/components/iot-resource-explorer/iot-tree-table.spec.ts +++ b/packages/components/src/components/iot-resource-explorer/iot-tree-table.spec.ts @@ -68,7 +68,9 @@ const treeTableSpecPage = async () => { html: '
', supportsShadowDom: false, }); - const treeTable = page.doc.createElement('iot-tree-table') as CustomHTMLElement; + const treeTable = page.doc.createElement( + 'iot-tree-table' + ) as CustomHTMLElement; const props: Partial = { items, collectionOptions, diff --git a/packages/components/src/components/iot-resource-explorer/iot-tree-table.tsx b/packages/components/src/components/iot-resource-explorer/iot-tree-table.tsx index 6336cae9c..546c44bf1 100644 --- a/packages/components/src/components/iot-resource-explorer/iot-tree-table.tsx +++ b/packages/components/src/components/iot-resource-explorer/iot-tree-table.tsx @@ -39,9 +39,13 @@ export class IotTreeTable { @Prop() sortingDisabled: boolean; @Prop() ariaLabels: TableProps.AriaLabels; - @Prop() onSelectionChange: (event: NonCancelableCustomEvent>) => void; + @Prop() onSelectionChange: ( + event: NonCancelableCustomEvent> + ) => void; @Prop() onExpandChildren: (node: ITreeNode) => void; - @Prop() onSortingChange: (event: NonCancelableCustomEvent>) => void; + @Prop() onSortingChange: ( + event: NonCancelableCustomEvent> + ) => void; private root: Root; componentDidLoad() { @@ -72,7 +76,11 @@ export class IotTreeTable { expandChildren: this.onExpandChildren, onSortingChange: this.onSortingChange, - onSelectionChange: (event: NonCancelableCustomEvent>) => { + onSelectionChange: ( + event: NonCancelableCustomEvent< + TableProps.SelectionChangeDetail + > + ) => { this.selectedItems = event.detail.selectedItems; if (this.onSelectionChange) { this.onSelectionChange(event); @@ -80,7 +88,9 @@ export class IotTreeTable { }, } as unknown as RelatedTableExtendedProps; - this.root.render(createElement(RelatedTableWithCollectionHooks, attributes)); + this.root.render( + createElement(RelatedTableWithCollectionHooks, attributes) + ); } render() { diff --git a/packages/components/src/components/iot-resource-explorer/utils.ts b/packages/components/src/components/iot-resource-explorer/utils.ts index 1e76630f1..2513f6410 100644 --- a/packages/components/src/components/iot-resource-explorer/utils.ts +++ b/packages/components/src/components/iot-resource-explorer/utils.ts @@ -1,4 +1,7 @@ -import { SiteWiseAssetTreeNode, HierarchyGroup } from '@iot-app-kit/source-iotsitewise'; +import { + SiteWiseAssetTreeNode, + HierarchyGroup, +} from '@iot-app-kit/source-iotsitewise'; import { SiteWiseAssetResource } from './types'; const recursiveParseSitewiseAssetTree = ( @@ -13,7 +16,11 @@ const recursiveParseSitewiseAssetTree = ( parentId, }); node.hierarchies.forEach((hierarchy: HierarchyGroup) => { - recursiveParseSitewiseAssetTree(flattenTree, hierarchy.children, node.asset.id); + recursiveParseSitewiseAssetTree( + flattenTree, + hierarchy.children, + node.asset.id + ); }); }); }; diff --git a/packages/components/src/testing/app/iot-test-routes.tsx b/packages/components/src/testing/app/iot-test-routes.tsx index 7d46cf8a1..7ea75bce6 100755 --- a/packages/components/src/testing/app/iot-test-routes.tsx +++ b/packages/components/src/testing/app/iot-test-routes.tsx @@ -12,7 +12,12 @@ export class IotTestRoutes { {routes.map((r) => ( - + ))} diff --git a/packages/components/src/testing/resource-explorer/iot-resource-explorer-demo.tsx b/packages/components/src/testing/resource-explorer/iot-resource-explorer-demo.tsx index 5907a6e31..044618929 100644 --- a/packages/components/src/testing/resource-explorer/iot-resource-explorer-demo.tsx +++ b/packages/components/src/testing/resource-explorer/iot-resource-explorer-demo.tsx @@ -10,7 +10,10 @@ export class IotResourceExplorerDemo { private query: SiteWiseQuery; componentWillLoad() { - const { query } = initialize({ awsCredentials: getEnvCredentials(), awsRegion: 'us-east-1' }); + const { query } = initialize({ + awsCredentials: getEnvCredentials(), + awsRegion: 'us-east-1', + }); this.query = query; } diff --git a/packages/components/src/testing/resource-explorer/iot-tree-table-demo.tsx b/packages/components/src/testing/resource-explorer/iot-tree-table-demo.tsx index 59599b621..ff0fed75e 100644 --- a/packages/components/src/testing/resource-explorer/iot-tree-table-demo.tsx +++ b/packages/components/src/testing/resource-explorer/iot-tree-table-demo.tsx @@ -247,7 +247,9 @@ export class IotTreeTableDemo { filterPlaceholder={this.filterPlaceholder} onExpandChildren={(node) => { if (!loaded.has(node.id)) { - const newItems = allItems.filter((item) => item.parentId === node.id); + const newItems = allItems.filter( + (item) => item.parentId === node.id + ); this.items = [...this.items, ...newItems]; } loaded.set(node.id, true); diff --git a/packages/components/src/testing/update.ts b/packages/components/src/testing/update.ts index acf7c6cfe..c2a9b6a2c 100644 --- a/packages/components/src/testing/update.ts +++ b/packages/components/src/testing/update.ts @@ -6,4 +6,5 @@ * For certain situations where we wish to ensure that T maintains it's type rather * than mutating types. This is the reason for this helper function. */ -export const update = (target: T, source: Partial): T => Object.assign(target, source); +export const update = (target: T, source: Partial): T => + Object.assign(target, source); diff --git a/packages/core-util/jest.config.ts b/packages/core-util/jest.config.ts index c6535edf4..404eaef92 100644 --- a/packages/core-util/jest.config.ts +++ b/packages/core-util/jest.config.ts @@ -12,7 +12,7 @@ const config = { statements: 54, branches: 50, functions: 51, - lines: 51, + lines: 50, }, }, }; diff --git a/packages/core-util/src/sdks/endpointProvider.ts b/packages/core-util/src/sdks/endpointProvider.ts index 918b1defc..dbfac8601 100644 --- a/packages/core-util/src/sdks/endpointProvider.ts +++ b/packages/core-util/src/sdks/endpointProvider.ts @@ -7,7 +7,9 @@ export const DEFAULT_PARTITION = 'com'; const constructEndpoint = (subDomain: string) => ([awsRegion, awsPartition]: [string, string]): EndpointV2 => ({ - url: new URL(`https://${subDomain}.${awsRegion}.amazonaws.${awsPartition}/`), + url: new URL( + `https://${subDomain}.${awsRegion}.amazonaws.${awsPartition}/` + ), }); export const getEndpointPovider = ({ @@ -23,12 +25,17 @@ export const getEndpointPovider = ({ let partition = Promise.resolve(DEFAULT_PARTITION); if (awsRegion) { - region = typeof awsRegion === 'string' ? Promise.resolve(awsRegion) : awsRegion(); + region = + typeof awsRegion === 'string' ? Promise.resolve(awsRegion) : awsRegion(); } if (awsPartition) { - partition = typeof awsPartition === 'string' ? Promise.resolve(awsPartition) : awsPartition(); + partition = + typeof awsPartition === 'string' + ? Promise.resolve(awsPartition) + : awsPartition(); } - return () => Promise.all([region, partition]).then(constructEndpoint(subDomain)); + return () => + Promise.all([region, partition]).then(constructEndpoint(subDomain)); }; diff --git a/packages/core-util/src/sdks/events.ts b/packages/core-util/src/sdks/events.ts index 4b3ee5688..822db3f11 100644 --- a/packages/core-util/src/sdks/events.ts +++ b/packages/core-util/src/sdks/events.ts @@ -21,7 +21,9 @@ export const eventsSdk = ({ credentials, }); -export const getIotEventsClient = (input: SiteWiseDataSourceInitalization): IoTEventsClient => { +export const getIotEventsClient = ( + input: SiteWiseDataSourceInitalization +): IoTEventsClient => { const { iotEventsClient, awsCredentials, awsRegion } = input; if (iotEventsClient) { diff --git a/packages/core-util/src/sdks/number.ts b/packages/core-util/src/sdks/number.ts index 6b40943cd..fab84a6dd 100644 --- a/packages/core-util/src/sdks/number.ts +++ b/packages/core-util/src/sdks/number.ts @@ -8,7 +8,12 @@ import type { Primitive } from '@iot-app-kit/core'; * round(.02345678) => 0.02346 */ export const round = (num: number, precision = 4): number => { - if (Number.isNaN(num) || num === Infinity || num === -Infinity || precision <= 0) { + if ( + Number.isNaN(num) || + num === Infinity || + num === -Infinity || + precision <= 0 + ) { return num; } @@ -18,7 +23,9 @@ export const round = (num: number, precision = 4): number => { const integer = Math.trunc(num); const decimal = num - integer; - return Number((integer + Number(decimal.toFixed(precision))).toFixed(precision)); + return Number( + (integer + Number(decimal.toFixed(precision))).toFixed(precision) + ); }; /** diff --git a/packages/core-util/src/sdks/sitewise.ts b/packages/core-util/src/sdks/sitewise.ts index c66c8fe7a..c6e61582e 100644 --- a/packages/core-util/src/sdks/sitewise.ts +++ b/packages/core-util/src/sdks/sitewise.ts @@ -21,7 +21,9 @@ export const sitewiseSdk = ({ credentials, }); -export const getSiteWiseClient = (input: SiteWiseDataSourceInitalization): IoTSiteWiseClient => { +export const getSiteWiseClient = ( + input: SiteWiseDataSourceInitalization +): IoTSiteWiseClient => { const { iotSiteWiseClient, awsCredentials, awsRegion } = input; if (iotSiteWiseClient) { diff --git a/packages/core/src/common/constants.ts b/packages/core/src/common/constants.ts index 7b5f45005..0676011cf 100644 --- a/packages/core/src/common/constants.ts +++ b/packages/core/src/common/constants.ts @@ -1,4 +1,9 @@ -import type { ComparisonOperator, DataType, StatusIconType, StreamType } from '../data-module/types'; +import type { + ComparisonOperator, + DataType, + StatusIconType, + StreamType, +} from '../data-module/types'; export const DATA_TYPE: { [dataType in DataType]: DataType } = { NUMBER: 'NUMBER', @@ -12,7 +17,9 @@ export const STREAM_TYPE: { [streamType in StreamType]: StreamType } = { ALARM_THRESHOLD: 'ALARM_THRESHOLD', }; -export const COMPARISON_OPERATOR: { [comparisonOperator in ComparisonOperator]: ComparisonOperator } = { +export const COMPARISON_OPERATOR: { + [comparisonOperator in ComparisonOperator]: ComparisonOperator; +} = { LT: 'LT', GT: 'GT', LTE: 'LTE', @@ -21,7 +28,9 @@ export const COMPARISON_OPERATOR: { [comparisonOperator in ComparisonOperator]: CONTAINS: 'CONTAINS', }; -export const STATUS_ICON_TYPE: { [statusIconType in StatusIconType]: StatusIconType } = { +export const STATUS_ICON_TYPE: { + [statusIconType in StatusIconType]: StatusIconType; +} = { error: 'error', active: 'active', normal: 'normal', diff --git a/packages/core/src/common/dataFilters.spec.ts b/packages/core/src/common/dataFilters.spec.ts index f0aba345b..15b589712 100644 --- a/packages/core/src/common/dataFilters.spec.ts +++ b/packages/core/src/common/dataFilters.spec.ts @@ -14,12 +14,19 @@ describe('getDataBeforeDate', () => { }); it('returns empty list when one point is given, and is after the date', () => { - expect(getDataBeforeDate([{ x: new Date(2002, 0, 0).getTime(), y: 100 }], new Date(DATE))).toBeEmpty(); + expect( + getDataBeforeDate( + [{ x: new Date(2002, 0, 0).getTime(), y: 100 }], + new Date(DATE) + ) + ).toBeEmpty(); }); it('returns data point when given one data point at the date', () => { const DATA_POINT = { x: DATE, y: 100 }; - expect(getDataBeforeDate([DATA_POINT], new Date(DATE))).toEqual([DATA_POINT]); + expect(getDataBeforeDate([DATA_POINT], new Date(DATE))).toEqual([ + DATA_POINT, + ]); }); it('returns empty list when all dates are after the given date', () => { diff --git a/packages/core/src/common/dataFilters.ts b/packages/core/src/common/dataFilters.ts index 0c0c7b214..9fb28b4f0 100644 --- a/packages/core/src/common/dataFilters.ts +++ b/packages/core/src/common/dataFilters.ts @@ -35,7 +35,9 @@ export const getVisibleData = ( const start = isHistoricalViewport(viewport) ? new Date(viewport.start) : new Date(Date.now() - parseDuration(viewport.duration)); - const end = isHistoricalViewport(viewport) ? new Date(viewport.end) : new Date(); + const end = isHistoricalViewport(viewport) + ? new Date(viewport.end) + : new Date(); // If there is no data if (data.length === 0) { @@ -52,8 +54,14 @@ export const getVisibleData = ( // Otherwise return all the data within the viewport, plus an additional single data point that falls outside of // the viewport in either direction. - const startIndex = Math.max(pointBisector.left(data, start) - (includeBoundaryPoints ? 1 : 0), 0); - const endIndex = Math.min(pointBisector.right(data, end) - (includeBoundaryPoints ? 0 : 1), data.length - 1); + const startIndex = Math.max( + pointBisector.left(data, start) - (includeBoundaryPoints ? 1 : 0), + 0 + ); + const endIndex = Math.min( + pointBisector.right(data, end) - (includeBoundaryPoints ? 0 : 1), + data.length - 1 + ); return data.slice(startIndex, endIndex + 1); }; @@ -62,7 +70,10 @@ export const getVisibleData = ( * * Assumes data is ordered chronologically. */ -export const getDataBeforeDate = (data: DataPoint[], date: Date): DataPoint[] => { +export const getDataBeforeDate = ( + data: DataPoint[], + date: Date +): DataPoint[] => { // If there is no data if (data.length === 0) { return []; @@ -74,6 +85,9 @@ export const getDataBeforeDate = (data: DataPoint[], dat // Otherwise return all the data within the viewport, plus an additional single data point that falls outside of // the viewport in either direction. - const endIndex = Math.min(pointBisector.right(data, date) - 1, data.length - 1); + const endIndex = Math.min( + pointBisector.right(data, date) - 1, + data.length - 1 + ); return data.slice(0, endIndex + 1); }; diff --git a/packages/core/src/common/dataStreamId.spec.ts b/packages/core/src/common/dataStreamId.spec.ts index 38e82951f..4636be868 100644 --- a/packages/core/src/common/dataStreamId.spec.ts +++ b/packages/core/src/common/dataStreamId.spec.ts @@ -2,7 +2,9 @@ import { toDataStreamId, toSiteWiseAssetProperty } from './dataStreamId'; describe('toDataStreamId', () => { it('converts property and asset id to a data stream id', () => { - expect(toDataStreamId({ assetId: 'asset-id', propertyId: 'property-id' })).toBe('asset-id---property-id'); + expect( + toDataStreamId({ assetId: 'asset-id', propertyId: 'property-id' }) + ).toBe('asset-id---property-id'); }); }); diff --git a/packages/core/src/common/dataStreamId.ts b/packages/core/src/common/dataStreamId.ts index eee824ee4..e9d87b3c5 100644 --- a/packages/core/src/common/dataStreamId.ts +++ b/packages/core/src/common/dataStreamId.ts @@ -1,12 +1,21 @@ // Something that is not likely to occur in any UUID implementation const ID_SEPARATOR = '---'; -export const toDataStreamId = ({ assetId, propertyId }: { assetId: string; propertyId: string }): string => - `${assetId}${ID_SEPARATOR}${propertyId}`; +export const toDataStreamId = ({ + assetId, + propertyId, +}: { + assetId: string; + propertyId: string; +}): string => `${assetId}${ID_SEPARATOR}${propertyId}`; -export const toSiteWiseAssetProperty = (dataStreamId: string): { assetId: string; propertyId: string } => { +export const toSiteWiseAssetProperty = ( + dataStreamId: string +): { assetId: string; propertyId: string } => { if (!dataStreamId.includes(ID_SEPARATOR)) { - throw new Error(`Invalid id ${dataStreamId}, expected to find the separator ${ID_SEPARATOR} but it was not found`); + throw new Error( + `Invalid id ${dataStreamId}, expected to find the separator ${ID_SEPARATOR} but it was not found` + ); } const [assetId, propertyId] = dataStreamId.split(ID_SEPARATOR); diff --git a/packages/core/src/common/intervalStructure.spec.ts b/packages/core/src/common/intervalStructure.spec.ts index 2df9be144..39536ba86 100755 --- a/packages/core/src/common/intervalStructure.spec.ts +++ b/packages/core/src/common/intervalStructure.spec.ts @@ -1,4 +1,10 @@ -import { addInterval, intersect, isContained, mergeItems, subtractIntervals } from './intervalStructure'; +import { + addInterval, + intersect, + isContained, + mergeItems, + subtractIntervals, +} from './intervalStructure'; import type { IntervalStructure } from './intervalStructure'; import { dataPointCompare } from '../data-module/data-cache/caching/caching'; @@ -147,7 +153,10 @@ describe('subtract intervals', () => { describe('is contained', () => { it('returns true', () => { - const cache: IntervalStructure = { intervals: [[883526400000, 915062400000]], items: [[]] }; + const cache: IntervalStructure = { + intervals: [[883526400000, 915062400000]], + items: [[]], + }; expect(isContained(cache, [946598400000, 978220800000])).toBeFalse(); }); @@ -249,38 +258,54 @@ describe('merge items', () => { }); it('combines two collections which share a boundary', () => { - expect(mergeItems([1, 2], [2, 3, 4], numericalCompare)).toEqual([1, 2, 3, 4]); + expect(mergeItems([1, 2], [2, 3, 4], numericalCompare)).toEqual([ + 1, 2, 3, 4, + ]); }); it('merged first items into second when second contains all of first', () => { - expect(mergeItems([1, 2], [0, 1, 2, 3, 4], numericalCompare)).toEqual([0, 1, 2, 3, 4]); + expect(mergeItems([1, 2], [0, 1, 2, 3, 4], numericalCompare)).toEqual([ + 0, 1, 2, 3, 4, + ]); }); it('merged first items into second when first contains all of second', () => { - expect(mergeItems([0, 1, 2, 3, 4], [1, 2], numericalCompare)).toEqual([0, 1, 2, 3, 4]); + expect(mergeItems([0, 1, 2, 3, 4], [1, 2], numericalCompare)).toEqual([ + 0, 1, 2, 3, 4, + ]); }); it('merges interval with data not present in second interval, maintains all new data', () => { - expect(mergeItems([0, 1, 2, 3, 4], [0, 4], numericalCompare)).toEqual([0, 1, 2, 3, 4]); + expect(mergeItems([0, 1, 2, 3, 4], [0, 4], numericalCompare)).toEqual([ + 0, 1, 2, 3, 4, + ]); }); it('both intervals contain data the other one does not', () => { - expect(mergeItems([0, 1, 2, 3, 4], [0, 4, 5], numericalCompare)).toEqual([0, 1, 2, 3, 4, 5]); + expect(mergeItems([0, 1, 2, 3, 4], [0, 4, 5], numericalCompare)).toEqual([ + 0, 1, 2, 3, 4, 5, + ]); }); it('partially overlapping intervals where the first interval contains data not present within the second intervals range', () => { - expect(mergeItems([0, 1, 2, 3, 4, 5], [3, 5, 7], numericalCompare)).toEqual([0, 1, 2, 3, 4, 5, 7]); + expect(mergeItems([0, 1, 2, 3, 4, 5], [3, 5, 7], numericalCompare)).toEqual( + [0, 1, 2, 3, 4, 5, 7] + ); }); it('partially overlapping intervals where the second interval contains data not present within the first intervals range', () => { - expect(mergeItems([3, 5, 7], [0, 1, 2, 3, 4, 5], numericalCompare)).toEqual([0, 1, 2, 3, 5, 7]); + expect(mergeItems([3, 5, 7], [0, 1, 2, 3, 4, 5], numericalCompare)).toEqual( + [0, 1, 2, 3, 5, 7] + ); }); }); describe('adds item to interval structure', () => { describe('add interval when no merging needs to occur', () => { it('adds item to empty interval structure', () => { - expect(addInterval(INITIAL_STATE, [10, 100], [10], numericalCompare)).toEqual({ + expect( + addInterval(INITIAL_STATE, [10, 100], [10], numericalCompare) + ).toEqual({ intervals: [[10, 100]], items: [[10]], }); @@ -331,10 +356,12 @@ describe('adds item to interval structure', () => { intervals: [[0, 5]], items: [[0, 1, 2, 3, 4, 5]], }; - expect(addInterval(structure, [0, 5], [2.5], numericalCompare)).toEqual({ - intervals: [[0, 5]], - items: [[0, 1, 2, 2.5, 3, 4, 5]], - }); + expect(addInterval(structure, [0, 5], [2.5], numericalCompare)).toEqual( + { + intervals: [[0, 5]], + items: [[0, 1, 2, 2.5, 3, 4, 5]], + } + ); }); it('merges interval with more data than present in containing interval, takes the additional data', () => { @@ -342,7 +369,9 @@ describe('adds item to interval structure', () => { intervals: [[0, 1000]], items: [[1, 55, 121]], }; - expect(addInterval(structure, [10, 100], [55, 56, 57], numericalCompare)).toEqual({ + expect( + addInterval(structure, [10, 100], [55, 56, 57], numericalCompare) + ).toEqual({ intervals: [[0, 1000]], items: [[1, 55, 56, 57, 121]], }); @@ -359,7 +388,14 @@ describe('adds item to interval structure', () => { [11, 15, 20], ], }; - expect(addInterval(structure, [5, 15], [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], numericalCompare)).toEqual({ + expect( + addInterval( + structure, + [5, 15], + [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + numericalCompare + ) + ).toEqual({ intervals: [[0, 20]], items: [[0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 20]], }); @@ -371,7 +407,9 @@ describe('adds item to interval structure', () => { intervals: [[-100, 1000]], items: [[1, 55, 121]], }; - expect(addInterval(structure, [10, 100], [55], numericalCompare)).toEqual(structure); + expect(addInterval(structure, [10, 100], [55], numericalCompare)).toEqual( + structure + ); }); it('merges three intervals into one', () => { @@ -401,7 +439,14 @@ describe('adds item to interval structure', () => { [45, 46], ], }; - expect(addInterval(structure, [0, 50], [5, 6, 25, 26, 35, 45, 46], numericalCompare)).toEqual({ + expect( + addInterval( + structure, + [0, 50], + [5, 6, 25, 26, 35, 45, 46], + numericalCompare + ) + ).toEqual({ intervals: [[0, 50]], items: [[5, 6, 25, 26, 35, 45, 46]], }); @@ -418,7 +463,14 @@ describe('adds item to interval structure', () => { [45, 60], ], }; - expect(addInterval(structure, [0, 50], [5, 6, 25, 26, 35, 45], numericalCompare)).toEqual({ + expect( + addInterval( + structure, + [0, 50], + [5, 6, 25, 26, 35, 45], + numericalCompare + ) + ).toEqual({ intervals: [[0, 60]], items: [[5, 6, 25, 26, 35, 45, 60]], }); @@ -435,7 +487,9 @@ describe('adds item to interval structure', () => { [40, 60], ], }; - expect(addInterval(structure, [25, 45], [30, 35, 40], numericalCompare)).toEqual({ + expect( + addInterval(structure, [25, 45], [30, 35, 40], numericalCompare) + ).toEqual({ intervals: [[20, 60]], items: [[20, 30, 35, 40, 60]], }); diff --git a/packages/core/src/common/intervalStructure.ts b/packages/core/src/common/intervalStructure.ts index d074b75aa..bd1bb4f32 100755 --- a/packages/core/src/common/intervalStructure.ts +++ b/packages/core/src/common/intervalStructure.ts @@ -17,26 +17,52 @@ export type IntervalStructure = { */ type CompareFn = (a: T, b: T) => number; -const toObjectNotation = ([start, end]: Interval): IntervalSE => ({ start, end }); -const toIntervalNotation = ({ start, end }: IntervalSE): Interval => [start, end] as Interval; - -const isBeforeInterval = ([_, aEnd]: Interval, [bStart]: Interval): boolean => aEnd < bStart; -const isAfterInterval = ([aStart]: Interval, [_, bEnd]: Interval): boolean => aStart > bEnd; -const isIntersecting = (a: Interval, b: Interval): boolean => !isBeforeInterval(a, b) && !isAfterInterval(a, b); - -export const isContained = (structure: IntervalStructure, interval: Interval): boolean => - structure.intervals.some(([start, end]) => start <= interval[0] && end >= interval[1]); +const toObjectNotation = ([start, end]: Interval): IntervalSE => ({ + start, + end, +}); +const toIntervalNotation = ({ start, end }: IntervalSE): Interval => + [start, end] as Interval; + +const isBeforeInterval = ([_, aEnd]: Interval, [bStart]: Interval): boolean => + aEnd < bStart; +const isAfterInterval = ([aStart]: Interval, [_, bEnd]: Interval): boolean => + aStart > bEnd; +const isIntersecting = (a: Interval, b: Interval): boolean => + !isBeforeInterval(a, b) && !isAfterInterval(a, b); + +export const isContained = ( + structure: IntervalStructure, + interval: Interval +): boolean => + structure.intervals.some( + ([start, end]) => start <= interval[0] && end >= interval[1] + ); -export const intersect = (aIntervals: Interval[], bIntervals: Interval[]): Interval[] => { +export const intersect = ( + aIntervals: Interval[], + bIntervals: Interval[] +): Interval[] => { const intersectedIntervals = simplify( - intersectFn(aIntervals.map(toObjectNotation), bIntervals.map(toObjectNotation)) + intersectFn( + aIntervals.map(toObjectNotation), + bIntervals.map(toObjectNotation) + ) ); return intersectedIntervals.map(toIntervalNotation); }; -export const subtractIntervals = (interval: Interval, intervals: Interval[]): Interval[] => { - const sortedIntervals = intervals.sort((i1, i2) => i1[0] - i2[0]).map(toObjectNotation); - const subtractedIntervals = substract([toObjectNotation(interval)], simplify(sortedIntervals)); +export const subtractIntervals = ( + interval: Interval, + intervals: Interval[] +): Interval[] => { + const sortedIntervals = intervals + .sort((i1, i2) => i1[0] - i2[0]) + .map(toObjectNotation); + const subtractedIntervals = substract( + [toObjectNotation(interval)], + simplify(sortedIntervals) + ); return simplify(subtractedIntervals) .filter((inter) => inter.start < inter.end) .map(toIntervalNotation); @@ -71,7 +97,11 @@ const uniqByKeepLast = (data: T[][], key: string) => { * * If `aItems` and `bItems` have overlap, always take the items specified in `aItems` */ -export const mergeItems = (aItems: T[], bItems: T[], compare: CompareFn) => { +export const mergeItems = ( + aItems: T[], + bItems: T[], + compare: CompareFn +) => { // Empty items edge cases if (aItems.length === 0) { return bItems; @@ -87,21 +117,32 @@ export const mergeItems = (aItems: T[], bItems: T[], compare: CompareFn) = return [...aItems, ...bItems]; } // Fully contained edge cases - if (compare(aItems[0], bItems[0]) <= 0 && compare(aItems[aItems.length - 1], bItems[bItems.length - 1]) >= 0) { + if ( + compare(aItems[0], bItems[0]) <= 0 && + compare(aItems[aItems.length - 1], bItems[bItems.length - 1]) >= 0 + ) { // `aItems` fully contains `bItems` return aItems; } - if (compare(bItems[0], aItems[0]) <= 0 && compare(bItems[bItems.length - 1], aItems[aItems.length - 1]) >= 0) { + if ( + compare(bItems[0], aItems[0]) <= 0 && + compare(bItems[bItems.length - 1], aItems[aItems.length - 1]) >= 0 + ) { // `bItems` fully contains `aItems` const itemsBeforeA = bItems.filter((item) => compare(item, aItems[0]) < 0); - const itemsAfterA = bItems.filter((item) => compare(item, aItems[aItems.length - 1]) > 0); + const itemsAfterA = bItems.filter( + (item) => compare(item, aItems[aItems.length - 1]) > 0 + ); return [...itemsBeforeA, ...aItems, ...itemsAfterA]; } // Merge items if (compare(aItems[0], bItems[0]) < 0) { // `aItems` interval begins before `bItems` - return [...aItems, ...bItems.filter((x) => compare(x, aItems[aItems.length - 1]) > 0)]; + return [ + ...aItems, + ...bItems.filter((x) => compare(x, aItems[aItems.length - 1]) > 0), + ]; } // `bItems` interval begins before `aItems` return [...bItems.filter((x) => compare(x, aItems[0]) < 0), ...aItems]; @@ -122,8 +163,14 @@ export const addInterval = ( // Combine all overlapping intervals into a single interval const combinedInterval = overlappingIntervals.reduce( - (mergedInterval: Interval, { interval: currInterval }: { interval: Interval; index: number }) => - [Math.min(mergedInterval[0], currInterval[0]), Math.max(mergedInterval[1], currInterval[1])] as Interval, + ( + mergedInterval: Interval, + { interval: currInterval }: { interval: Interval; index: number } + ) => + [ + Math.min(mergedInterval[0], currInterval[0]), + Math.max(mergedInterval[1], currInterval[1]), + ] as Interval, interval ); @@ -142,7 +189,11 @@ export const addInterval = ( // Apply update const updatedIntervals = [...intervalStructure.intervals]; - updatedIntervals.splice(insertIndex, overlappingIntervals.length, combinedInterval); + updatedIntervals.splice( + insertIndex, + overlappingIntervals.length, + combinedInterval + ); const updatedItems = [...intervalStructure.items]; updatedItems.splice(insertIndex, overlappingIntervals.length, combinedItems); diff --git a/packages/core/src/common/number.ts b/packages/core/src/common/number.ts index 15cc1ee11..df32b7d3d 100755 --- a/packages/core/src/common/number.ts +++ b/packages/core/src/common/number.ts @@ -21,7 +21,9 @@ export const round = (num: number): number => { const integer = Math.trunc(num); // in case of negative number, we need to remove the first 3 characters from decimal string eg. -0.123 => 123 - const decimal = (num - integer).toFixed(MAX_PRECISION).substring(num !== absoluteValue ? 3 : 2); + const decimal = (num - integer) + .toFixed(MAX_PRECISION) + .substring(num !== absoluteValue ? 3 : 2); return Number(`${integer}.${decimal}`); }; diff --git a/packages/core/src/common/predicates.spec.ts b/packages/core/src/common/predicates.spec.ts index fb6fed0dd..3bce306b5 100644 --- a/packages/core/src/common/predicates.spec.ts +++ b/packages/core/src/common/predicates.spec.ts @@ -7,7 +7,10 @@ import { isHistoricalViewport, } from './predicates'; import type { DataStream } from '../data-module/types'; -import type { HistoricalViewport, DurationViewport } from '../data-module/data-cache/requestTypes'; +import type { + HistoricalViewport, + DurationViewport, +} from '../data-module/data-cache/requestTypes'; describe('isDefined', () => { it('returns false when passed null', () => { @@ -83,9 +86,15 @@ describe('isNumberDataStream', () => { ], }; - const EMPTY_STRING_STREAM: DataStream = { ...STRING_DATA_STREAM, data: [] }; + const EMPTY_STRING_STREAM: DataStream = { + ...STRING_DATA_STREAM, + data: [], + }; - const EMPTY_NUMBER_STREAM: DataStream = { ...NUMBER_DATA_STREAM, data: [] }; + const EMPTY_NUMBER_STREAM: DataStream = { + ...NUMBER_DATA_STREAM, + data: [], + }; it('returns true when given empty number stream', () => { expect(isNumberDataStream(EMPTY_NUMBER_STREAM)).toBeTrue(); @@ -142,10 +151,14 @@ describe('isHistoricalViewport', () => { }); it('returns false when the end date is missing', () => { - expect(isHistoricalViewport({ start: new Date() } as unknown as DurationViewport)).toBeFalse(); + expect( + isHistoricalViewport({ start: new Date() } as unknown as DurationViewport) + ).toBeFalse(); }); it('returns false when the start date is missing', () => { - expect(isHistoricalViewport({ end: new Date() } as unknown as DurationViewport)).toBeFalse(); + expect( + isHistoricalViewport({ end: new Date() } as unknown as DurationViewport) + ).toBeFalse(); }); }); diff --git a/packages/core/src/common/predicates.ts b/packages/core/src/common/predicates.ts index 22d7e4b14..07c4593dd 100644 --- a/packages/core/src/common/predicates.ts +++ b/packages/core/src/common/predicates.ts @@ -1,6 +1,9 @@ import { DATA_TYPE } from '../common/constants'; import type { DataStream, DataType } from '../data-module/types'; -import type { HistoricalViewport, Viewport } from '../data-module/data-cache/requestTypes'; +import type { + HistoricalViewport, + Viewport, +} from '../data-module/data-cache/requestTypes'; /** * Predicate Utilities @@ -30,7 +33,8 @@ import type { HistoricalViewport, Viewport } from '../data-module/data-cache/req * */ -export const isDefined = (value: T | null | undefined): value is T => value != null; +export const isDefined = (value: T | null | undefined): value is T => + value != null; export const isValid = (predicate: (t: Partial) => boolean) => @@ -41,12 +45,16 @@ export const isValid = export const isSupportedDataType = (supportsString: boolean) => ({ dataType }: { dataType: DataType }) => - (supportsString && dataType === DATA_TYPE.STRING) || dataType !== DATA_TYPE.STRING; + (supportsString && dataType === DATA_TYPE.STRING) || + dataType !== DATA_TYPE.STRING; -export const isNumberDataStream = (stream: DataStream): stream is DataStream => - stream.dataType === DATA_TYPE.NUMBER; +export const isNumberDataStream = ( + stream: DataStream +): stream is DataStream => stream.dataType === DATA_TYPE.NUMBER; -export const isNumber = (val: T | number): val is number => typeof val === 'number'; +export const isNumber = (val: T | number): val is number => + typeof val === 'number'; -export const isHistoricalViewport = (viewport: Viewport): viewport is HistoricalViewport => - 'start' in viewport && 'end' in viewport; +export const isHistoricalViewport = ( + viewport: Viewport +): viewport is HistoricalViewport => 'start' in viewport && 'end' in viewport; diff --git a/packages/core/src/common/time.ts b/packages/core/src/common/time.ts index ff058e2a2..7c061587b 100644 --- a/packages/core/src/common/time.ts +++ b/packages/core/src/common/time.ts @@ -65,7 +65,11 @@ export const convertMS = ( }; }; -export const displayDate = (date: Date, resolution: number, { start, end }: { start: Date; end: Date }): string => { +export const displayDate = ( + date: Date, + resolution: number, + { start, end }: { start: Date; end: Date } +): string => { const viewportDurationMS = end.getTime() - start.getTime(); if (resolution < HOUR_IN_MS) { if (viewportDurationMS < MINUTE_IN_MS) { diff --git a/packages/core/src/common/types.ts b/packages/core/src/common/types.ts index 3612f8346..7a672eeb8 100644 --- a/packages/core/src/common/types.ts +++ b/packages/core/src/common/types.ts @@ -1,5 +1,13 @@ -import type { TimeSeriesDataRequest, Viewport } from '../data-module/data-cache/requestTypes'; -import type { ComparisonOperator, DataStreamId, StatusIconType, TimeSeriesData } from '../data-module/types'; +import type { + TimeSeriesDataRequest, + Viewport, +} from '../data-module/data-cache/requestTypes'; +import type { + ComparisonOperator, + DataStreamId, + StatusIconType, + TimeSeriesData, +} from '../data-module/types'; export type ErrorDetails = { msg: string; type?: string; status?: string }; @@ -32,7 +40,8 @@ export interface Query { toQueryString(): string; } -export interface TimeQuery extends Query { +export interface TimeQuery + extends Query { build(sessionId: string, params?: Params): ProviderWithViewport; } @@ -42,11 +51,15 @@ export interface TreeProvider extends Provider { collapse(branch: Branch): void; } -export interface TreeQuery extends Query { +export interface TreeQuery + extends Query { build(sessionId: string, params?: Params): TreeProvider; } -export type TimeSeriesDataQuery = TimeQuery; +export type TimeSeriesDataQuery = TimeQuery< + TimeSeriesData[], + TimeSeriesDataRequest +>; export type DataModuleSession = { close: () => void; @@ -82,7 +95,8 @@ export interface Annotation { id?: string; } -export interface Threshold extends Annotation { +export interface Threshold + extends Annotation { comparisonOperator: ComparisonOperator; severity?: number; dataStreamIds?: DataStreamId[]; diff --git a/packages/core/src/common/viewport.spec.ts b/packages/core/src/common/viewport.spec.ts index ae6c35747..fdd2ddd85 100644 --- a/packages/core/src/common/viewport.spec.ts +++ b/packages/core/src/common/viewport.spec.ts @@ -8,13 +8,17 @@ const mockCurrentTime = (mockedDate: Date) => { describe('viewportStart', () => { it('returns start date if one is present', () => { const START = new Date(2000, 0, 0); - expect(viewportStartDate({ start: START, end: new Date() })).toStrictEqual(START); + expect(viewportStartDate({ start: START, end: new Date() })).toStrictEqual( + START + ); }); it('returns start date when only the duration is present', () => { const TIME = new Date(2000, 0, 1); mockCurrentTime(TIME); - expect(viewportStartDate({ duration: DAY_IN_MS })).toEqual(new Date(Date.now() - DAY_IN_MS)); + expect(viewportStartDate({ duration: DAY_IN_MS })).toEqual( + new Date(Date.now() - DAY_IN_MS) + ); }); }); diff --git a/packages/core/src/common/viewport.ts b/packages/core/src/common/viewport.ts index 80e25f2b0..a440794df 100644 --- a/packages/core/src/common/viewport.ts +++ b/packages/core/src/common/viewport.ts @@ -2,11 +2,22 @@ import { isHistoricalViewport } from './predicates'; import { parseDuration } from './time'; import type { Viewport } from '../data-module/data-cache/requestTypes'; -export const viewportStartDate = (viewport: Viewport, currentDate?: Date): Date => +export const viewportStartDate = ( + viewport: Viewport, + currentDate?: Date +): Date => isHistoricalViewport(viewport) ? new Date(viewport.start) - : new Date((currentDate?.getTime() || Date.now()) - parseDuration(viewport.duration)); + : new Date( + (currentDate?.getTime() || Date.now()) - + parseDuration(viewport.duration) + ); -export const viewportEndDate = (viewport: Viewport, currentDate?: Date): Date => { - return isHistoricalViewport(viewport) ? new Date(viewport.end) : currentDate || new Date(Date.now()); +export const viewportEndDate = ( + viewport: Viewport, + currentDate?: Date +): Date => { + return isHistoricalViewport(viewport) + ? new Date(viewport.end) + : currentDate || new Date(Date.now()); }; diff --git a/packages/core/src/data-module/TimeSeriesDataModule.spec.ts b/packages/core/src/data-module/TimeSeriesDataModule.spec.ts index a43603c35..25f717f3b 100644 --- a/packages/core/src/data-module/TimeSeriesDataModule.spec.ts +++ b/packages/core/src/data-module/TimeSeriesDataModule.spec.ts @@ -2,20 +2,33 @@ import flushPromises from 'flush-promises'; import { DATA_STREAM } from '../mockWidgetProperties'; import { createMockSiteWiseDataSource } from '../__mocks__'; import * as caching from './data-cache/caching/caching'; -import { HOUR_IN_MS, MINUTE_IN_MS, MONTH_IN_MS, SECOND_IN_MS } from '../common/time'; +import { + HOUR_IN_MS, + MINUTE_IN_MS, + MONTH_IN_MS, + SECOND_IN_MS, +} from '../common/time'; import { TimeSeriesDataModule } from './TimeSeriesDataModule'; -import { toSiteWiseAssetProperty, toDataStreamId } from '../common/dataStreamId'; +import { + toSiteWiseAssetProperty, + toDataStreamId, +} from '../common/dataStreamId'; import type { MockSiteWiseQuery } from '../__mocks__'; import type { DataSource, DataPoint } from './types'; -import type { TimeSeriesDataRequest, TimeSeriesDataRequestSettings } from './data-cache/requestTypes'; +import type { + TimeSeriesDataRequest, + TimeSeriesDataRequestSettings, +} from './data-cache/requestTypes'; import type { DataStreamsStore, DataStreamStore } from './data-cache/types'; import Mock = jest.Mock; const { EMPTY_CACHE } = caching; -const { propertyId: PROPERTY_ID, assetId: ASSET_ID } = toSiteWiseAssetProperty(DATA_STREAM.id); +const { propertyId: PROPERTY_ID, assetId: ASSET_ID } = toSiteWiseAssetProperty( + DATA_STREAM.id +); const DATA_STREAM_QUERY: MockSiteWiseQuery = { assets: [ @@ -58,7 +71,9 @@ it('subscribes to an empty set of queries', async () => { describe('update subscription', () => { it('provides new data streams when subscription is updated', async () => { - const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); + const dataSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); const dataModule = new TimeSeriesDataModule(dataSource); const timeSeriesCallback = jest.fn(); @@ -147,7 +162,10 @@ describe('initial request', () => { dataModule.subscribeToDataStreams( { queries: [query], - request: { viewport: { start: START, end: END }, settings: { fetchFromStartToEnd: true } }, + request: { + viewport: { start: START, end: END }, + settings: { fetchFromStartToEnd: true }, + }, }, timeSeriesCallback ); @@ -189,7 +207,14 @@ describe('initial request', () => { const someDataType = 'STRING'; const dataSource: DataSource = createMockSiteWiseDataSource({ - dataStreams: [{ ...DATA_STREAM, meta: someMetaStuff, name: someName, dataType: someDataType }], + dataStreams: [ + { + ...DATA_STREAM, + meta: someMetaStuff, + name: someName, + dataType: someDataType, + }, + ], }); const dataModule = new TimeSeriesDataModule(dataSource); @@ -198,7 +223,10 @@ describe('initial request', () => { dataModule.subscribeToDataStreams( { queries: [query], - request: { viewport: { start: START, end: END }, settings: { fetchFromStartToEnd: true } }, + request: { + viewport: { start: START, end: END }, + settings: { fetchFromStartToEnd: true }, + }, }, timeSeriesCallback ); @@ -242,7 +270,10 @@ describe('initial request', () => { dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], - request: { viewport: { start: START, end: END }, settings: { fetchFromStartToEnd: true } }, + request: { + viewport: { start: START, end: END }, + settings: { fetchFromStartToEnd: true }, + }, }, timeSeriesCallback ); @@ -267,7 +298,10 @@ describe('initial request', () => { expect(dataSource.initiateRequest).toBeCalledWith( expect.objectContaining({ query: DATA_STREAM_QUERY, - request: { viewport: { start: START, end: END }, settings: { fetchFromStartToEnd: true } }, + request: { + viewport: { start: START, end: END }, + settings: { fetchFromStartToEnd: true }, + }, }), [ expect.objectContaining({ @@ -282,7 +316,9 @@ describe('initial request', () => { }); it('subscribes to a single data stream', async () => { - const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); + const dataSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); const dataModule = new TimeSeriesDataModule(dataSource); const { propertyId, assetId } = toSiteWiseAssetProperty(DATA_STREAM.id); @@ -330,7 +366,9 @@ it('subscribes to a single data stream', async () => { }); it('requests data from a custom data source', async () => { - const customSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); + const customSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); const { propertyId, assetId } = toSiteWiseAssetProperty(DATA_STREAM.id); const dataModule = new TimeSeriesDataModule(customSource); @@ -502,7 +540,9 @@ it('only requests latest value', async () => { const onRequestData = jest.fn(); const source = createMockSiteWiseDataSource({ onRequestData }); - const LATEST_VALUE_REQUEST_SETTINGS: TimeSeriesDataRequestSettings = { fetchMostRecentBeforeEnd: true }; + const LATEST_VALUE_REQUEST_SETTINGS: TimeSeriesDataRequestSettings = { + fetchMostRecentBeforeEnd: true, + }; const dataModule = new TimeSeriesDataModule(source); const onSuccess = jest.fn(); @@ -539,7 +579,11 @@ it('only requests latest value', async () => { }); describe('error handling', () => { - const ERR = { msg: 'An error has occurred!', type: 'ResourceNotFoundException', status: '404' }; + const ERR = { + msg: 'An error has occurred!', + type: 'ResourceNotFoundException', + status: '404', + }; const CACHE_WITH_ERROR: DataStreamsStore = { [DATA_STREAM.id]: { @@ -572,9 +616,13 @@ describe('error handling', () => { }; it('provides a data stream which has an error associated with it on initial subscription', async () => { - const customSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); + const customSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); - const dataModule = new TimeSeriesDataModule(customSource, { initialDataCache: CACHE_WITH_ERROR }); + const dataModule = new TimeSeriesDataModule(customSource, { + initialDataCache: CACHE_WITH_ERROR, + }); const timeSeriesCallback = jest.fn(); const START = new Date(2000, 0, 0); @@ -606,9 +654,13 @@ describe('error handling', () => { }); it('does not re-request a data stream with an error associated with it', async () => { - const customSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); + const customSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); - const dataModule = new TimeSeriesDataModule(customSource, { initialDataCache: CACHE_WITH_ERROR }); + const dataModule = new TimeSeriesDataModule(customSource, { + initialDataCache: CACHE_WITH_ERROR, + }); const timeSeriesCallback = jest.fn(); dataModule.subscribeToDataStreams( @@ -633,9 +685,13 @@ describe('error handling', () => { }); it('does request a data stream which has no error associated with it', async () => { - const customSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); + const customSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); - const dataModule = new TimeSeriesDataModule(customSource, { initialDataCache: CACHE_WITHOUT_ERROR }); + const dataModule = new TimeSeriesDataModule(customSource, { + initialDataCache: CACHE_WITHOUT_ERROR, + }); const timeSeriesCallback = jest.fn(); @@ -670,7 +726,9 @@ describe('error handling', () => { describe('caching', () => { it('does not request already cached data', async () => { - const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); + const dataSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); const dataModule = new TimeSeriesDataModule(dataSource); const START_1 = new Date(2000, 1, 0); @@ -683,23 +741,38 @@ describe('caching', () => { const { update } = dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], - request: { viewport: { start: START_1, end: END_1 }, settings: { fetchFromStartToEnd: true } }, + request: { + viewport: { start: START_1, end: END_1 }, + settings: { fetchFromStartToEnd: true }, + }, }, timeSeriesCallback ); await flushPromises(); - await update({ request: { viewport: { start: START_2, end: END_2 }, settings: { fetchFromStartToEnd: true } } }); + await update({ + request: { + viewport: { start: START_2, end: END_2 }, + settings: { fetchFromStartToEnd: true }, + }, + }); (dataSource.initiateRequest as Mock).mockClear(); - await update({ request: { viewport: { start: START_1, end: END_1 }, settings: { fetchFromStartToEnd: true } } }); + await update({ + request: { + viewport: { start: START_1, end: END_1 }, + settings: { fetchFromStartToEnd: true }, + }, + }); expect(dataSource.initiateRequest).not.toBeCalled(); }); it('requests only uncached data', async () => { - const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); + const dataSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); const dataModule = new TimeSeriesDataModule(dataSource); // Order of points through time: @@ -716,7 +789,10 @@ describe('caching', () => { const { update } = dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], - request: { viewport: { start: START_1, end: END_1 }, settings: { fetchFromStartToEnd: true } }, + request: { + viewport: { start: START_1, end: END_1 }, + settings: { fetchFromStartToEnd: true }, + }, }, timeSeriesCallback ); @@ -728,7 +804,10 @@ describe('caching', () => { expect(dataSource.initiateRequest).toHaveBeenCalledWith( expect.objectContaining({ query: DATA_STREAM_QUERY, - request: { viewport: { start: START_1, end: END_1 }, settings: { fetchFromStartToEnd: true } }, + request: { + viewport: { start: START_1, end: END_1 }, + settings: { fetchFromStartToEnd: true }, + }, }), [ expect.objectContaining({ @@ -742,7 +821,12 @@ describe('caching', () => { (dataSource.initiateRequest as Mock).mockClear(); - await update({ request: { viewport: { start: START_2, end: END_2 }, settings: { fetchFromStartToEnd: true } } }); + await update({ + request: { + viewport: { start: START_2, end: END_2 }, + settings: { fetchFromStartToEnd: true }, + }, + }); await flushPromises(); @@ -751,7 +835,10 @@ describe('caching', () => { expect(dataSource.initiateRequest).toHaveBeenCalledWith( expect.objectContaining({ query: DATA_STREAM_QUERY, - request: { viewport: { start: START_2, end: END_2 }, settings: { fetchFromStartToEnd: true } }, + request: { + viewport: { start: START_2, end: END_2 }, + settings: { fetchFromStartToEnd: true }, + }, }), [ expect.objectContaining({ @@ -771,7 +858,9 @@ describe('caching', () => { }); it('immediately request when subscribed to an entirely new time interval not previously requested', async () => { - const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); + const dataSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); const dataModule = new TimeSeriesDataModule(dataSource); const START_1 = new Date(2000, 1, 0); @@ -796,13 +885,19 @@ describe('caching', () => { (dataSource.initiateRequest as Mock).mockClear(); await update({ - request: { viewport: { start: START_2, end: END_2 }, settings: { fetchFromStartToEnd: true } }, + request: { + viewport: { start: START_2, end: END_2 }, + settings: { fetchFromStartToEnd: true }, + }, }); expect(dataSource.initiateRequest).toBeCalledWith( expect.objectContaining({ query: DATA_STREAM_QUERY, - request: { viewport: { start: START_2, end: END_2 }, settings: { fetchFromStartToEnd: true } }, + request: { + viewport: { start: START_2, end: END_2 }, + settings: { fetchFromStartToEnd: true }, + }, }), [ expect.objectContaining({ @@ -816,7 +911,9 @@ describe('caching', () => { }); it('requests already cached data if the default TTL has expired', async () => { - const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); + const dataSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); const dataModule = new TimeSeriesDataModule(dataSource); const END = new Date(); @@ -828,7 +925,11 @@ describe('caching', () => { queries: [DATA_STREAM_QUERY], request: { viewport: { start: START, end: END }, - settings: { fetchFromStartToEnd: true, refreshRate: MINUTE_IN_MS, requestBuffer: 0 }, + settings: { + fetchFromStartToEnd: true, + refreshRate: MINUTE_IN_MS, + requestBuffer: 0, + }, }, }, timeSeriesCallback @@ -846,7 +947,11 @@ describe('caching', () => { request: { // 1 minute time advancement invalidates 3 minutes of cache by default, which is 2 minutes from END_1 viewport: { start: START, end: END }, - settings: { fetchFromStartToEnd: true, refreshRate: MINUTE_IN_MS, requestBuffer: 0 }, + settings: { + fetchFromStartToEnd: true, + refreshRate: MINUTE_IN_MS, + requestBuffer: 0, + }, }, }), [ @@ -869,8 +974,12 @@ describe('caching', () => { }, }; - const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); - const dataModule = new TimeSeriesDataModule(dataSource, { cacheSettings: customCacheSettings }); + const dataSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); + const dataModule = new TimeSeriesDataModule(dataSource, { + cacheSettings: customCacheSettings, + }); const END = new Date(); const START = new Date(END.getTime() - HOUR_IN_MS); @@ -879,7 +988,10 @@ describe('caching', () => { dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], - request: { viewport: { start: START, end: END }, settings: { refreshRate: MINUTE_IN_MS, requestBuffer: 0 } }, + request: { + viewport: { start: START, end: END }, + settings: { refreshRate: MINUTE_IN_MS, requestBuffer: 0 }, + }, }, timeSeriesCallback ); @@ -918,8 +1030,12 @@ it.skip('overrides module-level cache TTL if query-level cache TTL is provided', }, }; - const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); - const dataModule = new TimeSeriesDataModule(dataSource, { cacheSettings: customCacheSettings }); + const dataSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); + const dataModule = new TimeSeriesDataModule(dataSource, { + cacheSettings: customCacheSettings, + }); const END = new Date(); const START = new Date(END.getTime() - HOUR_IN_MS); @@ -938,7 +1054,10 @@ it.skip('overrides module-level cache TTL if query-level cache TTL is provided', }, }, ], - request: { viewport: { start: START, end: END }, settings: { refreshRate: MINUTE_IN_MS, requestBuffer: 0 } }, + request: { + viewport: { start: START, end: END }, + settings: { refreshRate: MINUTE_IN_MS, requestBuffer: 0 }, + }, }, timeSeriesCallback ); @@ -978,7 +1097,9 @@ it.skip('overrides module-level cache TTL if query-level cache TTL is provided', describe('request scheduler', () => { it('periodically requests duration based queries', async () => { - const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); + const dataSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); const dataModule = new TimeSeriesDataModule(dataSource); const timeSeriesCallback = jest.fn(); @@ -987,7 +1108,10 @@ describe('request scheduler', () => { queries: [DATA_STREAM_QUERY], request: { viewport: { duration: 900000 }, - settings: { fetchFromStartToEnd: true, refreshRate: SECOND_IN_MS * 1.5 }, + settings: { + fetchFromStartToEnd: true, + refreshRate: SECOND_IN_MS * 1.5, + }, }, }, timeSeriesCallback @@ -1016,8 +1140,12 @@ describe('request scheduler', () => { }, }; - const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); - const dataModule = new TimeSeriesDataModule(dataSource, { cacheSettings: customCacheSettings }); + const dataSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); + const dataModule = new TimeSeriesDataModule(dataSource, { + cacheSettings: customCacheSettings, + }); const END = new Date(); const START = new Date(END.getTime() - HOUR_IN_MS); @@ -1028,7 +1156,10 @@ describe('request scheduler', () => { queries: [DATA_STREAM_QUERY], request: { viewport: { start: START, end: END }, - settings: { fetchFromStartToEnd: true, refreshRate: SECOND_IN_MS * 0.1 }, + settings: { + fetchFromStartToEnd: true, + refreshRate: SECOND_IN_MS * 0.1, + }, }, }, timeSeriesCallback @@ -1061,7 +1192,9 @@ describe('request scheduler', () => { }); it('stops requesting for data after unsubscribing', async () => { - const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); + const dataSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); const dataModule = new TimeSeriesDataModule(dataSource); const timeSeriesCallback = jest.fn(); @@ -1094,7 +1227,9 @@ describe('request scheduler', () => { it('periodically requests data after switching from static to duration based viewport', async () => { const DATA_POINT: DataPoint = { x: Date.now(), y: 1921 }; - const dataSource = createMockSiteWiseDataSource({ dataStreams: [{ ...DATA_STREAM, data: [DATA_POINT] }] }); + const dataSource = createMockSiteWiseDataSource({ + dataStreams: [{ ...DATA_STREAM, data: [DATA_POINT] }], + }); const dataModule = new TimeSeriesDataModule(dataSource); const timeSeriesCallback = jest.fn(); @@ -1141,7 +1276,9 @@ describe('request scheduler', () => { }); it('stops the request scheduler when we first update request info to have duration and then call unsubscribe', async () => { - const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); + const dataSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); const dataModule = new TimeSeriesDataModule(dataSource); const timeSeriesCallback = jest.fn(); @@ -1182,14 +1319,19 @@ describe('request scheduler', () => { }); it('stops the request scheduler when request info gets updated with static viewport that does not intersect with any TTL intervals', async () => { - const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); + const dataSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); const dataModule = new TimeSeriesDataModule(dataSource); const timeSeriesCallback = jest.fn(); const { update } = dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], - request: { viewport: { duration: SECOND_IN_MS }, settings: { fetchFromStartToEnd: true } }, + request: { + viewport: { duration: SECOND_IN_MS }, + settings: { fetchFromStartToEnd: true }, + }, }, timeSeriesCallback ); @@ -1213,14 +1355,19 @@ describe('request scheduler', () => { }); it('continues the scheduled requests when request info gets updated with static viewport that intersects with TTL intervals', async () => { - const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); + const dataSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); const dataModule = new TimeSeriesDataModule(dataSource); const timeSeriesCallback = jest.fn(); const { update } = dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], - request: { viewport: { duration: SECOND_IN_MS }, settings: { fetchFromStartToEnd: true } }, + request: { + viewport: { duration: SECOND_IN_MS }, + settings: { fetchFromStartToEnd: true }, + }, }, timeSeriesCallback ); @@ -1233,7 +1380,10 @@ describe('request scheduler', () => { await update({ request: { viewport: { start: START, end: END }, - settings: { refreshRate: SECOND_IN_MS * 1.5, fetchFromStartToEnd: true }, + settings: { + refreshRate: SECOND_IN_MS * 1.5, + fetchFromStartToEnd: true, + }, }, }); timeSeriesCallback.mockClear(); @@ -1246,7 +1396,9 @@ describe('request scheduler', () => { }); it('when data is requested from the viewport start to end with a buffer, include a buffer', async () => { - const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM] }); + const dataSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + }); const dataModule = new TimeSeriesDataModule(dataSource); const timeSeriesCallback = jest.fn(); @@ -1259,7 +1411,10 @@ it('when data is requested from the viewport start to end with a buffer, include const { unsubscribe } = dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], - request: { viewport: { start, end }, settings: { requestBuffer, fetchFromStartToEnd: true } }, + request: { + viewport: { start, end }, + settings: { requestBuffer, fetchFromStartToEnd: true }, + }, }, timeSeriesCallback ); @@ -1292,7 +1447,10 @@ it('when data is requested from the viewport start to end with a buffer, include it('passes meta field returned from data source into the initiate request', async () => { const someMetaData = { someMetaData: 42 }; - const dataSource = createMockSiteWiseDataSource({ dataStreams: [DATA_STREAM], meta: someMetaData }); + const dataSource = createMockSiteWiseDataSource({ + dataStreams: [DATA_STREAM], + meta: someMetaData, + }); const dataModule = new TimeSeriesDataModule(dataSource); const END = new Date(); @@ -1304,7 +1462,11 @@ it('passes meta field returned from data source into the initiate request', asyn queries: [DATA_STREAM_QUERY], request: { viewport: { start: START, end: END }, - settings: { fetchFromStartToEnd: true, refreshRate: MINUTE_IN_MS, requestBuffer: 0 }, + settings: { + fetchFromStartToEnd: true, + refreshRate: MINUTE_IN_MS, + requestBuffer: 0, + }, }, }, timeSeriesCallback diff --git a/packages/core/src/data-module/TimeSeriesDataModule.ts b/packages/core/src/data-module/TimeSeriesDataModule.ts index c2d6394e9..0ef0cc8da 100644 --- a/packages/core/src/data-module/TimeSeriesDataModule.ts +++ b/packages/core/src/data-module/TimeSeriesDataModule.ts @@ -17,7 +17,10 @@ import type { TimeSeriesData, } from './types'; import type { DataStreamsStore, CacheSettings } from './data-cache/types'; -import type { TimeSeriesDataRequest, Viewport } from './data-cache/requestTypes'; +import type { + TimeSeriesDataRequest, + Viewport, +} from './data-cache/requestTypes'; export const DEFAULT_CACHE_SETTINGS = { ttlDurationMapping: { @@ -45,7 +48,10 @@ export class TimeSeriesDataModule { * Create a new data module, optionally with a pre-hydrated data cache. * */ - constructor(dataSource: DataSource, configuration: IotAppKitDataModuleConfiguration = {}) { + constructor( + dataSource: DataSource, + configuration: IotAppKitDataModuleConfiguration = {} + ) { const { initialDataCache, cacheSettings } = configuration; this.dataSourceStore = new DataSourceStore(dataSource); @@ -77,9 +83,16 @@ export class TimeSeriesDataModule { request: TimeSeriesDataRequest; queries: Query[]; }) => { - const requestedStreams = await this.dataSourceStore.getRequestsFromQueries({ queries, request }); + const requestedStreams = await this.dataSourceStore.getRequestsFromQueries({ + queries, + request, + }); - const isRequestedDataStream = ({ id, resolution, aggregationType }: RequestInformation) => + const isRequestedDataStream = ({ + id, + resolution, + aggregationType, + }: RequestInformation) => this.dataCache.shouldRequestDataStream({ dataStreamId: id, resolution: parseDuration(resolution), @@ -111,7 +124,9 @@ export class TimeSeriesDataModule { } }; - private getAdjustedViewport = (request: TimeSeriesDataRequest): { start: Date; end: Date } => { + private getAdjustedViewport = ( + request: TimeSeriesDataRequest + ): { start: Date; end: Date } => { // Get the date range to request data for. // Pass in 'now' for max since we don't want to request for data in the future yet - it doesn't exist yet. const { start, end } = requestRange( @@ -156,12 +171,16 @@ export class TimeSeriesDataModule { this.unsubscribe(subscriptionId); }; - const update = (subscriptionUpdate: SubscriptionUpdate) => this.update(subscriptionId, subscriptionUpdate); + const update = (subscriptionUpdate: SubscriptionUpdate) => + this.update(subscriptionId, subscriptionUpdate); return { unsubscribe, update }; }; - private update = async (subscriptionId: string, subscriptionUpdate: SubscriptionUpdate): Promise => { + private update = async ( + subscriptionId: string, + subscriptionUpdate: SubscriptionUpdate + ): Promise => { const subscription = this.subscriptions.getSubscription(subscriptionId); const updatedSubscription = { ...subscription, ...subscriptionUpdate }; diff --git a/packages/core/src/data-module/data-cache/bestStreamStore.spec.ts b/packages/core/src/data-module/data-cache/bestStreamStore.spec.ts index 6d9d7d255..bac929997 100755 --- a/packages/core/src/data-module/data-cache/bestStreamStore.spec.ts +++ b/packages/core/src/data-module/data-cache/bestStreamStore.spec.ts @@ -132,7 +132,11 @@ describe(' get best stream store based', () => { [AGGREGATE_TYPE]: { id: ID, resolution: 50, - error: { msg: 'woah an error!', type: 'ResourceNotFoundException', status: '404' }, + error: { + msg: 'woah an error!', + type: 'ResourceNotFoundException', + status: '404', + }, requestHistory: [], isLoading: false, isRefreshing: false, @@ -165,7 +169,11 @@ describe(' get best stream store based', () => { const ERROR_STORE: DataStreamStore = { id: ID, requestHistory: [], - error: { msg: 'woah an error!', type: 'ResourceNotFoundException', status: '404' }, + error: { + msg: 'woah an error!', + type: 'ResourceNotFoundException', + status: '404', + }, resolution: 0, isLoading: false, isRefreshing: false, diff --git a/packages/core/src/data-module/data-cache/bestStreamStore.ts b/packages/core/src/data-module/data-cache/bestStreamStore.ts index 22e128c79..edda8ce7f 100755 --- a/packages/core/src/data-module/data-cache/bestStreamStore.ts +++ b/packages/core/src/data-module/data-cache/bestStreamStore.ts @@ -22,7 +22,11 @@ export const getBestStreamStore = ( const resolutionStreamStore = store[dataStreamId]?.resolutions; const rawDataStreamStore = store[dataStreamId]?.rawData; - if (requestResolution === 0 && rawDataStreamStore && !rawDataStreamStore.isLoading) { + if ( + requestResolution === 0 && + rawDataStreamStore && + !rawDataStreamStore.isLoading + ) { return rawDataStreamStore; } @@ -35,7 +39,9 @@ export const getBestStreamStore = ( .sort(ascendingSort) .filter((res) => res >= requestResolution); - const streamStores = resolutions.map((res) => resolutionStreamStore[res]?.[requestAggregation]).filter(isDefined); + const streamStores = resolutions + .map((res) => resolutionStreamStore[res]?.[requestAggregation]) + .filter(isDefined); const closestAvailableData = streamStores.find( ({ error, isLoading }: DataStreamStore) => error == null && !isLoading @@ -43,13 +49,16 @@ export const getBestStreamStore = ( // If the exact store is present and is not in a loading state, return it! // This is because we want to display an error if it occurs on our requested resolution. - const exactStore = resolutions[0] === requestResolution ? streamStores[0] : undefined; + const exactStore = + resolutions[0] === requestResolution ? streamStores[0] : undefined; if (exactStore && !exactStore.isLoading) { return exactStore; } return ( closestAvailableData || - (requestAggregation ? resolutionStreamStore[requestResolution]?.[requestAggregation] : undefined) + (requestAggregation + ? resolutionStreamStore[requestResolution]?.[requestAggregation] + : undefined) ); }; diff --git a/packages/core/src/data-module/data-cache/caching/caching.spec.ts b/packages/core/src/data-module/data-cache/caching/caching.spec.ts index b5b46cdf4..bd6ae580c 100755 --- a/packages/core/src/data-module/data-cache/caching/caching.spec.ts +++ b/packages/core/src/data-module/data-cache/caching/caching.spec.ts @@ -351,7 +351,11 @@ describe('getDateRangesToRequest', () => { requestHistory: [], isLoading: false, isRefreshing: false, - error: { msg: 'errored!', type: 'ResourceNotFoundException', status: '404' }, + error: { + msg: 'errored!', + type: 'ResourceNotFoundException', + status: '404', + }, dataCache: EMPTY_CACHE, requestCache: createDataPointCache({ start: new Date(1991, 0, 0), @@ -451,7 +455,10 @@ describe('getRequestInformations', () => { getRequestInformations({ request: { viewport: { start: START_DATE, end: END_DATE }, - settings: { fetchMostRecentBeforeEnd: true, fetchFromStartToEnd: true }, + settings: { + fetchMostRecentBeforeEnd: true, + fetchFromStartToEnd: true, + }, }, store: { [STREAM_ID]: { @@ -494,7 +501,10 @@ describe('getRequestInformations', () => { getRequestInformations({ request: { viewport: { start: START_DATE, end: END_DATE }, - settings: { fetchMostRecentBeforeEnd: false, fetchFromStartToEnd: true }, + settings: { + fetchMostRecentBeforeEnd: false, + fetchFromStartToEnd: true, + }, }, store: { [STREAM_ID]: { @@ -529,7 +539,10 @@ describe('getRequestInformations', () => { getRequestInformations({ request: { viewport: { start: START_DATE, end: END_DATE }, - settings: { fetchMostRecentBeforeEnd: true, fetchFromStartToEnd: true }, + settings: { + fetchMostRecentBeforeEnd: true, + fetchFromStartToEnd: true, + }, }, store: { [STREAM_ID]: { @@ -576,7 +589,10 @@ describe('getRequestInformations', () => { getRequestInformations({ request: { viewport: { start: START_DATE, end: END_DATE }, - settings: { fetchFromStartToEnd: true, fetchMostRecentBeforeEnd: true }, + settings: { + fetchFromStartToEnd: true, + fetchMostRecentBeforeEnd: true, + }, }, store: { [STREAM_ID]: { @@ -686,7 +702,10 @@ describe('getRequestInformations', () => { getRequestInformations({ request: { viewport: { start: START_DATE, end: END_DATE }, - settings: { fetchFromStartToEnd: true, fetchMostRecentBeforeEnd: true }, + settings: { + fetchFromStartToEnd: true, + fetchMostRecentBeforeEnd: true, + }, }, store: { [STREAM_ID]: { @@ -740,7 +759,10 @@ describe('getRequestInformations', () => { getRequestInformations({ request: { viewport: { start: START_DATE, end: END_DATE }, - settings: { fetchMostRecentBeforeStart: true, fetchFromStartToEnd: true }, + settings: { + fetchMostRecentBeforeStart: true, + fetchFromStartToEnd: true, + }, }, store: { [STREAM_ID]: { @@ -834,7 +856,10 @@ describe('getRequestInformations', () => { getRequestInformations({ request: { viewport: { start: START_DATE, end: END_DATE }, - settings: { fetchMostRecentBeforeStart: true, fetchFromStartToEnd: true }, + settings: { + fetchMostRecentBeforeStart: true, + fetchFromStartToEnd: true, + }, }, store: { [STREAM_ID]: { @@ -845,7 +870,9 @@ describe('getRequestInformations', () => { isLoading: false, isRefreshing: false, dataCache: { - intervals: [[START_DATE.getTime() - 2000, START_DATE.getTime()]], + intervals: [ + [START_DATE.getTime() - 2000, START_DATE.getTime()], + ], items: [ [ { @@ -910,7 +937,9 @@ describe('retrieve unexpired cached time intervals', () => { dataCache: EMPTY_CACHE, requestCache, }; - expect(unexpiredCacheIntervals(streamStore, {})).toEqual(requestCache.intervals); + expect(unexpiredCacheIntervals(streamStore, {})).toEqual( + requestCache.intervals + ); }); it('no cached intervals are expired when request range has a duration of 0', () => { @@ -944,7 +973,9 @@ describe('retrieve unexpired cached time intervals', () => { dataCache: EMPTY_CACHE, requestCache, }; - expect(unexpiredCacheIntervals(streamStore, TTL_CACHE_MAP)).toEqual(requestCache.intervals); + expect(unexpiredCacheIntervals(streamStore, TTL_CACHE_MAP)).toEqual( + requestCache.intervals + ); }); it('returns empty set of intervals as all caches are expired by a single TTL', () => { @@ -1054,7 +1085,9 @@ describe('retrieve unexpired cached time intervals', () => { requestCache, }; - expect(unexpiredCacheIntervals(streamStore, TTL_CACHE_MAP)).toEqual([[START.getTime(), END.getTime()]]); + expect(unexpiredCacheIntervals(streamStore, TTL_CACHE_MAP)).toEqual([ + [START.getTime(), END.getTime()], + ]); }); }); @@ -1127,7 +1160,9 @@ describe('retrieve unexpired cached time intervals', () => { requestCache, }; - expect(unexpiredCacheIntervals(streamStore, TTL_CACHE_MAP)).toEqual([[START.getTime(), END.getTime()]]); + expect(unexpiredCacheIntervals(streamStore, TTL_CACHE_MAP)).toEqual([ + [START.getTime(), END.getTime()], + ]); }); it('case #2', () => { diff --git a/packages/core/src/data-module/data-cache/caching/caching.ts b/packages/core/src/data-module/data-cache/caching/caching.ts index 6c4ce9a81..8613a2d37 100755 --- a/packages/core/src/data-module/data-cache/caching/caching.ts +++ b/packages/core/src/data-module/data-cache/caching/caching.ts @@ -1,24 +1,52 @@ -import { MINUTE_IN_MS, SECOND_IN_MS, parseDuration } from '../../../common/time'; +import { + MINUTE_IN_MS, + SECOND_IN_MS, + parseDuration, +} from '../../../common/time'; import { getDataStreamStore } from '../getDataStreamStore'; -import { addInterval, intersect, subtractIntervals } from '../../../common/intervalStructure'; +import { + addInterval, + intersect, + subtractIntervals, +} from '../../../common/intervalStructure'; import { AggregateType } from '@aws-sdk/client-iotsitewise'; import { getExpiredCacheIntervals } from './expiredCacheIntervals'; import { pointBisector } from '../../../common/dataFilters'; -import type { DataPoint, Primitive, RequestInformation, RequestInformationAndRange } from '../../types'; -import type { Interval, IntervalStructure } from '../../../common/intervalStructure'; -import type { CacheSettings, DataStreamsStore, DataStreamStore, TTLDurationMapping } from '../types'; -import type { TimeSeriesDataRequestSettings, TimeSeriesDataRequest } from '../requestTypes'; +import type { + DataPoint, + Primitive, + RequestInformation, + RequestInformationAndRange, +} from '../../types'; +import type { + Interval, + IntervalStructure, +} from '../../../common/intervalStructure'; +import type { + CacheSettings, + DataStreamsStore, + DataStreamStore, + TTLDurationMapping, +} from '../types'; +import type { + TimeSeriesDataRequestSettings, + TimeSeriesDataRequest, +} from '../requestTypes'; export const unexpiredCacheIntervals = ( streamStore: DataStreamStore, ttlDurationMapping: TTLDurationMapping ): Interval[] => { const expiredCacheIntervals = streamStore.requestHistory - .map((historicalRequest) => getExpiredCacheIntervals(ttlDurationMapping, historicalRequest)) + .map((historicalRequest) => + getExpiredCacheIntervals(ttlDurationMapping, historicalRequest) + ) .flat(); const allCachedIntervals = streamStore.requestCache.intervals; - return allCachedIntervals.map((interval) => subtractIntervals(interval, expiredCacheIntervals)).flat(); + return allCachedIntervals + .map((interval) => subtractIntervals(interval, expiredCacheIntervals)) + .flat(); }; // What is considered 'too close', and will cause intervals to merge together. @@ -39,7 +67,10 @@ const MINIMUM_INTERVAL = SECOND_IN_MS * 3; * * Assumes combined intervals are sorted, in a strictly ascending order. */ -const combineShortIntervals = (combinedIntervals: Interval[], interval: Interval): Interval[] => { +const combineShortIntervals = ( + combinedIntervals: Interval[], + interval: Interval +): Interval[] => { if (combinedIntervals.length === 0) { return [interval]; } @@ -79,7 +110,12 @@ export const getDateRangesToRequest = ({ cacheSettings: CacheSettings; aggregationType?: AggregateType; }): [Date, Date][] => { - const streamStore = getDataStreamStore(dataStreamId, resolution, store, aggregationType); + const streamStore = getDataStreamStore( + dataStreamId, + resolution, + store, + aggregationType + ); if (end.getTime() === start.getTime()) { // nothing to request @@ -92,13 +128,21 @@ export const getDateRangesToRequest = ({ } // NOTE: Use the request cache since we don't want to request intervals that already have been requested. - const cacheIntervals = unexpiredCacheIntervals(streamStore, cacheSettings.ttlDurationMapping); - const millisecondIntervals = subtractIntervals([start.getTime(), end.getTime()], cacheIntervals); + const cacheIntervals = unexpiredCacheIntervals( + streamStore, + cacheSettings.ttlDurationMapping + ); + const millisecondIntervals = subtractIntervals( + [start.getTime(), end.getTime()], + cacheIntervals + ); return millisecondIntervals .reduce(combineShortIntervals, []) .filter(([startMs, endMs]) => endMs - startMs > MINIMUM_INTERVAL) - .map(([startMS, endMS]) => [new Date(startMS), new Date(endMS)] as [Date, Date]); + .map( + ([startMS, endMS]) => [new Date(startMS), new Date(endMS)] as [Date, Date] + ); }; /** @@ -208,7 +252,10 @@ export const getRequestInformations = ({ return requestInformations; }; -export const dataPointCompare = (a: DataPoint, b: DataPoint) => { +export const dataPointCompare = ( + a: DataPoint, + b: DataPoint +) => { const aTime = a.x; const bTime = b.x; if (aTime !== bTime) { @@ -268,7 +315,12 @@ export const addToDataPointCache = ({ if (data.length === 0 && start.getTime() === end.getTime()) { return cache; } - return addInterval(cache, [start.getTime(), end.getTime()], data, dataPointCompare); + return addInterval( + cache, + [start.getTime(), end.getTime()], + data, + dataPointCompare + ); }; export const checkCacheForRecentPoint = ({ @@ -286,20 +338,32 @@ export const checkCacheForRecentPoint = ({ cacheSettings: CacheSettings; aggregationType?: AggregateType; }) => { - const streamStore = getDataStreamStore(dataStreamId, resolution, store, aggregationType); + const streamStore = getDataStreamStore( + dataStreamId, + resolution, + store, + aggregationType + ); if (streamStore && streamStore.dataCache.intervals.length > 0) { const { dataCache } = streamStore; - const cacheIntervals = unexpiredCacheIntervals(streamStore, cacheSettings.ttlDurationMapping); + const cacheIntervals = unexpiredCacheIntervals( + streamStore, + cacheSettings.ttlDurationMapping + ); const intersectedIntervals = intersect(cacheIntervals, dataCache.intervals); - const interval = intersectedIntervals.find((inter) => inter[0] <= start.getTime() && start.getTime() <= inter[1]); + const interval = intersectedIntervals.find( + (inter) => inter[0] <= start.getTime() && start.getTime() <= inter[1] + ); if (interval) { const dataPoints = dataCache.items.flat(); const elementIndex = pointBisector.right(dataPoints, start); - return elementIndex !== 0 && dataPoints[elementIndex - 1].x >= interval[0]; + return ( + elementIndex !== 0 && dataPoints[elementIndex - 1].x >= interval[0] + ); } return false; } @@ -308,7 +372,9 @@ export const checkCacheForRecentPoint = ({ // Validates request config to see if we need to make a fetch Request // This will expand in future to accomodate more requestConfig variants -export const validateRequestConfig = (requestConfig: TimeSeriesDataRequestSettings | undefined) => { +export const validateRequestConfig = ( + requestConfig: TimeSeriesDataRequestSettings | undefined +) => { if (requestConfig) { return requestConfig.fetchMostRecentBeforeStart; } @@ -318,7 +384,9 @@ export const validateRequestConfig = (requestConfig: TimeSeriesDataRequestSettin // Returns the maximum duration for possible uncached data for given CacheSettings export const maxCacheDuration = (cacheSettings: CacheSettings) => { - const ttlDurations = Object.keys(cacheSettings.ttlDurationMapping).map((key) => Number(key)); + const ttlDurations = Object.keys(cacheSettings.ttlDurationMapping).map( + (key) => Number(key) + ); if (ttlDurations.length === 0) { return 0; diff --git a/packages/core/src/data-module/data-cache/caching/expiredCacheIntervals.ts b/packages/core/src/data-module/data-cache/caching/expiredCacheIntervals.ts index bc25db549..787b97a1f 100755 --- a/packages/core/src/data-module/data-cache/caching/expiredCacheIntervals.ts +++ b/packages/core/src/data-module/data-cache/caching/expiredCacheIntervals.ts @@ -22,13 +22,18 @@ export const getExpiredCacheIntervals = ( const timeSinceRequest = now - requestedAt.getTime(); // get the active cache rule - const caches = sortedCaches.filter(({ duration, ttl }) => timeSinceRequest <= duration && timeSinceRequest > ttl); + const caches = sortedCaches.filter( + ({ duration, ttl }) => + timeSinceRequest <= duration && timeSinceRequest > ttl + ); // If there are no active cache rules, then there are no expired cache intervals if (caches.length === 0) { return []; } - const expiredIntervals = caches.map(({ duration, ttl }) => [now - duration, now - ttl] as Interval); + const expiredIntervals = caches.map( + ({ duration, ttl }) => [now - duration, now - ttl] as Interval + ); return intersect(expiredIntervals, [[start.getTime(), end.getTime()]]); }; diff --git a/packages/core/src/data-module/data-cache/createStore.ts b/packages/core/src/data-module/data-cache/createStore.ts index 9aa692daa..f1f6ac5f4 100755 --- a/packages/core/src/data-module/data-cache/createStore.ts +++ b/packages/core/src/data-module/data-cache/createStore.ts @@ -4,4 +4,5 @@ import { dataReducer } from './dataReducer'; import type { Store } from 'redux'; import type { DataStreamsStore } from './types'; -export const configureStore = (initialState: DataStreamsStore = {}): Store => createStore(dataReducer, initialState); +export const configureStore = (initialState: DataStreamsStore = {}): Store => + createStore(dataReducer, initialState); diff --git a/packages/core/src/data-module/data-cache/dataActions.ts b/packages/core/src/data-module/data-cache/dataActions.ts index 733558af3..5b0046baa 100755 --- a/packages/core/src/data-module/data-cache/dataActions.ts +++ b/packages/core/src/data-module/data-cache/dataActions.ts @@ -1,6 +1,11 @@ import { AggregateType } from '@aws-sdk/client-iotsitewise'; import type { Action, Dispatch } from 'redux'; -import type { DataStreamId, Resolution, DataStream, RequestInformationAndRange } from '../types'; +import type { + DataStreamId, + Resolution, + DataStream, + RequestInformationAndRange, +} from '../types'; import type { ErrorDetails } from '../../common/types'; /** @@ -23,14 +28,17 @@ export interface RequestData extends Action<'REQUEST'> { export type OnRequest = (payload: RequestData['payload']) => [Date, Date][]; -export const onRequestAction = (payload: RequestData['payload']): RequestData => ({ +export const onRequestAction = ( + payload: RequestData['payload'] +): RequestData => ({ type: REQUEST, payload, }); -export const onRequest = (payload: RequestData['payload']) => (dispatch: Dispatch) => { - dispatch(onRequestAction(payload)); -}; +export const onRequest = + (payload: RequestData['payload']) => (dispatch: Dispatch) => { + dispatch(onRequestAction(payload)); + }; /** * On Error @@ -63,7 +71,12 @@ export const onErrorAction = ( }); export const onError = - (id: DataStreamId, resolution: Resolution, error: ErrorDetails, aggregationType?: AggregateType) => + ( + id: DataStreamId, + resolution: Resolution, + error: ErrorDetails, + aggregationType?: AggregateType + ) => (dispatch: Dispatch) => { dispatch(onErrorAction(id, resolution, error, aggregationType)); }; @@ -101,7 +114,13 @@ export const onSuccessAction = ( }); export const onSuccess = - (id: DataStreamId, data: DataStream, first: Date, last: Date, requestInformation: RequestInformationAndRange) => + ( + id: DataStreamId, + data: DataStream, + first: Date, + last: Date, + requestInformation: RequestInformationAndRange + ) => (dispatch: Dispatch) => { dispatch(onSuccessAction(id, data, first, last, requestInformation)); }; diff --git a/packages/core/src/data-module/data-cache/dataCacheWrapped.spec.ts b/packages/core/src/data-module/data-cache/dataCacheWrapped.spec.ts index 27cf4ad1c..d12d09c13 100644 --- a/packages/core/src/data-module/data-cache/dataCacheWrapped.spec.ts +++ b/packages/core/src/data-module/data-cache/dataCacheWrapped.spec.ts @@ -23,7 +23,11 @@ it('initializes', () => { }); describe('shouldRequestDataStream', () => { - const ERR = { msg: 'An error has occurred!', type: 'ResourceNotFoundException', status: '404' }; + const ERR = { + msg: 'An error has occurred!', + type: 'ResourceNotFoundException', + status: '404', + }; const CACHE_WITH_ERROR: DataStreamsStore = { [DATA_STREAM.id]: { @@ -117,9 +121,18 @@ describe('actions', () => { const ID = 'some-id'; const RESOLUTION = SECOND_IN_MS; - const ERROR = { msg: 'some error', type: 'ResourceNotFoundException', status: '404' }; + const ERROR = { + msg: 'some error', + type: 'ResourceNotFoundException', + status: '404', + }; - dataCache.onError({ id: ID, resolution: RESOLUTION, aggregationType: AGGREGATE_TYPE, error: ERROR }); + dataCache.onError({ + id: ID, + resolution: RESOLUTION, + aggregationType: AGGREGATE_TYPE, + error: ERROR, + }); const state = dataCache.getState(); expect(state?.[ID]?.resolutions?.[RESOLUTION]?.[AGGREGATE_TYPE]).toEqual( @@ -190,7 +203,9 @@ describe('actions', () => { end ); const state = dataCache.getState() as DataStreamsStore; - expect(state[DATA_STREAM.id]?.resolutions?.[100]?.[AGGREGATE_TYPE]).toBeDefined(); + expect( + state[DATA_STREAM.id]?.resolutions?.[100]?.[AGGREGATE_TYPE] + ).toBeDefined(); expect(state[DATA_STREAM.id]?.rawData).toBeUndefined(); }); }); diff --git a/packages/core/src/data-module/data-cache/dataCacheWrapped.ts b/packages/core/src/data-module/data-cache/dataCacheWrapped.ts index 416794633..c583ae54c 100644 --- a/packages/core/src/data-module/data-cache/dataCacheWrapped.ts +++ b/packages/core/src/data-module/data-cache/dataCacheWrapped.ts @@ -6,11 +6,19 @@ import { Observable, map, startWith, pairwise, from } from 'rxjs'; import { filter } from 'rxjs/operators'; import { toDataStreams } from './toDataStreams'; import type { Store } from 'redux'; -import type { Resolution, RequestInformation, DataStream, RequestInformationAndRange } from '../types'; +import type { + Resolution, + RequestInformation, + DataStream, + RequestInformationAndRange, +} from '../types'; import type { DataStreamsStore } from './types'; import type { ErrorDetails } from '../../common/types'; -type StoreChange = { prevDataCache: DataStreamsStore; currDataCache: DataStreamsStore }; +type StoreChange = { + prevDataCache: DataStreamsStore; + currDataCache: DataStreamsStore; +}; /** * Referential comparison of information related to the requested information. @@ -53,11 +61,17 @@ export class DataCache { this.observableStore = from(this.dataCache).pipe( startWith(undefined), pairwise(), - map(([prevDataCache, currDataCache]) => ({ prevDataCache, currDataCache })) + map(([prevDataCache, currDataCache]) => ({ + prevDataCache, + currDataCache, + })) ); } - public subscribe = (requestInformations: RequestInformation[], emit: (dataStreams: DataStream[]) => void) => { + public subscribe = ( + requestInformations: RequestInformation[], + emit: (dataStreams: DataStream[]) => void + ) => { const subscription = this.observableStore .pipe( // Filter out any changes that don't effect the requested informations @@ -96,7 +110,12 @@ export class DataCache { resolution: number; aggregationType?: AggregateType; }) => { - const associatedStore = getDataStreamStore(dataStreamId, resolution, this.getState(), aggregationType); + const associatedStore = getDataStreamStore( + dataStreamId, + resolution, + this.getState(), + aggregationType + ); const hasError = associatedStore ? associatedStore.error != null : false; return !hasError; }; @@ -121,7 +140,9 @@ export class DataCache { // For example, if we have queried data for the last day, but it took 1 minute for the query to resolve, we would have the start and the end date // incorrectly offset by one minute with the correct logic. dataStreams.forEach((stream) => { - this.dataCache.dispatch(onSuccessAction(stream.id, stream, start, end, requestInformation)); + this.dataCache.dispatch( + onSuccessAction(stream.id, stream, start, end, requestInformation) + ); }); }; @@ -136,7 +157,9 @@ export class DataCache { error: ErrorDetails; aggregationType?: AggregateType; }): void => { - this.dataCache.dispatch(onErrorAction(id, resolution, error, aggregationType)); + this.dataCache.dispatch( + onErrorAction(id, resolution, error, aggregationType) + ); }; public onRequest = (requestInformation: RequestInformationAndRange): void => { diff --git a/packages/core/src/data-module/data-cache/dataReducer.spec.ts b/packages/core/src/data-module/data-cache/dataReducer.spec.ts index 187ce945d..5ddf05166 100755 --- a/packages/core/src/data-module/data-cache/dataReducer.spec.ts +++ b/packages/core/src/data-module/data-cache/dataReducer.spec.ts @@ -49,7 +49,9 @@ describe('loading status', () => { }) ); - expect(reRequestState?.[ID]?.resolutions?.[RESOLUTION]?.[AGGREGATE_TYPE]).toEqual( + expect( + reRequestState?.[ID]?.resolutions?.[RESOLUTION]?.[AGGREGATE_TYPE] + ).toEqual( expect.objectContaining({ isLoading: true, aggregationType: AGGREGATE_TYPE, @@ -87,7 +89,9 @@ describe('loading status', () => { ) ); - expect(errorState?.[ID]?.resolutions?.[RESOLUTION]?.[AGGREGATE_TYPE]).toEqual( + expect( + errorState?.[ID]?.resolutions?.[RESOLUTION]?.[AGGREGATE_TYPE] + ).toEqual( expect.objectContaining({ isLoading: false, aggregationType: AGGREGATE_TYPE, @@ -111,7 +115,9 @@ describe('loading status', () => { }) ); - expect(afterRequestState?.[ID]?.resolutions?.[RESOLUTION]?.[AGGREGATE_TYPE]).toEqual( + expect( + afterRequestState?.[ID]?.resolutions?.[RESOLUTION]?.[AGGREGATE_TYPE] + ).toEqual( expect.objectContaining({ isLoading: true, aggregationType: AGGREGATE_TYPE, @@ -210,7 +216,9 @@ describe('loading status', () => { ) ); - expect(successState?.[ID]?.resolutions?.[RESOLUTION]?.[AGGREGATE_TYPE]).toEqual( + expect( + successState?.[ID]?.resolutions?.[RESOLUTION]?.[AGGREGATE_TYPE] + ).toEqual( expect.objectContaining({ isLoading: false, aggregationType: AGGREGATE_TYPE, @@ -224,7 +232,11 @@ describe('on request', () => { it('retains existing error message', () => { const ID = 'some-id'; const RESOLUTION = SECOND_IN_MS; - const ERR = { msg: 'a terrible error', type: 'ResourceNotFoundException', status: '404' }; + const ERR = { + msg: 'a terrible error', + type: 'ResourceNotFoundException', + status: '404', + }; const INITIAL_STATE: DataStreamsStore = { [ID]: { @@ -258,7 +270,9 @@ describe('on request', () => { }) ); - expect(afterRequestState?.[ID]?.resolutions?.[RESOLUTION]?.[AGGREGATE_TYPE]).toEqual( + expect( + afterRequestState?.[ID]?.resolutions?.[RESOLUTION]?.[AGGREGATE_TYPE] + ).toEqual( expect.objectContaining({ error: ERR, aggregationType: AGGREGATE_TYPE, @@ -270,7 +284,9 @@ describe('on request', () => { it('returns the state back directly when a non-existent action type is passed in', () => { const INITIAL_STATE = {}; - expect(dataReducer(INITIAL_STATE, { type: 'fake-action' } as never)).toBe(INITIAL_STATE); + expect(dataReducer(INITIAL_STATE, { type: 'fake-action' } as never)).toBe( + INITIAL_STATE + ); // Reducers should not alter the reference or structure of the state if no action is to be applied. // This helps prevent accidental re-renders, since re-rendering is done based on referential equality. expect(INITIAL_STATE).toEqual({}); @@ -278,7 +294,11 @@ it('returns the state back directly when a non-existent action type is passed in it('sets an error message for a previously loaded state', () => { const ID = 'my-id'; - const ERROR = { msg: 'my-error!', type: 'ResourceNotFoundException', status: '404' }; + const ERROR = { + msg: 'my-error!', + type: 'ResourceNotFoundException', + status: '404', + }; const INITIAL_STATE: DataStreamsStore = { [ID]: { rawData: { @@ -809,7 +829,9 @@ it('merges data into existing data cache', () => { }) ); - expect(getDataStreamStore(ID, SECOND_IN_MS, successState, AGGREGATE_TYPE)).toEqual({ + expect( + getDataStreamStore(ID, SECOND_IN_MS, successState, AGGREGATE_TYPE) + ).toEqual({ ...getDataStreamStore(ID, SECOND_IN_MS, INITIAL_STATE, AGGREGATE_TYPE), isLoading: false, isRefreshing: false, @@ -819,7 +841,14 @@ it('merges data into existing data cache', () => { error: undefined, dataCache: { intervals: [[DATE_ONE, DATE_FOUR]], - items: [[...DATA_POINTS_ONE, OLDER_DATA_POINT_2, NEWER_DATA_POINT_1, ...DATA_POINTS_TWO]], + items: [ + [ + ...DATA_POINTS_ONE, + OLDER_DATA_POINT_2, + NEWER_DATA_POINT_1, + ...DATA_POINTS_TWO, + ], + ], }, requestCache: expect.objectContaining({ intervals: [[DATE_ONE, DATE_FOUR]], @@ -827,7 +856,10 @@ it('merges data into existing data cache', () => { requestHistory: expect.any(Array), }); - const BEFORE_START_DATA_POINT = { x: new Date(1990, 11, 0).getTime(), y: 500 }; + const BEFORE_START_DATA_POINT = { + x: new Date(1990, 11, 0).getTime(), + y: 500, + }; const beforeStartDataStream = { name: 'some name', @@ -853,7 +885,14 @@ it('merges data into existing data cache', () => { }) ); - expect(getDataStreamStore(ID, SECOND_IN_MS, beforeStartSuccessState, AGGREGATE_TYPE)).toEqual({ + expect( + getDataStreamStore( + ID, + SECOND_IN_MS, + beforeStartSuccessState, + AGGREGATE_TYPE + ) + ).toEqual({ ...getDataStreamStore(ID, SECOND_IN_MS, successState, AGGREGATE_TYPE), isLoading: false, isRefreshing: false, @@ -862,7 +901,13 @@ it('merges data into existing data cache', () => { dataCache: { intervals: [[BEFORE_START_DATA_POINT.x, DATE_FOUR]], items: [ - [BEFORE_START_DATA_POINT, ...DATA_POINTS_ONE, OLDER_DATA_POINT_2, NEWER_DATA_POINT_1, ...DATA_POINTS_TWO], + [ + BEFORE_START_DATA_POINT, + ...DATA_POINTS_ONE, + OLDER_DATA_POINT_2, + NEWER_DATA_POINT_1, + ...DATA_POINTS_TWO, + ], ], }, requestCache: expect.objectContaining({ @@ -916,10 +961,19 @@ describe('requests to different resolutions', () => { aggregationType: AGGREGATE_TYPE, }; - const requestState = dataReducer(INITIAL_STATE, onRequestAction(requestInformation)); + const requestState = dataReducer( + INITIAL_STATE, + onRequestAction(requestInformation) + ); const newState = dataReducer( requestState, - onSuccessAction(ID, DATA, NEW_FIRST_DATE, NEW_LAST_DATE, requestInformation) + onSuccessAction( + ID, + DATA, + NEW_FIRST_DATE, + NEW_LAST_DATE, + requestInformation + ) ); expect(newState).toEqual({ [ID]: { @@ -942,16 +996,23 @@ describe('requests to different resolutions', () => { }, ], dataCache: { - intervals: [[NEW_FIRST_DATE.getTime(), NEW_LAST_DATE.getTime()]], + intervals: [ + [NEW_FIRST_DATE.getTime(), NEW_LAST_DATE.getTime()], + ], items: [newDataPoints], }, requestCache: { - intervals: [[NEW_FIRST_DATE.getTime(), NEW_LAST_DATE.getTime()]], + intervals: [ + [NEW_FIRST_DATE.getTime(), NEW_LAST_DATE.getTime()], + ], items: [[]], }, }, }, - [SECOND_IN_MS]: { [AGGREGATE_TYPE]: INITIAL_STATE[ID]['resolutions'][SECOND_IN_MS][AGGREGATE_TYPE] }, + [SECOND_IN_MS]: { + [AGGREGATE_TYPE]: + INITIAL_STATE[ID]['resolutions'][SECOND_IN_MS][AGGREGATE_TYPE], + }, }, }, }); @@ -989,32 +1050,46 @@ describe('requests to different resolutions', () => { fetchFromStartToEnd: true, aggregationType: AGGREGATE_TYPE, }; - const requestState = dataReducer(INITIAL_STATE, onRequestAction(requestInformation)); - const ERROR = { msg: 'error!', type: 'ResourceNotFoundException', status: '404' }; - const newState = dataReducer(requestState, onErrorAction(ID, RESOLUTION, ERROR, AGGREGATE_TYPE)); + const requestState = dataReducer( + INITIAL_STATE, + onRequestAction(requestInformation) + ); + const ERROR = { + msg: 'error!', + type: 'ResourceNotFoundException', + status: '404', + }; + const newState = dataReducer( + requestState, + onErrorAction(ID, RESOLUTION, ERROR, AGGREGATE_TYPE) + ); // maintained other resolution - expect(newState?.[ID]?.resolutions?.[SECOND_IN_MS]).toBe(INITIAL_STATE[ID]['resolutions'][SECOND_IN_MS]); + expect(newState?.[ID]?.resolutions?.[SECOND_IN_MS]).toBe( + INITIAL_STATE[ID]['resolutions'][SECOND_IN_MS] + ); - expect(newState?.[ID]?.resolutions?.[RESOLUTION]?.[AGGREGATE_TYPE]).toEqual({ - id: ID, - resolution: RESOLUTION, - aggregationType: AGGREGATE_TYPE, - error: ERROR, - isLoading: false, - isRefreshing: false, - requestHistory: [ - { - start: NEW_FIRST_DATE, - end: NEW_LAST_DATE, - requestedAt: DATE_NOW, + expect(newState?.[ID]?.resolutions?.[RESOLUTION]?.[AGGREGATE_TYPE]).toEqual( + { + id: ID, + resolution: RESOLUTION, + aggregationType: AGGREGATE_TYPE, + error: ERROR, + isLoading: false, + isRefreshing: false, + requestHistory: [ + { + start: NEW_FIRST_DATE, + end: NEW_LAST_DATE, + requestedAt: DATE_NOW, + }, + ], + dataCache: EMPTY_CACHE, + requestCache: { + intervals: [[NEW_FIRST_DATE.getTime(), NEW_LAST_DATE.getTime()]], + items: [[]], }, - ], - dataCache: EMPTY_CACHE, - requestCache: { - intervals: [[NEW_FIRST_DATE.getTime(), NEW_LAST_DATE.getTime()]], - items: [[]], - }, - }); + } + ); }); }); diff --git a/packages/core/src/data-module/data-cache/dataReducer.ts b/packages/core/src/data-module/data-cache/dataReducer.ts index a11fac5fa..82b3702bb 100755 --- a/packages/core/src/data-module/data-cache/dataReducer.ts +++ b/packages/core/src/data-module/data-cache/dataReducer.ts @@ -19,11 +19,27 @@ export const dataReducer: Reducer = ( ): DataStreamsStore => { switch (action.type) { case REQUEST: { - const { id, resolution, aggregationType, start, end, fetchFromStartToEnd } = action.payload; - const streamStore = getDataStreamStore(id, resolution, state, aggregationType); - const dataCache = streamStore != null ? streamStore.dataCache : EMPTY_CACHE; - const requestCache = streamStore != null ? streamStore.requestCache : EMPTY_CACHE; - const existingRequestHistory = streamStore ? streamStore.requestHistory : []; + const { + id, + resolution, + aggregationType, + start, + end, + fetchFromStartToEnd, + } = action.payload; + const streamStore = getDataStreamStore( + id, + resolution, + state, + aggregationType + ); + const dataCache = + streamStore != null ? streamStore.dataCache : EMPTY_CACHE; + const requestCache = + streamStore != null ? streamStore.requestCache : EMPTY_CACHE; + const existingRequestHistory = streamStore + ? streamStore.requestHistory + : []; // We only consider it loading if data has not been requested before, or if it's already loading. const isLoading = streamStore == null || streamStore.isLoading; @@ -65,7 +81,9 @@ export const dataReducer: Reducer = ( } : state[id]?.resolutions || undefined; const newRawData = - numericResolution === 0 ? { ...state[id]?.rawData, ...newStreamStore } : state[id]?.rawData || undefined; + numericResolution === 0 + ? { ...state[id]?.rawData, ...newStreamStore } + : state[id]?.rawData || undefined; return { ...state, @@ -78,13 +96,29 @@ export const dataReducer: Reducer = ( } case SUCCESS: { - const { id, data: dataStream, first, last, requestInformation } = action.payload; - const { aggregationType, fetchFromStartToEnd, fetchMostRecentBeforeEnd, fetchMostRecentBeforeStart } = - requestInformation; - const streamStore = getDataStreamStore(id, dataStream.resolution, state, aggregationType); + const { + id, + data: dataStream, + first, + last, + requestInformation, + } = action.payload; + const { + aggregationType, + fetchFromStartToEnd, + fetchMostRecentBeforeEnd, + fetchMostRecentBeforeStart, + } = requestInformation; + const streamStore = getDataStreamStore( + id, + dataStream.resolution, + state, + aggregationType + ); // Updating request cache is a hack to deal with latest value update // TODO: clean this to one single source of truth cache - const requestCache = streamStore != null ? streamStore.requestCache : EMPTY_CACHE; + const requestCache = + streamStore != null ? streamStore.requestCache : EMPTY_CACHE; // We always want data in ascending order in the cache const sortedData = dataStream.data.sort((a, b) => a.x - b.x); @@ -99,7 +133,10 @@ export const dataReducer: Reducer = ( // start the interval from the returned data point to avoid over-caching // if there is no data point it's fine to cache the entire interval - if ((fetchMostRecentBeforeStart || fetchMostRecentBeforeEnd) && sortedData.length > 0) { + if ( + (fetchMostRecentBeforeStart || fetchMostRecentBeforeEnd) && + sortedData.length > 0 + ) { intervalStart = new Date(sortedData[0].x); } @@ -110,7 +147,9 @@ export const dataReducer: Reducer = ( cache: (streamStore && streamStore.dataCache) || EMPTY_CACHE, }); - const existingRequestHistory = streamStore ? streamStore.requestHistory : []; + const existingRequestHistory = streamStore + ? streamStore.requestHistory + : []; // eslint-disable-next-line @typescript-eslint/no-unused-vars const { data, ...restOfDataStream } = dataStream; @@ -147,7 +186,9 @@ export const dataReducer: Reducer = ( } : state[id]?.resolutions || undefined; const newRawData = - dataStream.resolution === 0 ? { ...state[id]?.rawData, ...newStreamStore } : state[id]?.rawData || undefined; + dataStream.resolution === 0 + ? { ...state[id]?.rawData, ...newStreamStore } + : state[id]?.rawData || undefined; return { ...state, @@ -162,7 +203,12 @@ export const dataReducer: Reducer = ( case ERROR: { const { id, error, resolution, aggregationType } = action.payload; - const streamStore = getDataStreamStore(id, resolution, state, aggregationType); + const streamStore = getDataStreamStore( + id, + resolution, + state, + aggregationType + ); const newStreamStore = { ...streamStore, diff --git a/packages/core/src/data-module/data-cache/dateUtils.ts b/packages/core/src/data-module/data-cache/dateUtils.ts index 5d8703585..d90d7bab0 100755 --- a/packages/core/src/data-module/data-cache/dateUtils.ts +++ b/packages/core/src/data-module/data-cache/dateUtils.ts @@ -10,7 +10,9 @@ export const getDateInterval = (viewport: Viewport): DateInterval => { const start = isHistoricalViewport(viewport) ? new Date(viewport.start) : new Date(Date.now() - parseDuration(viewport.duration)); - const end = isHistoricalViewport(viewport) ? new Date(viewport.end) : new Date(); + const end = isHistoricalViewport(viewport) + ? new Date(viewport.end) + : new Date(); return { start, end, diff --git a/packages/core/src/data-module/data-cache/mergeHistoricalRequests.spec.ts b/packages/core/src/data-module/data-cache/mergeHistoricalRequests.spec.ts index 113b63e85..1bc951e0d 100755 --- a/packages/core/src/data-module/data-cache/mergeHistoricalRequests.spec.ts +++ b/packages/core/src/data-module/data-cache/mergeHistoricalRequests.spec.ts @@ -20,7 +20,10 @@ it('with a single existing non-overlapping historical request, simply append the end: new Date(2000, 1, 0), requestedAt: new Date(2000, 1, 0), }; - expect(mergeHistoricalRequests([existingRequest], request)).toEqual([request, existingRequest]); + expect(mergeHistoricalRequests([existingRequest], request)).toEqual([ + request, + existingRequest, + ]); }); it('with multiple existing non-overlapping historical request, simply append the new request to the front and maintain prior order', () => { @@ -40,7 +43,10 @@ it('with multiple existing non-overlapping historical request, simply append the requestedAt: new Date(2000, 1, 0), }; const existingHistory = [existingRequest2, existingRequest1]; - expect(mergeHistoricalRequests(existingHistory, request)).toEqual([request, ...existingHistory]); + expect(mergeHistoricalRequests(existingHistory, request)).toEqual([ + request, + ...existingHistory, + ]); }); it('when a new historical request is append and partially overlaps an existing request, truncate the existing historical request', () => { @@ -77,7 +83,9 @@ it('when a new historical request is append and fully overlaps an existing reque requestedAt: new Date(2000, 1, 0), }; - expect(mergeHistoricalRequests([existingRequest], request)).toEqual([request]); + expect(mergeHistoricalRequests([existingRequest], request)).toEqual([ + request, + ]); }); it('when a new historical request is appended and fully overlaps an multiple existing requests, fully remove previous historical requests', () => { @@ -97,7 +105,9 @@ it('when a new historical request is appended and fully overlaps an multiple exi requestedAt: new Date(2005, 0, 0), }; - expect(mergeHistoricalRequests([existingRequest2, existingRequest1], request)).toEqual([request]); + expect( + mergeHistoricalRequests([existingRequest2, existingRequest1], request) + ).toEqual([request]); }); it('when a new request partially overlaps one existing request, and fully overlaps another, it will return only two requests, one of which is truncated', () => { @@ -117,7 +127,9 @@ it('when a new request partially overlaps one existing request, and fully overla requestedAt: new Date(2005, 0, 0), }; - expect(mergeHistoricalRequests([existingRequest2, existingRequest1], request)).toEqual([ + expect( + mergeHistoricalRequests([existingRequest2, existingRequest1], request) + ).toEqual([ request, { start: existingRequest1.start, diff --git a/packages/core/src/data-module/data-cache/mergeHistoricalRequests.ts b/packages/core/src/data-module/data-cache/mergeHistoricalRequests.ts index cdbd9b093..542104122 100755 --- a/packages/core/src/data-module/data-cache/mergeHistoricalRequests.ts +++ b/packages/core/src/data-module/data-cache/mergeHistoricalRequests.ts @@ -7,11 +7,17 @@ const mergeHistoricalRequest = ( newRequest: HistoricalRequest, existingRequest: HistoricalRequest ): HistoricalRequest[] => { - if (newRequest.start <= existingRequest.start && newRequest.end >= existingRequest.end) { + if ( + newRequest.start <= existingRequest.start && + newRequest.end >= existingRequest.end + ) { // new request fully contains existing request. return []; } - if (newRequest.start >= existingRequest.start && newRequest.end <= existingRequest.end) { + if ( + newRequest.start >= existingRequest.start && + newRequest.end <= existingRequest.end + ) { // new request fully contained within existing request return [ { @@ -27,7 +33,10 @@ const mergeHistoricalRequest = ( ]; } - if (newRequest.start < existingRequest.start && newRequest.end > existingRequest.start) { + if ( + newRequest.start < existingRequest.start && + newRequest.end > existingRequest.start + ) { // new request overlaps on the left side return [ { @@ -38,7 +47,10 @@ const mergeHistoricalRequest = ( ]; } - if (existingRequest.start < newRequest.start && existingRequest.end > newRequest.start) { + if ( + existingRequest.start < newRequest.start && + existingRequest.end > newRequest.start + ) { // new request overlaps on the right side return [ { @@ -53,8 +65,10 @@ const mergeHistoricalRequest = ( return [existingRequest]; }; -const chronologicalSort = (r1: HistoricalRequest, r2: HistoricalRequest): number => - r2.start.getTime() - r1.start.getTime(); +const chronologicalSort = ( + r1: HistoricalRequest, + r2: HistoricalRequest +): number => r2.start.getTime() - r1.start.getTime(); /** * Merge Historical Requests @@ -67,4 +81,7 @@ export const mergeHistoricalRequests = ( existingHistory: HistoricalRequest[], newRequest: HistoricalRequest ): HistoricalRequest[] => - [newRequest, ...existingHistory.map((r) => mergeHistoricalRequest(newRequest, r)).flat()].sort(chronologicalSort); + [ + newRequest, + ...existingHistory.map((r) => mergeHistoricalRequest(newRequest, r)).flat(), + ].sort(chronologicalSort); diff --git a/packages/core/src/data-module/data-cache/requestRange.spec.ts b/packages/core/src/data-module/data-cache/requestRange.spec.ts index 7e6f297c8..e3c7dde02 100755 --- a/packages/core/src/data-module/data-cache/requestRange.spec.ts +++ b/packages/core/src/data-module/data-cache/requestRange.spec.ts @@ -1,5 +1,10 @@ import { requestRange, roundUp } from './requestRange'; -import { DAY_IN_MS, HOUR_IN_MS, MINUTE_IN_MS, SECOND_IN_MS } from '../../common/time'; +import { + DAY_IN_MS, + HOUR_IN_MS, + MINUTE_IN_MS, + SECOND_IN_MS, +} from '../../common/time'; const TEST_BUFFER = 0.3; const LARGE_MAX_DATE = new Date(9999, 0, 0); @@ -28,7 +33,11 @@ describe('date range to fetch data on request', () => { it('returns a date range that contains the input range', () => { const START = new Date(2000, 0, 0); const END = new Date(2001, 0, 0); - const { start, end } = requestRange({ start: START, end: END, max: LARGE_MAX_DATE }); + const { start, end } = requestRange({ + start: START, + end: END, + max: LARGE_MAX_DATE, + }); expect(start <= START).toBe(true); expect(end >= END).toBe(true); }); @@ -37,39 +46,69 @@ describe('date range to fetch data on request', () => { it('for a time span of 1 second, do not request more than 5 seconds', () => { const START = new Date(2015, 0, 5, 0, 0, 0); const END = new Date(2015, 0, 5, 0, 0, 1); - const { start, end } = requestRange({ start: START, end: END, max: LARGE_MAX_DATE }); - expect(end.getTime() - start.getTime()).toBeLessThanOrEqual(5 * SECOND_IN_MS); + const { start, end } = requestRange({ + start: START, + end: END, + max: LARGE_MAX_DATE, + }); + expect(end.getTime() - start.getTime()).toBeLessThanOrEqual( + 5 * SECOND_IN_MS + ); }); it('for a time span of 30 seconds, requests at most one minute of data', () => { const START = new Date(2015, 0, 5, 0, 0, 15); const END = new Date(2015, 0, 5, 0, 0, 45); - const { start, end } = requestRange({ start: START, end: END, max: LARGE_MAX_DATE }); + const { start, end } = requestRange({ + start: START, + end: END, + max: LARGE_MAX_DATE, + }); expect(end.getTime() - start.getTime()).toBeLessThanOrEqual(MINUTE_IN_MS); }); it('for a time span of 5 minutes, requests at most 10 minutes of data', () => { const START = new Date(2015, 0, 5, 0, 0); const END = new Date(2015, 0, 5, 0, 5); - const { start, end } = requestRange({ start: START, end: END, max: LARGE_MAX_DATE }); - expect(end.getTime() - start.getTime()).toBeLessThanOrEqual(10 * MINUTE_IN_MS); + const { start, end } = requestRange({ + start: START, + end: END, + max: LARGE_MAX_DATE, + }); + expect(end.getTime() - start.getTime()).toBeLessThanOrEqual( + 10 * MINUTE_IN_MS + ); }); it('for a time span of one hour, requests at most 2 hours of data', () => { const START = new Date(2015, 0, 5, 0); const END = new Date(2015, 0, 5, 1); - const { start, end } = requestRange({ start: START, end: END, max: LARGE_MAX_DATE }); - expect(end.getTime() - start.getTime()).toBeLessThanOrEqual(3 * HOUR_IN_MS); + const { start, end } = requestRange({ + start: START, + end: END, + max: LARGE_MAX_DATE, + }); + expect(end.getTime() - start.getTime()).toBeLessThanOrEqual( + 3 * HOUR_IN_MS + ); }); }); describe('date range is identical when', () => { it('is panned a short distance towards the future', () => { const range1 = requestRange( - { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0), max: LARGE_MAX_DATE }, + { + start: new Date(2000, 0, 0), + end: new Date(2001, 0, 0), + max: LARGE_MAX_DATE, + }, TEST_BUFFER ); const range2 = requestRange( - { start: new Date(2000, 0, 2), end: new Date(2001, 0, 2), max: LARGE_MAX_DATE }, + { + start: new Date(2000, 0, 2), + end: new Date(2001, 0, 2), + max: LARGE_MAX_DATE, + }, TEST_BUFFER ); expect(range1.start.toISOString()).toBe(range2.start.toISOString()); @@ -78,11 +117,19 @@ describe('date range to fetch data on request', () => { it('is slightly condensed time frame', () => { const range1 = requestRange( - { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0), max: LARGE_MAX_DATE }, + { + start: new Date(2000, 0, 0), + end: new Date(2001, 0, 0), + max: LARGE_MAX_DATE, + }, TEST_BUFFER ); const range2 = requestRange( - { start: new Date(2000, 0, 10), end: new Date(2000, 12, 15), max: LARGE_MAX_DATE }, + { + start: new Date(2000, 0, 10), + end: new Date(2000, 12, 15), + max: LARGE_MAX_DATE, + }, TEST_BUFFER ); expect(range1.start.toISOString()).toBe(range2.start.toISOString()); @@ -91,11 +138,19 @@ describe('date range to fetch data on request', () => { it('is slightly expanded time frame', () => { const range1 = requestRange( - { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0), max: LARGE_MAX_DATE }, + { + start: new Date(2000, 0, 0), + end: new Date(2001, 0, 0), + max: LARGE_MAX_DATE, + }, TEST_BUFFER ); const range2 = requestRange( - { start: new Date(1999, 11, 15), end: new Date(2001, 0, 15), max: LARGE_MAX_DATE }, + { + start: new Date(1999, 11, 15), + end: new Date(2001, 0, 15), + max: LARGE_MAX_DATE, + }, TEST_BUFFER ); expect(range1.start.toISOString()).toBe(range2.start.toISOString()); @@ -106,11 +161,19 @@ describe('date range to fetch data on request', () => { describe('date range differs when', () => { it('pans a significant distances toward the future', () => { const range1 = requestRange( - { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0), max: LARGE_MAX_DATE }, + { + start: new Date(2000, 0, 0), + end: new Date(2001, 0, 0), + max: LARGE_MAX_DATE, + }, TEST_BUFFER ); const range2 = requestRange( - { start: new Date(2000, 8, 0), end: new Date(2001, 8, 0), max: LARGE_MAX_DATE }, + { + start: new Date(2000, 8, 0), + end: new Date(2001, 8, 0), + max: LARGE_MAX_DATE, + }, TEST_BUFFER ); expect(range1.start < range2.start).toBe(true); @@ -119,11 +182,19 @@ describe('date range to fetch data on request', () => { it('scales out to a significantly larger span of time', () => { const range1 = requestRange( - { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0), max: LARGE_MAX_DATE }, + { + start: new Date(2000, 0, 0), + end: new Date(2001, 0, 0), + max: LARGE_MAX_DATE, + }, TEST_BUFFER ); const range2 = requestRange( - { start: new Date(1999, 0, 0), end: new Date(2002, 8, 0), max: LARGE_MAX_DATE }, + { + start: new Date(1999, 0, 0), + end: new Date(2002, 8, 0), + max: LARGE_MAX_DATE, + }, TEST_BUFFER ); expect(range1.start > range2.start).toBe(true); @@ -132,7 +203,11 @@ describe('date range to fetch data on request', () => { it('scales as expected for a large buffer percent', () => { const range = requestRange( - { start: new Date(2018, 11, 0, 13), end: new Date(2018, 11, 0, 14), max: LARGE_MAX_DATE }, + { + start: new Date(2018, 11, 0, 13), + end: new Date(2018, 11, 0, 14), + max: LARGE_MAX_DATE, + }, 1 ); expect(range.end >= new Date(2018, 11, 0, 15)).toBeTrue(); @@ -145,7 +220,11 @@ describe('date range to fetch data on request', () => { const START_DATE = new Date(1999, 0, 0); const MAX_DATE = new Date(START_DATE.getTime() + DAY_IN_MS); const END_DATE = new Date(MAX_DATE.getTime() + DAY_IN_MS); - const { end } = requestRange({ start: START_DATE, end: END_DATE, max: MAX_DATE }); + const { end } = requestRange({ + start: START_DATE, + end: END_DATE, + max: MAX_DATE, + }); expect(end).toEqual(MAX_DATE); }); @@ -153,7 +232,11 @@ describe('date range to fetch data on request', () => { const MAX_DATE = new Date(1999, 0, 0); const START_DATE = new Date(MAX_DATE.getTime() + DAY_IN_MS); const END_DATE = new Date(START_DATE.getTime() + DAY_IN_MS); - const { start } = requestRange({ start: START_DATE, end: END_DATE, max: MAX_DATE }); + const { start } = requestRange({ + start: START_DATE, + end: END_DATE, + max: MAX_DATE, + }); expect(start).toEqual(MAX_DATE); }); }); diff --git a/packages/core/src/data-module/data-cache/requestRange.ts b/packages/core/src/data-module/data-cache/requestRange.ts index b185bb1ea..a7135edc5 100755 --- a/packages/core/src/data-module/data-cache/requestRange.ts +++ b/packages/core/src/data-module/data-cache/requestRange.ts @@ -41,7 +41,10 @@ export const requestRange = ( const duration = end.getTime() - start.getTime(); const bufferedDuration = roundUp(duration * (1 + buffer * 2)); const durationStep = bufferedDuration / 4; - adjustedStart = new Date(Math.floor(start.getTime() / durationStep) * durationStep - durationStep / 2); + adjustedStart = new Date( + Math.floor(start.getTime() / durationStep) * durationStep - + durationStep / 2 + ); adjustedEnd = new Date(adjustedStart.getTime() + bufferedDuration); } diff --git a/packages/core/src/data-module/data-cache/requestTypes.ts b/packages/core/src/data-module/data-cache/requestTypes.ts index 6c3573f28..be90aeaf8 100755 --- a/packages/core/src/data-module/data-cache/requestTypes.ts +++ b/packages/core/src/data-module/data-cache/requestTypes.ts @@ -39,8 +39,18 @@ export interface TimeSeriesDataRequestSettings { export type OnRequestData = (opts: { request: TimeSeriesDataRequest; resolution: number; // milliseconds, 0 for raw data - onError: (dataStreamId: DataStreamId, resolution: number, error: string, aggregationType?: AggregateType) => void; - onSuccess: (dataStreamId: DataStreamId, dataStream: DataStream, first: Date, last: Date) => void; + onError: ( + dataStreamId: DataStreamId, + resolution: number, + error: string, + aggregationType?: AggregateType + ) => void; + onSuccess: ( + dataStreamId: DataStreamId, + dataStream: DataStream, + first: Date, + last: Date + ) => void; dataStreamId: string; }) => void; diff --git a/packages/core/src/data-module/data-cache/toDataStreams.spec.ts b/packages/core/src/data-module/data-cache/toDataStreams.spec.ts index d7b6b8ac4..677211171 100755 --- a/packages/core/src/data-module/data-cache/toDataStreams.spec.ts +++ b/packages/core/src/data-module/data-cache/toDataStreams.spec.ts @@ -81,11 +81,18 @@ const STORE_WITH_NUMBERS_ONLY: DataStreamsStore = { }; it('returns no data streams when provided no infos or stores', () => { - expect(toDataStreams({ requestInformations: [], dataStreamsStores: {} })).toBeEmpty(); + expect( + toDataStreams({ requestInformations: [], dataStreamsStores: {} }) + ).toBeEmpty(); }); it('returns no data streams when provided no infos with a non-empty store', () => { - expect(toDataStreams({ requestInformations: [], dataStreamsStores: STORE_WITH_NUMBERS_ONLY })).toBeEmpty(); + expect( + toDataStreams({ + requestInformations: [], + dataStreamsStores: STORE_WITH_NUMBERS_ONLY, + }) + ).toBeEmpty(); }); it('returns an array of data streams containing the requested resolutions', () => { diff --git a/packages/core/src/data-module/data-cache/toDataStreams.ts b/packages/core/src/data-module/data-cache/toDataStreams.ts index 1a910c5eb..6180d0514 100755 --- a/packages/core/src/data-module/data-cache/toDataStreams.ts +++ b/packages/core/src/data-module/data-cache/toDataStreams.ts @@ -25,7 +25,10 @@ export const toDataStreams = ({ requestCache: _requestCache, requestHistory: _requestHistory, ...restOfStream - } = (aggregationType && (resolutionStreamStore[parsedResolution]?.[aggregationType] as DataStreamStore)) || + } = (aggregationType && + (resolutionStreamStore[parsedResolution]?.[ + aggregationType + ] as DataStreamStore)) || rawDataStreamStore || {}; diff --git a/packages/core/src/data-module/data-cache/types.ts b/packages/core/src/data-module/data-cache/types.ts index 6cea693a8..ecff8ca02 100755 --- a/packages/core/src/data-module/data-cache/types.ts +++ b/packages/core/src/data-module/data-cache/types.ts @@ -36,7 +36,9 @@ export type AggregationStreamStore = { }; export type DataStoreForID = { - resolutions?: { [resolution: number]: AggregationStreamStore | undefined } | undefined; + resolutions?: + | { [resolution: number]: AggregationStreamStore | undefined } + | undefined; rawData?: DataStreamStore | undefined; }; diff --git a/packages/core/src/data-module/data-source-store/dataSourceStore.spec.ts b/packages/core/src/data-module/data-source-store/dataSourceStore.spec.ts index b67b9155a..cf3451e49 100644 --- a/packages/core/src/data-module/data-source-store/dataSourceStore.spec.ts +++ b/packages/core/src/data-module/data-source-store/dataSourceStore.spec.ts @@ -2,12 +2,18 @@ import DataSourceStore from './dataSourceStore'; import type { DataSource } from '../types'; it('initiate a request on a registered data source', () => { - const customSource: DataSource = { initiateRequest: jest.fn(), getRequestsFromQuery: () => Promise.resolve([]) }; + const customSource: DataSource = { + initiateRequest: jest.fn(), + getRequestsFromQuery: () => Promise.resolve([]), + }; const dataSourceStore = new DataSourceStore(customSource); const query = { source: 'custom' }; - const request = { viewport: { start: new Date(), end: new Date() }, settings: { fetchFromStartToEnd: true } }; + const request = { + viewport: { start: new Date(), end: new Date() }, + settings: { fetchFromStartToEnd: true }, + }; dataSourceStore.initiateRequest( { diff --git a/packages/core/src/data-module/data-source-store/dataSourceStore.ts b/packages/core/src/data-module/data-source-store/dataSourceStore.ts index b68a72f19..c67282fcb 100644 --- a/packages/core/src/data-module/data-source-store/dataSourceStore.ts +++ b/packages/core/src/data-module/data-source-store/dataSourceStore.ts @@ -37,11 +37,17 @@ export default class DataSourceStore { return this.dataSource .getRequestsFromQuery({ query, request }) .then((requestInformations) => - requestInformations.map((requestInfo) => ({ ...requestInfo, cacheSettings: query.cacheSettings })) + requestInformations.map((requestInfo) => ({ + ...requestInfo, + cacheSettings: query.cacheSettings, + })) ); }; - public initiateRequest = (request: DataSourceRequest, requestInformations: RequestInformationAndRange[]) => { + public initiateRequest = ( + request: DataSourceRequest, + requestInformations: RequestInformationAndRange[] + ) => { this.dataSource.initiateRequest(request, requestInformations); }; } diff --git a/packages/core/src/data-module/request-scheduler/requestScheduler.ts b/packages/core/src/data-module/request-scheduler/requestScheduler.ts index 3e734d81c..bba2647ba 100644 --- a/packages/core/src/data-module/request-scheduler/requestScheduler.ts +++ b/packages/core/src/data-module/request-scheduler/requestScheduler.ts @@ -38,7 +38,8 @@ export default class RequestScheduler { return; } - const isExpired = () => refreshExpiration && Date.now() >= refreshExpiration; + const isExpired = () => + refreshExpiration && Date.now() >= refreshExpiration; if (isExpired()) { return; diff --git a/packages/core/src/data-module/subscription-store/subscriptionStore.spec.ts b/packages/core/src/data-module/subscription-store/subscriptionStore.spec.ts index 092f70dd7..d688977bb 100644 --- a/packages/core/src/data-module/subscription-store/subscriptionStore.spec.ts +++ b/packages/core/src/data-module/subscription-store/subscriptionStore.spec.ts @@ -45,14 +45,21 @@ it('updates subscription', async () => { const queries = [ { - assets: [{ assetId: '123', properties: [{ propertyId: 'prop1' }, { propertyId: 'prop2' }] }], + assets: [ + { + assetId: '123', + properties: [{ propertyId: 'prop1' }, { propertyId: 'prop2' }], + }, + ], }, ]; await subscriptionStore.addSubscription(SUBSCRIPTION_ID, MOCK_SUBSCRIPTION); await subscriptionStore.updateSubscription(SUBSCRIPTION_ID, { queries }); - expect(subscriptionStore.getSubscriptions()).toEqual([{ ...MOCK_SUBSCRIPTION, queries }]); + expect(subscriptionStore.getSubscriptions()).toEqual([ + { ...MOCK_SUBSCRIPTION, queries }, + ]); subscriptionStore.removeSubscription(SUBSCRIPTION_ID); }); @@ -70,19 +77,25 @@ it('gets subscription by subscriptionId', async () => { const subscriptionStore = createSubscriptionStore(); await subscriptionStore.addSubscription('some-id', MOCK_SUBSCRIPTION); - expect(subscriptionStore.getSubscription('some-id')).toEqual(MOCK_SUBSCRIPTION); + expect(subscriptionStore.getSubscription('some-id')).toEqual( + MOCK_SUBSCRIPTION + ); subscriptionStore.removeSubscription('some-id'); }); describe('throws errors when', () => { it('throws error when trying to update non-existent subscription', async () => { const subscriptionStore = createSubscriptionStore(); - await expect(subscriptionStore.updateSubscription('some-id', {})).rejects.toThrowError(/some-id/); + await expect( + subscriptionStore.updateSubscription('some-id', {}) + ).rejects.toThrowError(/some-id/); }); it('throws error when trying to remove non-existent subscription', () => { const subscriptionStore = createSubscriptionStore(); - expect(() => subscriptionStore.removeSubscription('some-id')).toThrowError(/some-id/); + expect(() => subscriptionStore.removeSubscription('some-id')).toThrowError( + /some-id/ + ); }); it('throws error when trying to add the same subscription id twice', async () => { @@ -90,7 +103,9 @@ describe('throws errors when', () => { const subscriptionStore = createSubscriptionStore(); await subscriptionStore.addSubscription(SUBSCRIPTION_ID, MOCK_SUBSCRIPTION); - await expect(subscriptionStore.addSubscription(SUBSCRIPTION_ID, MOCK_SUBSCRIPTION)).rejects.toThrowError(/some-id/); + await expect( + subscriptionStore.addSubscription(SUBSCRIPTION_ID, MOCK_SUBSCRIPTION) + ).rejects.toThrowError(/some-id/); subscriptionStore.removeSubscription('some-id'); }); diff --git a/packages/core/src/data-module/subscription-store/subscriptionStore.ts b/packages/core/src/data-module/subscription-store/subscriptionStore.ts index b1a92e290..81ed9c331 100644 --- a/packages/core/src/data-module/subscription-store/subscriptionStore.ts +++ b/packages/core/src/data-module/subscription-store/subscriptionStore.ts @@ -3,7 +3,11 @@ import DataSourceStore from '../data-source-store/dataSourceStore'; import RequestScheduler from '../request-scheduler/requestScheduler'; import { viewportEndDate } from '../../common/viewport'; import { maxCacheDuration } from '../data-cache/caching/caching'; -import type { DataStreamQuery, Subscription, SubscriptionUpdate } from '../types'; +import type { + DataStreamQuery, + Subscription, + SubscriptionUpdate, +} from '../types'; import type { CacheSettings } from '../data-cache/types'; /** @@ -33,7 +37,10 @@ export default class SubscriptionStore { this.cacheSettings = cacheSettings; } - async addSubscription(subscriptionId: string, subscription: Subscription): Promise { + async addSubscription( + subscriptionId: string, + subscription: Subscription + ): Promise { if (this.subscriptions[subscriptionId] == null) { /** * If the subscription is query based @@ -58,7 +65,10 @@ export default class SubscriptionStore { viewportEndDate(subscription.request.viewport).getTime() + Math.max( ...subscription.queries.map((query) => - maxCacheDuration({ ...this.cacheSettings, ...query.cacheSettings }) + maxCacheDuration({ + ...this.cacheSettings, + ...query.cacheSettings, + }) ) ), }); @@ -66,11 +76,20 @@ export default class SubscriptionStore { const { queries, request } = subscription; - const requestInfos = await this.dataSourceStore.getRequestsFromQueries({ queries, request }); + const requestInfos = await this.dataSourceStore.getRequestsFromQueries({ + queries, + request, + }); // Subscribe to changes from the data cache - const unsubscribe = this.dataCache.subscribe(requestInfos, (dataStreams) => - subscription.emit({ dataStreams, viewport: subscription.request.viewport, thresholds: [] }) + const unsubscribe = this.dataCache.subscribe( + requestInfos, + (dataStreams) => + subscription.emit({ + dataStreams, + viewport: subscription.request.viewport, + thresholds: [], + }) ); this.unsubscribeMap[subscriptionId] = () => { @@ -92,7 +111,10 @@ export default class SubscriptionStore { } } - async updateSubscription(subscriptionId: string, subscriptionUpdate: SubscriptionUpdate): Promise { + async updateSubscription( + subscriptionId: string, + subscriptionUpdate: SubscriptionUpdate + ): Promise { if (this.subscriptions[subscriptionId] == null) { throw new Error( `Attempted to update a subscription with an id of "${subscriptionId}", but the requested subscription does not exist.` @@ -126,5 +148,6 @@ export default class SubscriptionStore { getSubscriptions = (): Subscription[] => Object.values(this.subscriptions); - getSubscription = (subscriptionId: string): Subscription => this.subscriptions[subscriptionId]; + getSubscription = (subscriptionId: string): Subscription => + this.subscriptions[subscriptionId]; } diff --git a/packages/core/src/data-module/types.ts b/packages/core/src/data-module/types.ts index f954bc395..bd0968b74 100644 --- a/packages/core/src/data-module/types.ts +++ b/packages/core/src/data-module/types.ts @@ -1,5 +1,8 @@ import { AggregateType } from '@aws-sdk/client-iotsitewise'; -import type { TimeSeriesDataRequest, Viewport } from './data-cache/requestTypes'; +import type { + TimeSeriesDataRequest, + Viewport, +} from './data-cache/requestTypes'; import type { CacheSettings } from './data-cache/types'; import type { ErrorDetails, Threshold } from '../common/types'; @@ -16,7 +19,8 @@ export type DataPointBase = { y: T; }; -export interface DataPoint extends DataPointBase { +export interface DataPoint + extends DataPointBase { x: Timestamp; } @@ -51,15 +55,31 @@ export type RequestInformation = { // Mechanism to associate some information about how the request should be made meta?: Record; }; -export type RequestInformationAndRange = RequestInformation & { start: Date; end: Date }; +export type RequestInformationAndRange = RequestInformation & { + start: Date; + end: Date; +}; export type DataType = 'NUMBER' | 'STRING' | 'BOOLEAN'; export type StreamType = 'ALARM' | 'ANOMALY' | 'ALARM_THRESHOLD'; -export type ComparisonOperator = 'LT' | 'GT' | 'LTE' | 'GTE' | 'EQ' | 'CONTAINS'; - -export type StatusIconType = 'error' | 'active' | 'normal' | 'acknowledged' | 'snoozed' | 'disabled' | 'latched'; +export type ComparisonOperator = + | 'LT' + | 'GT' + | 'LTE' + | 'GTE' + | 'EQ' + | 'CONTAINS'; + +export type StatusIconType = + | 'error' + | 'active' + | 'normal' + | 'acknowledged' + | 'snoozed' + | 'disabled' + | 'latched'; export interface DataStreamBase { data: DataPointBase[]; @@ -69,7 +89,8 @@ export interface DataStreamBase { meta?: Record; } -export interface DataStream extends DataStreamBase { +export interface DataStream + extends DataStreamBase { id: DataStreamId; data: DataPoint[]; resolution: number; @@ -86,7 +107,10 @@ export interface DataStream extends DataStreamB } export type DataSource = { - initiateRequest: (request: DataSourceRequest, requestInformations: RequestInformationAndRange[]) => void; + initiateRequest: ( + request: DataSourceRequest, + requestInformations: RequestInformationAndRange[] + ) => void; getRequestsFromQuery: ({ query, request, @@ -96,7 +120,10 @@ export type DataSource = { }) => Promise; }; -export type DataStreamCallback = (dataStreams: DataStream[], requestInformation: RequestInformationAndRange) => void; +export type DataStreamCallback = ( + dataStreams: DataStream[], + requestInformation: RequestInformationAndRange +) => void; export type OnSuccessCallback = ( dataStreams: DataStream[], requestInformation: RequestInformationAndRange, @@ -112,7 +139,8 @@ export type QuerySubscription = { fulfill: () => void; }; -export type Subscription = QuerySubscription; +export type Subscription = + QuerySubscription; export type DataModuleSubscription = { request: TimeSeriesDataRequest; @@ -137,7 +165,9 @@ export type ErrorCallback = ({ aggregationType?: AggregateType; }) => void; -export type SubscriptionUpdate = Partial, 'emit'>>; +export type SubscriptionUpdate = Partial< + Omit, 'emit'> +>; export type DataSourceRequest = { request: TimeSeriesDataRequest; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 2eeb4e18b..724a16eaa 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -6,7 +6,11 @@ export { combineProviders } from './common/combineProviders'; export { round } from './common/number'; // Data utilities -export { getVisibleData, getDataBeforeDate, pointBisector } from './common/dataFilters'; +export { + getVisibleData, + getDataBeforeDate, + pointBisector, +} from './common/dataFilters'; // Viewport utilities export { parseDuration } from './common/time'; @@ -17,7 +21,12 @@ export { viewportEndDate, viewportStartDate } from './common/viewport'; // Exposed but for internal usage only. Liable to change. export { TimeSeriesDataModule } from './data-module/TimeSeriesDataModule'; -export { DATA_TYPE, STREAM_TYPE, COMPARISON_OPERATOR, STATUS_ICON_TYPE } from './common/constants'; +export { + DATA_TYPE, + STREAM_TYPE, + COMPARISON_OPERATOR, + STATUS_ICON_TYPE, +} from './common/constants'; export const NANO_SECOND_IN_MS = 1 / 1000000; export const SECOND_IN_MS = 1000; @@ -26,5 +35,8 @@ export const HOUR_IN_MS = 60 * MINUTE_IN_MS; export const DAY_IN_MS = 24 * HOUR_IN_MS; export type { Log, Logger } from './logger/logger.interface'; -export type { Metric, MetricsRecorder } from './metricRecorder/metricsRecorder.interface'; +export type { + Metric, + MetricsRecorder, +} from './metricRecorder/metricsRecorder.interface'; export { registerPlugin, getPlugin } from './plugins/pluginsRegistry'; diff --git a/packages/core/src/mockWidgetProperties.ts b/packages/core/src/mockWidgetProperties.ts index 7f1fc5a86..a6919ef22 100755 --- a/packages/core/src/mockWidgetProperties.ts +++ b/packages/core/src/mockWidgetProperties.ts @@ -1,4 +1,9 @@ -import { COMPARISON_OPERATOR, DATA_TYPE, STATUS_ICON_TYPE, STREAM_TYPE } from './common/constants'; +import { + COMPARISON_OPERATOR, + DATA_TYPE, + STATUS_ICON_TYPE, + STREAM_TYPE, +} from './common/constants'; import { DAY_IN_MS } from './common/time'; import type { Threshold } from './common/types'; import type { DataStream } from './data-module/types'; @@ -47,7 +52,9 @@ export const ALARM = 'alarm'; export const OK = 'ok'; export const WITHIN_VIEWPORT_DATE = new Date(2000, 0, 1); -export const BEFORE_VIEWPORT_DATE = new Date(VIEW_PORT.start.getTime() - DAY_IN_MS); +export const BEFORE_VIEWPORT_DATE = new Date( + VIEW_PORT.start.getTime() - DAY_IN_MS +); export const ALARM_STREAM: DataStream = { id: 'alarm-stream', diff --git a/packages/core/src/plugins/metricsRecorderSettings.ts b/packages/core/src/plugins/metricsRecorderSettings.ts index ad3281005..ed80f2c02 100644 --- a/packages/core/src/plugins/metricsRecorderSettings.ts +++ b/packages/core/src/plugins/metricsRecorderSettings.ts @@ -1,3 +1,5 @@ import type { MetricsRecorder } from '../metricRecorder/metricsRecorder.interface'; -export type MetricsRecorderSettings = { provider: () => MetricsRecorder | undefined }; +export type MetricsRecorderSettings = { + provider: () => MetricsRecorder | undefined; +}; diff --git a/packages/core/src/plugins/pluginsRegistry.ts b/packages/core/src/plugins/pluginsRegistry.ts index 905c51667..6ccfa4e9b 100644 --- a/packages/core/src/plugins/pluginsRegistry.ts +++ b/packages/core/src/plugins/pluginsRegistry.ts @@ -60,7 +60,9 @@ export function registerPlugin< export function getPlugin( namespace: Namespace ): ReturnType { - const plugin = pluginsRegistry[namespace].provider() as ReturnType; + const plugin = pluginsRegistry[namespace].provider() as ReturnType< + PluginsRegistry[Namespace]['provider'] + >; return plugin; } diff --git a/packages/core/src/viewportManager/viewportManager.ts b/packages/core/src/viewportManager/viewportManager.ts index 28f80e96a..214f5cd8c 100644 --- a/packages/core/src/viewportManager/viewportManager.ts +++ b/packages/core/src/viewportManager/viewportManager.ts @@ -34,7 +34,8 @@ export const viewportManager = { viewport: Viewport | null; } => { const id = v4(); - const listeners = listenerMap.get(viewportGroup) || new Map(); + const listeners = + listenerMap.get(viewportGroup) || new Map(); listeners.set(id, viewportListener); listenerMap.set(viewportGroup, listeners); diff --git a/packages/dashboard/.eslintrc.js b/packages/dashboard/.eslintrc.js index 55f1ea686..1cef4579d 100644 --- a/packages/dashboard/.eslintrc.js +++ b/packages/dashboard/.eslintrc.js @@ -1,5 +1,9 @@ module.exports = { root: true, - extends: ['iot-app-kit', 'plugin:react-hooks/recommended', 'plugin:jsx-a11y/recommended'], + extends: [ + 'iot-app-kit', + 'plugin:react-hooks/recommended', + 'plugin:jsx-a11y/recommended', + ], plugins: ['jsx-a11y'], }; diff --git a/packages/dashboard/custom.d.ts b/packages/dashboard/custom.d.ts index 4ab00a51b..34025face 100644 --- a/packages/dashboard/custom.d.ts +++ b/packages/dashboard/custom.d.ts @@ -2,7 +2,9 @@ declare module '*.svg' { import * as React from 'react'; - export const ReactComponent: React.FunctionComponent>; + export const ReactComponent: React.FunctionComponent< + React.SVGProps + >; const src: string; export default src; diff --git a/packages/dashboard/e2e/tests/constants.ts b/packages/dashboard/e2e/tests/constants.ts index 431b4943d..7d3470a62 100644 --- a/packages/dashboard/e2e/tests/constants.ts +++ b/packages/dashboard/e2e/tests/constants.ts @@ -5,9 +5,11 @@ export const COMPONENT_SELECTOR = '.dashboard'; export const TOOLBAR_FRAME = '.dashboard-toolbar'; export const RESOURCE_EXPLORER_FRAME = '.collapsible-panel-left'; -export const RESOURCE_EXPLORER_ICON = '[data-testid="collapsed-left-panel-icon"]'; +export const RESOURCE_EXPLORER_ICON = + '[data-testid="collapsed-left-panel-icon"]'; export const MODELED_TAB = '[data-testid="explore-modeled-tab"]'; export const UNMODELED_TAB = '[data-testid="explore-unmodeled-tab"]'; export const ASSET_MODEL_TAB = '[data-testid="explore-asset-model-tab"]'; -export const WIDGET_EMPTY_STATE_TEXT = 'Browse and select to add your asset properties in your line widget.'; +export const WIDGET_EMPTY_STATE_TEXT = + 'Browse and select to add your asset properties in your line widget.'; diff --git a/packages/dashboard/e2e/tests/dashboard/dashboard.spec.ts b/packages/dashboard/e2e/tests/dashboard/dashboard.spec.ts index b8b1347f7..f46e991e3 100644 --- a/packages/dashboard/e2e/tests/dashboard/dashboard.spec.ts +++ b/packages/dashboard/e2e/tests/dashboard/dashboard.spec.ts @@ -46,7 +46,9 @@ test.skip('dashboard resize, move, and select gestures', async ({ page }) => { const widget = await grid.addWidget('kpi', () => location1); // Placeholder text for kpi widget - await expect(page.getByText('Add a property or alarm to populate KPI')).toBeVisible(); + await expect( + page.getByText('Add a property or alarm to populate KPI') + ).toBeVisible(); const initialWidgetBoundingBox = await getBoundingBox(widget); @@ -59,8 +61,12 @@ test.skip('dashboard resize, move, and select gestures', async ({ page }) => { const resizedWidgetBoundingBox = await getBoundingBox(widget); // Widget should be bigger now - await expect(resizedWidgetBoundingBox.width).toBeGreaterThan(initialWidgetBoundingBox.width); - await expect(resizedWidgetBoundingBox.height).toBeGreaterThan(initialWidgetBoundingBox.height); + await expect(resizedWidgetBoundingBox.width).toBeGreaterThan( + initialWidgetBoundingBox.width + ); + await expect(resizedWidgetBoundingBox.height).toBeGreaterThan( + initialWidgetBoundingBox.height + ); // translate the widget down and right await grid.moveSelection(({ source, target }) => ({ @@ -71,8 +77,12 @@ test.skip('dashboard resize, move, and select gestures', async ({ page }) => { const translatedWidgetBoundingBox = await getBoundingBox(widget); // Widget should be shifted now - await expect(translatedWidgetBoundingBox.x).toBeGreaterThan(initialWidgetBoundingBox.x); - await expect(translatedWidgetBoundingBox.y).toBeGreaterThan(initialWidgetBoundingBox.y); + await expect(translatedWidgetBoundingBox.x).toBeGreaterThan( + initialWidgetBoundingBox.x + ); + await expect(translatedWidgetBoundingBox.y).toBeGreaterThan( + initialWidgetBoundingBox.y + ); await grid.clearSelection(); @@ -107,7 +117,10 @@ test('dashboard add and remove multiple widgets', async ({ page }) => { await page.keyboard.down('Delete'); - const deleteBtn = await page.getByRole('button', { name: 'Delete', exact: true }); + const deleteBtn = await page.getByRole('button', { + name: 'Delete', + exact: true, + }); await deleteBtn.click(); diff --git a/packages/dashboard/e2e/tests/resourceExplorer/resourceExplorer.spec.ts b/packages/dashboard/e2e/tests/resourceExplorer/resourceExplorer.spec.ts index 4159a49df..120a79599 100644 --- a/packages/dashboard/e2e/tests/resourceExplorer/resourceExplorer.spec.ts +++ b/packages/dashboard/e2e/tests/resourceExplorer/resourceExplorer.spec.ts @@ -1,6 +1,12 @@ import { expect, test } from '../test'; import { gridUtil } from '../utils/grid'; -import { ASSET_MODEL_TAB, MODELED_TAB, TEST_PAGE, UNMODELED_TAB, WIDGET_EMPTY_STATE_TEXT } from '../constants'; +import { + ASSET_MODEL_TAB, + MODELED_TAB, + TEST_PAGE, + UNMODELED_TAB, + WIDGET_EMPTY_STATE_TEXT, +} from '../constants'; import { resourceExplorerUtil } from '../utils/resourceExplorer'; test('can load resource explorer', async ({ page }) => { @@ -35,8 +41,13 @@ test('can load configure a widget with an asset model', async ({ page }) => { await expect(page.locator(ASSET_MODEL_TAB)).toBeVisible(); await resourceExplorer.tabTo('assetModel'); - const { selectAssetModel, selectAsset, saveAssetModel, selectProperty, addToWidget } = - resourceExplorer.assetModelActions; + const { + selectAssetModel, + selectAsset, + saveAssetModel, + selectProperty, + addToWidget, + } = resourceExplorer.assetModelActions; // configure asset model and default asset and select await selectAssetModel('Site'); await selectAsset('Africa site'); diff --git a/packages/dashboard/e2e/tests/utils/dragAndDrop.ts b/packages/dashboard/e2e/tests/utils/dragAndDrop.ts index c8d87f53d..f1e41b9cc 100644 --- a/packages/dashboard/e2e/tests/utils/dragAndDrop.ts +++ b/packages/dashboard/e2e/tests/utils/dragAndDrop.ts @@ -13,7 +13,13 @@ export type Point = [number, number]; * @returns a point tuple offset from the bounding box top left * */ -const toPointTuple = ({ x, y }: { x: number; y: number }): [x: number, y: number] => [x, y]; +const toPointTuple = ({ + x, + y, +}: { + x: number; + y: number; +}): [x: number, y: number] => [x, y]; /** * @@ -84,7 +90,13 @@ const interpolate = (points: Point[], splits: number) => { return interpolated; }; -export type DragPosition = ({ source, target }: { source: BoundingBox; target: BoundingBox }) => { +export type DragPosition = ({ + source, + target, +}: { + source: BoundingBox; + target: BoundingBox; +}) => { x: number; y: number; }; @@ -127,7 +139,9 @@ export const dragAndDrop = const fromPoint = sourcePosition ? toPointTuple(sourcePosition({ source: fromBb, target: toBb })) : center(fromBb); - const toPoint = targetPosition ? toPointTuple(targetPosition({ source: fromBb, target: toBb })) : center(toBb); + const toPoint = targetPosition + ? toPointTuple(targetPosition({ source: fromBb, target: toBb })) + : center(toBb); const points = interpolate([fromPoint, toPoint], 2); const [firstPoint, ...interpolatedPoints] = points; diff --git a/packages/dashboard/e2e/tests/utils/grid.ts b/packages/dashboard/e2e/tests/utils/grid.ts index 791b5350e..3fb581e2a 100644 --- a/packages/dashboard/e2e/tests/utils/grid.ts +++ b/packages/dashboard/e2e/tests/utils/grid.ts @@ -44,7 +44,8 @@ export const gridUtil = (page: Page) => { * * @returns all of the widget Locators in the grid */ - widgets: async (): Promise => await page.locator(WidgetSelector).all(), + widgets: async (): Promise => + await page.locator(WidgetSelector).all(), /** * @param widgetType - which widget to drag and drop from the component pallette * @param offset - position offset from the top left corner of grid @@ -56,9 +57,13 @@ export const gridUtil = (page: Page) => { ): Promise => { const defaultGridTargetOffset = ({ x, y }: BoundingBox) => ({ x, y }); // Default is 0, 0 on the grid const gridTargetOffset = offset || defaultGridTargetOffset; - const widget = page.getByRole('button', { name: WidgetSelectorMap[widgetType] }); + const widget = page.getByRole('button', { + name: WidgetSelectorMap[widgetType], + }); const draggableWidget = dragGenerator(widget); - await draggableWidget.dragTo(gridArea, { targetPosition: ({ target }) => gridTargetOffset(target) }); + await draggableWidget.dragTo(gridArea, { + targetPosition: ({ target }) => gridTargetOffset(target), + }); const widgets = await page.locator(WidgetSelector).all(); // Assumption that the widget added is last in the DOM const addedWidget = widgets.at(-1); @@ -90,14 +95,20 @@ export const gridUtil = (page: Page) => { selectAll: async () => { await dragGenerator(gridArea).dragTo(gridArea, { sourcePosition: ({ source }) => ({ x: source.x, y: source.y }), - targetPosition: ({ target }) => ({ x: target.x + target.width, y: target.y + target.height }), + targetPosition: ({ target }) => ({ + x: target.x + target.width, + y: target.y + target.height, + }), }); }, /** * @param anchor - anchor of the selection box to click drag * @param targetPosition - the position offset to move the anchor to */ - resizeSelection: async (anchor: keyof typeof SelectionAnchorMap, targetPosition: DragPosition) => { + resizeSelection: async ( + anchor: keyof typeof SelectionAnchorMap, + targetPosition: DragPosition + ) => { const anchorLocator = page.locator(SelectionAnchorMap[anchor]); await dragGenerator(anchorLocator).dragTo(gridArea, { targetPosition, diff --git a/packages/dashboard/e2e/tests/utils/mousePosition.ts b/packages/dashboard/e2e/tests/utils/mousePosition.ts index 40648c0de..721f2ae04 100644 --- a/packages/dashboard/e2e/tests/utils/mousePosition.ts +++ b/packages/dashboard/e2e/tests/utils/mousePosition.ts @@ -5,11 +5,17 @@ import { BoundingBox } from './locator'; * @param box bounding box * @returns the midpoint of the bounding box */ -export const center = (box: BoundingBox): [x: number, y: number] => [box.x + box.width / 2, box.y + box.height / 2]; +export const center = (box: BoundingBox): [x: number, y: number] => [ + box.x + box.width / 2, + box.y + box.height / 2, +]; /** * * @param box bounding box * @returns the midpoint of the bounding box */ -export const topCenter = (box: BoundingBox): [x: number, y: number] => [box.x + box.width / 2, box.y + 10]; +export const topCenter = (box: BoundingBox): [x: number, y: number] => [ + box.x + box.width / 2, + box.y + 10, +]; diff --git a/packages/dashboard/e2e/tests/utils/resourceExplorer.ts b/packages/dashboard/e2e/tests/utils/resourceExplorer.ts index e85a942c3..c463c02bc 100644 --- a/packages/dashboard/e2e/tests/utils/resourceExplorer.ts +++ b/packages/dashboard/e2e/tests/utils/resourceExplorer.ts @@ -1,5 +1,10 @@ import { Page, expect } from '@playwright/test'; -import { RESOURCE_EXPLORER_FRAME, MODELED_TAB, UNMODELED_TAB, ASSET_MODEL_TAB } from '../constants'; +import { + RESOURCE_EXPLORER_FRAME, + MODELED_TAB, + UNMODELED_TAB, + ASSET_MODEL_TAB, +} from '../constants'; const tabMap = { modeled: MODELED_TAB, diff --git a/packages/dashboard/rollup.config.js b/packages/dashboard/rollup.config.js index 0d6d56218..4b9cd2100 100644 --- a/packages/dashboard/rollup.config.js +++ b/packages/dashboard/rollup.config.js @@ -40,7 +40,12 @@ export default [ typescript({ tsconfig: './tsconfig.json', tsconfigOverride: { - exclude: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'], + exclude: [ + '**/*.test.ts', + '**/*.test.tsx', + '**/*.spec.ts', + '**/*.spec.tsx', + ], }, }), postcss({ diff --git a/packages/dashboard/src/components/actions/index.tsx b/packages/dashboard/src/components/actions/index.tsx index 91af29925..1a149d968 100644 --- a/packages/dashboard/src/components/actions/index.tsx +++ b/packages/dashboard/src/components/actions/index.tsx @@ -31,7 +31,8 @@ const Actions: React.FC = ({ readOnly, onSave, }) => { - const [dashboardSettingsVisible, setDashboardSettingsVisible] = useState(false); + const [dashboardSettingsVisible, setDashboardSettingsVisible] = + useState(false); const dispatch = useDispatch(); const { viewport } = useViewport(); @@ -86,7 +87,12 @@ const Actions: React.FC = ({ {onSave && } - {editable && } + {editable && ( + + )} {editable && !readOnly && ( )} - + } diff --git a/packages/dashboard/src/components/contextMenu/contextMenuOptions.ts b/packages/dashboard/src/components/contextMenu/contextMenuOptions.ts index 452def649..5b13e5f3f 100644 --- a/packages/dashboard/src/components/contextMenu/contextMenuOptions.ts +++ b/packages/dashboard/src/components/contextMenu/contextMenuOptions.ts @@ -31,14 +31,18 @@ export type ContextMenuConfigurationProps = { }; }; -type OptionCreator = (props: ContextMenuConfigurationProps) => ContextMenuOptionConfiguration; +type OptionCreator = ( + props: ContextMenuConfigurationProps +) => ContextMenuOptionConfiguration; type ContextMenuConfigurationCreator = { id: string; options: OptionCreator[]; }; -const createCopyOption: OptionCreator = (props: ContextMenuConfigurationProps): ContextMenuOptionConfiguration => { +const createCopyOption: OptionCreator = ( + props: ContextMenuConfigurationProps +): ContextMenuOptionConfiguration => { const { actions, messages, state } = props; return { id: 'copy', @@ -49,7 +53,9 @@ const createCopyOption: OptionCreator = (props: ContextMenuConfigurationProps): }; }; -const createPasteOption: OptionCreator = (props: ContextMenuConfigurationProps): ContextMenuOptionConfiguration => { +const createPasteOption: OptionCreator = ( + props: ContextMenuConfigurationProps +): ContextMenuOptionConfiguration => { const { actions, messages, state } = props; return { id: 'paste', @@ -60,7 +66,9 @@ const createPasteOption: OptionCreator = (props: ContextMenuConfigurationProps): }; }; -const createDeleteOption: OptionCreator = (props: ContextMenuConfigurationProps): ContextMenuOptionConfiguration => { +const createDeleteOption: OptionCreator = ( + props: ContextMenuConfigurationProps +): ContextMenuOptionConfiguration => { const { actions, messages, state } = props; return { id: 'delete', @@ -107,7 +115,9 @@ const configuration: ContextMenuConfigurationCreator[] = [ }, ]; -export const createContextMenuOptions = (props: ContextMenuConfigurationProps): ContextMenuConfiguration => +export const createContextMenuOptions = ( + props: ContextMenuConfigurationProps +): ContextMenuConfiguration => configuration.map((section) => ({ ...section, options: section.options.map((createOption) => createOption(props)), diff --git a/packages/dashboard/src/components/contextMenu/index.test.tsx b/packages/dashboard/src/components/contextMenu/index.test.tsx index 24eabbd6f..018658f70 100644 --- a/packages/dashboard/src/components/contextMenu/index.test.tsx +++ b/packages/dashboard/src/components/contextMenu/index.test.tsx @@ -19,7 +19,10 @@ const renderContextMenu = (args: ContextMenuProps, open?: boolean) => { act(() => { const { container } = render( -
+
diff --git a/packages/dashboard/src/components/contextMenu/index.tsx b/packages/dashboard/src/components/contextMenu/index.tsx index 49555d90d..18050cffe 100644 --- a/packages/dashboard/src/components/contextMenu/index.tsx +++ b/packages/dashboard/src/components/contextMenu/index.tsx @@ -1,7 +1,10 @@ import React, { useCallback, useEffect, useState } from 'react'; import Menu from './menu'; import ContextMenuOption from './option'; -import { DASHBOARD_CONTAINER_ID, getDashboardPosition } from '../grid/getDashboardPosition'; +import { + DASHBOARD_CONTAINER_ID, + getDashboardPosition, +} from '../grid/getDashboardPosition'; import { useKeyPress } from '~/hooks/useKeyPress'; import { createContextMenuOptions } from './contextMenuOptions'; import { useLayers } from '../internalDashboard/useLayers'; @@ -32,7 +35,8 @@ const ContextMenu: React.FC = ({ sendWidgetsToBack, }) => { const [contextMenuOpen, setContextMenuOpen] = useState(false); - const [contextMenuPosition, setContextMenuPosition] = useState(null); + const [contextMenuPosition, setContextMenuPosition] = + useState(null); const { contextMenuLayer } = useLayers(); @@ -74,7 +78,9 @@ const ContextMenu: React.FC = ({ }; const copyAction = withClose(() => copyWidgets()); - const pasteAction = withClose(() => pasteWidgets(contextMenuPosition || { x: 0, y: 0 })); + const pasteAction = withClose(() => + pasteWidgets(contextMenuPosition || { x: 0, y: 0 }) + ); const deleteAction = withClose(() => deleteWidgets()); const bringToFrontAction = withClose(() => bringWidgetsToFront()); const sendToBackAction = withClose(() => sendWidgetsToBack()); @@ -96,7 +102,10 @@ const ContextMenu: React.FC = ({ return contextMenuOpen && contextMenuPosition ? (
- + {configuration.map(({ id: sectionId, options }) => (
    = ({ style={{ margin: `calc(${spaceScaledXxxs} * -1)` }} > {options.map(({ id: optionId, text, action, hotkey, disabled }) => ( - + ))}
))} diff --git a/packages/dashboard/src/components/contextMenu/menu.tsx b/packages/dashboard/src/components/contextMenu/menu.tsx index 7caadc8d1..857a02daa 100644 --- a/packages/dashboard/src/components/contextMenu/menu.tsx +++ b/packages/dashboard/src/components/contextMenu/menu.tsx @@ -20,23 +20,31 @@ export type MenuProps = { }; const Menu: React.FC = ({ position, clickOutside, children }) => { - const [referenceElement, setReferenceElement] = useState(null); + const [referenceElement, setReferenceElement] = useState( + null + ); - const popperElement = useClickOutside((e) => clickOutside && clickOutside(e)); + const popperElement = useClickOutside( + (e) => clickOutside && clickOutside(e) + ); - const { styles, attributes } = usePopper(referenceElement, popperElement.current, { - placement: 'right-start', - modifiers: [ - flip, - preventOverflow, - { - name: 'offset', - options: { - offset: [0, 10], + const { styles, attributes } = usePopper( + referenceElement, + popperElement.current, + { + placement: 'right-start', + modifiers: [ + flip, + preventOverflow, + { + name: 'offset', + options: { + offset: [0, 10], + }, }, - }, - ], - }); + ], + } + ); return ( <> diff --git a/packages/dashboard/src/components/contextMenu/option.tsx b/packages/dashboard/src/components/contextMenu/option.tsx index 3ea4e79fd..cf5ee73dd 100644 --- a/packages/dashboard/src/components/contextMenu/option.tsx +++ b/packages/dashboard/src/components/contextMenu/option.tsx @@ -19,7 +19,12 @@ export type ContextMenuOptionProps = { action: () => void; }; -const ContextMenuOption: React.FC = ({ disabled, text, hotkey, action }) => { +const ContextMenuOption: React.FC = ({ + disabled, + text, + hotkey, + action, +}) => { const [hover, setHover] = useState(false); let hoverStyle; @@ -34,7 +39,10 @@ const ContextMenuOption: React.FC = ({ disabled, text, h }; } - const disabledStyle = { color: colorBackgroundControlDisabled, cursor: 'not-allowed' }; + const disabledStyle = { + color: colorBackgroundControlDisabled, + cursor: 'not-allowed', + }; return ( // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
  • Promise.resolve({ isError: false, data: [] })); +const mockFetchViewportData = jest.fn(() => + Promise.resolve({ isError: false, data: [] }) +); jest.mock('./useViewportData', () => ({ useViewportData: jest.fn(() => ({ fetchViewportData: mockFetchViewportData, @@ -64,14 +69,21 @@ it('creates a file for download if data is empty', async function () { } as StyledSiteWiseQueryConfig; render( - + ); const downloadButton = screen.queryByTestId(/csv-download-button/); downloadButton && fireEvent.click(downloadButton); const createElementSpyOn = jest.spyOn(document, 'createElement'); - expect(useViewportData).toHaveBeenCalledWith({ queryConfig: mockQueryConfig, client: mockClient }); + expect(useViewportData).toHaveBeenCalledWith({ + queryConfig: mockQueryConfig, + client: mockClient, + }); await waitFor(() => expect(mockFetchViewportData).toHaveBeenCalled()); await waitFor(() => expect(createElementSpyOn).toBeCalledWith('a')); }); diff --git a/packages/dashboard/src/components/csvDownloadButton/errorDownload.test.tsx b/packages/dashboard/src/components/csvDownloadButton/errorDownload.test.tsx index ddbe335be..85cb5b2e7 100644 --- a/packages/dashboard/src/components/csvDownloadButton/errorDownload.test.tsx +++ b/packages/dashboard/src/components/csvDownloadButton/errorDownload.test.tsx @@ -25,7 +25,9 @@ const MOCK_QUERY: StyledAssetQuery = { properties: [{ propertyAlias: alias1 }], }; -const mockFetchViewportData = jest.fn(() => Promise.resolve({ isError: true, data: [] })); +const mockFetchViewportData = jest.fn(() => + Promise.resolve({ isError: true, data: [] }) +); jest.mock('./useViewportData', () => ({ useViewportData: jest.fn(() => ({ fetchViewportData: mockFetchViewportData, diff --git a/packages/dashboard/src/components/csvDownloadButton/getDescribedTimeSeries.tsx b/packages/dashboard/src/components/csvDownloadButton/getDescribedTimeSeries.tsx index 5a7cbf2f5..bc2f3b6bb 100644 --- a/packages/dashboard/src/components/csvDownloadButton/getDescribedTimeSeries.tsx +++ b/packages/dashboard/src/components/csvDownloadButton/getDescribedTimeSeries.tsx @@ -1,4 +1,7 @@ -import { DescribeTimeSeriesCommand, IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; +import { + DescribeTimeSeriesCommand, + IoTSiteWiseClient, +} from '@aws-sdk/client-iotsitewise'; import invariant from 'tiny-invariant'; const isEnabled = (input?: string): input is string => Boolean(input); diff --git a/packages/dashboard/src/components/csvDownloadButton/index.tsx b/packages/dashboard/src/components/csvDownloadButton/index.tsx index 5a8f3420c..0d25f3aaa 100644 --- a/packages/dashboard/src/components/csvDownloadButton/index.tsx +++ b/packages/dashboard/src/components/csvDownloadButton/index.tsx @@ -27,7 +27,11 @@ const EMPTY_DATA = [ const isQueryEmpty = (queryConfig: StyledSiteWiseQueryConfig) => { const query = queryConfig.query; - return !query?.assets?.length && !query?.properties?.length && !query?.assetModels?.length; + return ( + !query?.assets?.length && + !query?.properties?.length && + !query?.assetModels?.length + ); }; export const CSVDownloadButton = ({ @@ -35,7 +39,11 @@ export const CSVDownloadButton = ({ fileName, client, ...rest -}: { queryConfig: StyledSiteWiseQueryConfig; client: IoTSiteWiseClient; fileName: string } & ButtonProps) => { +}: { + queryConfig: StyledSiteWiseQueryConfig; + client: IoTSiteWiseClient; + fileName: string; +} & ButtonProps) => { const { fetchViewportData } = useViewportData({ queryConfig, client }); const [isDownloading, setIsDownloading] = useState(false); @@ -52,7 +60,8 @@ export const CSVDownloadButton = ({ return; } - const stringCSVData = data.length === 0 ? unparse(EMPTY_DATA) : unparse(data); + const stringCSVData = + data.length === 0 ? unparse(EMPTY_DATA) : unparse(data); const file = new Blob([stringCSVData], { type: 'text/csv' }); // create Anchor element with download attribute, click on it, and then delete it diff --git a/packages/dashboard/src/components/csvDownloadButton/succesfulDownload.test.tsx b/packages/dashboard/src/components/csvDownloadButton/succesfulDownload.test.tsx index 5795d29f5..30cdfa557 100644 --- a/packages/dashboard/src/components/csvDownloadButton/succesfulDownload.test.tsx +++ b/packages/dashboard/src/components/csvDownloadButton/succesfulDownload.test.tsx @@ -6,7 +6,10 @@ import { Quality, type IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; import { CSVDownloadButton } from './index'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useViewportData } from './useViewportData'; -import { StyledAssetQuery, StyledSiteWiseQueryConfig } from '~/customization/widgets/types'; +import { + StyledAssetQuery, + StyledSiteWiseQueryConfig, +} from '~/customization/widgets/types'; const testQueryClient = new QueryClient({ defaultOptions: { @@ -40,7 +43,10 @@ const MOCK_CSV_OBJECT_1 = { }; const mockFetchViewportData = jest.fn(() => - Promise.resolve({ isError: false, data: [MOCK_CSV_OBJECT_1, MOCK_CSV_OBJECT_1] }) + Promise.resolve({ + isError: false, + data: [MOCK_CSV_OBJECT_1, MOCK_CSV_OBJECT_1], + }) ); jest.mock('./useViewportData', () => ({ useViewportData: jest.fn(() => ({ @@ -79,14 +85,21 @@ it('creates a file for download if query has content', async function () { } as StyledSiteWiseQueryConfig; render( - + ); const downloadButton = screen.queryByTestId(/csv-download-button/); downloadButton && fireEvent.click(downloadButton); const createElementSpyOn = jest.spyOn(document, 'createElement'); - expect(useViewportData).toHaveBeenCalledWith({ queryConfig: mockQueryConfig, client: mockClient }); + expect(useViewportData).toHaveBeenCalledWith({ + queryConfig: mockQueryConfig, + client: mockClient, + }); await waitFor(() => expect(mockFetchViewportData).toHaveBeenCalled()); await waitFor(() => expect(createElementSpyOn).toBeCalledWith('a')); }); diff --git a/packages/dashboard/src/components/csvDownloadButton/useViewportData.test.tsx b/packages/dashboard/src/components/csvDownloadButton/useViewportData.test.tsx index d7a0fcf9b..26c31bb13 100644 --- a/packages/dashboard/src/components/csvDownloadButton/useViewportData.test.tsx +++ b/packages/dashboard/src/components/csvDownloadButton/useViewportData.test.tsx @@ -3,7 +3,10 @@ import { server } from '~/msw/server'; import { useViewportData } from './useViewportData'; import { useTimeSeriesData } from '@iot-app-kit/react-components'; import { renderHook, waitFor } from '@testing-library/react'; -import { StyledAssetQuery, StyledSiteWiseQueryConfig } from '~/customization/widgets/types'; +import { + StyledAssetQuery, + StyledSiteWiseQueryConfig, +} from '~/customization/widgets/types'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import React from 'react'; import { DataStream } from '@iot-app-kit/core'; @@ -16,7 +19,9 @@ const PROPERTY_ID_2 = 'some-property-id-2'; const ALIAS_1 = '/aws/windfarm/0/turbine/0/temperature'; const MOCK_QUERY: StyledAssetQuery = { - assets: [{ assetId: ASSET_ID_1, properties: [{ propertyId: PROPERTY_ID_1 }] }], + assets: [ + { assetId: ASSET_ID_1, properties: [{ propertyId: PROPERTY_ID_1 }] }, + ], properties: [{ propertyAlias: ALIAS_1 }], }; @@ -53,12 +58,16 @@ jest.mock('@iot-app-kit/react-components', () => { const original = jest.requireActual('@iot-app-kit/react-components'); return { ...original, - useTimeSeriesData: jest.fn(() => ({ dataStreams: [DATA_STREAM_1, DATA_STREAM_2] })), + useTimeSeriesData: jest.fn(() => ({ + dataStreams: [DATA_STREAM_1, DATA_STREAM_2], + })), }; }); it('given query with modeled data it returns a list of CSVObjects', async () => { - (useTimeSeriesData as jest.Mock).mockImplementation(jest.fn(() => ({ dataStreams: [DATA_STREAM_1, DATA_STREAM_2] }))); + (useTimeSeriesData as jest.Mock).mockImplementation( + jest.fn(() => ({ dataStreams: [DATA_STREAM_1, DATA_STREAM_2] })) + ); server.use(describeTimeSeriesHandler()); const mockQueryConfig = { @@ -71,11 +80,20 @@ it('given query with modeled data it returns a list of CSVObjects', async () => const { result } = renderHook( () => useViewportData({ - viewport: { start: new Date(1699554901783), end: new Date(1699555901883) }, + viewport: { + start: new Date(1699554901783), + end: new Date(1699555901883), + }, queryConfig: mockQueryConfig, client: createMockSiteWiseSDK() as unknown as IoTSiteWiseClient, }), - { wrapper: ({ children }) => {children} } + { + wrapper: ({ children }) => ( + + {children} + + ), + } ); await waitFor(() => expect(useTimeSeriesData).toBeCalled()); const { fetchViewportData } = result.current; @@ -92,7 +110,9 @@ it('given query with modeled data it returns a list of CSVObjects', async () => }); it('given empty query it returns an empty list ofCSVObjects', async () => { - (useTimeSeriesData as jest.Mock).mockImplementation(jest.fn(() => ({ dataStreams: [] }))); + (useTimeSeriesData as jest.Mock).mockImplementation( + jest.fn(() => ({ dataStreams: [] })) + ); const mockQueryConfig = { source: 'iotsitewise', @@ -102,11 +122,20 @@ it('given empty query it returns an empty list ofCSVObjects', async () => { const { result } = renderHook( () => useViewportData({ - viewport: { start: new Date(1699554901783), end: new Date(1699555901883) }, + viewport: { + start: new Date(1699554901783), + end: new Date(1699555901883), + }, queryConfig: mockQueryConfig, client: createMockSiteWiseSDK() as unknown as IoTSiteWiseClient, }), - { wrapper: ({ children }) => {children} } + { + wrapper: ({ children }) => ( + + {children} + + ), + } ); await waitFor(() => expect(useTimeSeriesData).toBeCalled()); diff --git a/packages/dashboard/src/components/csvDownloadButton/useViewportData.ts b/packages/dashboard/src/components/csvDownloadButton/useViewportData.ts index cfdeebafa..ba29de650 100644 --- a/packages/dashboard/src/components/csvDownloadButton/useViewportData.ts +++ b/packages/dashboard/src/components/csvDownloadButton/useViewportData.ts @@ -1,4 +1,9 @@ -import { Viewport, parseDuration, DataStream, DataPoint } from '@iot-app-kit/core'; +import { + Viewport, + parseDuration, + DataStream, + DataPoint, +} from '@iot-app-kit/core'; import { useTimeSeriesData, useViewport } from '@iot-app-kit/react-components'; import { StyledSiteWiseQueryConfig } from '~/customization/widgets/types'; import { useListAssetPropertiesMapQuery } from '~/hooks/useAssetDescriptionQueries'; @@ -10,12 +15,19 @@ import { getDescribedTimeSeries } from './getDescribedTimeSeries'; const DEFAULT_VIEWPORT = { duration: '10m' }; // Check if time is within passed in viewport OR within last x amount of time from request -const isTimeWithinViewport = (dataPointTimestamp: number, viewport: Viewport, timeOfRequestMS: number) => { +const isTimeWithinViewport = ( + dataPointTimestamp: number, + viewport: Viewport, + timeOfRequestMS: number +) => { const currentPoint = new Date(dataPointTimestamp); if ('duration' in viewport) { // absolute time range const duration = parseDuration(viewport.duration); - return currentPoint >= new Date(timeOfRequestMS - duration) && currentPoint <= new Date(timeOfRequestMS); + return ( + currentPoint >= new Date(timeOfRequestMS - duration) && + currentPoint <= new Date(timeOfRequestMS) + ); } else { // relative time range return currentPoint >= viewport.start && currentPoint <= viewport.end; @@ -34,16 +46,30 @@ export const useViewportData = ({ const queries = useQueries(queryConfig.query); const { dataStreams } = useTimeSeriesData({ queries }); - const describedAssetsMapQuery = useListAssetPropertiesMapQuery(queryConfig.query); + const describedAssetsMapQuery = useListAssetPropertiesMapQuery( + queryConfig.query + ); const describedAssetsMap = describedAssetsMapQuery.data ?? {}; const { viewport: injectedViewport } = useViewport(); const viewport = passedInViewport || injectedViewport || DEFAULT_VIEWPORT; // flatten all the data in a single dataStream into one array of CSVDownloadObject - const flattenDataPoints = async (dataStream: DataStream, timeOfRequestMS: number) => { - const { id, unit, resolution, aggregationType, data, error: dataStreamError } = dataStream; - const isUnmodeledData = queryConfig.query?.properties?.some((pr) => pr.propertyAlias === id); + const flattenDataPoints = async ( + dataStream: DataStream, + timeOfRequestMS: number + ) => { + const { + id, + unit, + resolution, + aggregationType, + data, + error: dataStreamError, + } = dataStream; + const isUnmodeledData = queryConfig.query?.properties?.some( + (pr) => pr.propertyAlias === id + ); const { data: unmodeledDescribedTimeSeries, isError } = isUnmodeledData ? await getDescribedTimeSeries({ client, @@ -55,60 +81,71 @@ export const useViewportData = ({ return { flatPoints: [], isError }; } - const flatPoints = data.reduce((flattenedData: CSVDownloadObject[], currentDataPoint: DataPoint) => { - const { x: xValue, y: yValue } = currentDataPoint; - const pointWithinViewport = isTimeWithinViewport(xValue, viewport, timeOfRequestMS); - - // do not include data point if it falls outside viewport range - if (pointWithinViewport) { - const commonData = { - value: yValue, - unit, - timestamp: new Date(xValue).toISOString(), - aggregationType: aggregationType, - resolution, - dataQuality: Quality.GOOD, - }; - - if (isUnmodeledData) { - const unmodeledDataPoint: CSVDownloadObject = { - ...commonData, - propertyAlias: unmodeledDescribedTimeSeries?.alias, - dataType: unmodeledDescribedTimeSeries?.dataType, - dataTypeSpec: unmodeledDescribedTimeSeries?.dataTypeSpec, - assetId: unmodeledDescribedTimeSeries?.assetId, - propertyId: undefined, + const flatPoints = data.reduce( + (flattenedData: CSVDownloadObject[], currentDataPoint: DataPoint) => { + const { x: xValue, y: yValue } = currentDataPoint; + const pointWithinViewport = isTimeWithinViewport( + xValue, + viewport, + timeOfRequestMS + ); + + // do not include data point if it falls outside viewport range + if (pointWithinViewport) { + const commonData = { + value: yValue, + unit, + timestamp: new Date(xValue).toISOString(), + aggregationType: aggregationType, + resolution, + dataQuality: Quality.GOOD, }; - flattenedData.push(unmodeledDataPoint); - } else { - const assetPropId = dataStream.id.split('---'); - const describedModelProperty = describedAssetsMap[assetPropId[0]]?.properties.find( - ({ propertyId }) => propertyId === assetPropId[1] - ); - - const modeledDataPoint: CSVDownloadObject = { - ...commonData, - assetName: describedAssetsMap[assetPropId[0]]?.assetName, - propertyName: describedModelProperty?.name, - propertyAlias: describedModelProperty?.alias, - dataType: describedModelProperty?.dataType, - dataTypeSpec: undefined, - assetId: assetPropId[0], - propertyId: assetPropId[1], - }; - - flattenedData.push(modeledDataPoint); + if (isUnmodeledData) { + const unmodeledDataPoint: CSVDownloadObject = { + ...commonData, + propertyAlias: unmodeledDescribedTimeSeries?.alias, + dataType: unmodeledDescribedTimeSeries?.dataType, + dataTypeSpec: unmodeledDescribedTimeSeries?.dataTypeSpec, + assetId: unmodeledDescribedTimeSeries?.assetId, + propertyId: undefined, + }; + + flattenedData.push(unmodeledDataPoint); + } else { + const assetPropId = dataStream.id.split('---'); + const describedModelProperty = describedAssetsMap[ + assetPropId[0] + ]?.properties.find( + ({ propertyId }) => propertyId === assetPropId[1] + ); + + const modeledDataPoint: CSVDownloadObject = { + ...commonData, + assetName: describedAssetsMap[assetPropId[0]]?.assetName, + propertyName: describedModelProperty?.name, + propertyAlias: describedModelProperty?.alias, + dataType: describedModelProperty?.dataType, + dataTypeSpec: undefined, + assetId: assetPropId[0], + propertyId: assetPropId[1], + }; + + flattenedData.push(modeledDataPoint); + } } - } - return flattenedData; - }, [] as CSVDownloadObject[]); + return flattenedData; + }, + [] as CSVDownloadObject[] + ); return { flatPoints, isError: false }; }; const fetchViewportData = async (timeOfRequestMS: number) => { - const promises = dataStreams.map((dataStream: DataStream) => flattenDataPoints(dataStream, timeOfRequestMS)); + const promises = dataStreams.map((dataStream: DataStream) => + flattenDataPoints(dataStream, timeOfRequestMS) + ); const flatData = await Promise.all(promises); const isError = flatData.some((point) => point.isError); diff --git a/packages/dashboard/src/components/customOrangeButton/index.spec.tsx b/packages/dashboard/src/components/customOrangeButton/index.spec.tsx index 36137f8d8..171871065 100644 --- a/packages/dashboard/src/components/customOrangeButton/index.spec.tsx +++ b/packages/dashboard/src/components/customOrangeButton/index.spec.tsx @@ -7,11 +7,15 @@ describe('CustomOrangeButton', () => { const handleClick = jest.fn(); test('renders button with correct title', () => { - const { getByText } = render(); + const { getByText } = render( + + ); expect(getByText(title)).toBeInTheDocument(); }); test('calls handleClick when button is clicked', () => { - const { getByRole } = render(); + const { getByRole } = render( + + ); const button = getByRole('button'); fireEvent.click(button); expect(handleClick).toHaveBeenCalledTimes(1); diff --git a/packages/dashboard/src/components/customOrangeButton/index.tsx b/packages/dashboard/src/components/customOrangeButton/index.tsx index 4cee7129a..fb396e4a7 100644 --- a/packages/dashboard/src/components/customOrangeButton/index.tsx +++ b/packages/dashboard/src/components/customOrangeButton/index.tsx @@ -11,7 +11,12 @@ const CustomOrangeButton = ({ ...rest }: { title: string; handleClick?: () => void } & ButtonProps) => { return ( - ); diff --git a/packages/dashboard/src/components/dashboard/clientContext.ts b/packages/dashboard/src/components/dashboard/clientContext.ts index e5d166728..cc34b68cc 100644 --- a/packages/dashboard/src/components/dashboard/clientContext.ts +++ b/packages/dashboard/src/components/dashboard/clientContext.ts @@ -1,6 +1,8 @@ import { createContext, useContext } from 'react'; import { DashboardIotSiteWiseClients } from '~/types'; -export const ClientContext = createContext>({}); +export const ClientContext = createContext< + Partial +>({}); export const useClients = () => useContext(ClientContext); diff --git a/packages/dashboard/src/components/dashboard/getClients.ts b/packages/dashboard/src/components/dashboard/getClients.ts index 8ae1fb33b..38fb89316 100644 --- a/packages/dashboard/src/components/dashboard/getClients.ts +++ b/packages/dashboard/src/components/dashboard/getClients.ts @@ -1,15 +1,23 @@ import { IoTEventsClient } from '@aws-sdk/client-iot-events'; import { IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; import { IoTTwinMakerClient } from '@aws-sdk/client-iottwinmaker'; -import { DashboardClientConfiguration, DashboardIotSiteWiseClients, DashboardClientCredentials } from '~/types'; +import { + DashboardClientConfiguration, + DashboardIotSiteWiseClients, + DashboardClientCredentials, +} from '~/types'; export const isCredentials = ( dashboardClientConfiguration: DashboardClientConfiguration ): dashboardClientConfiguration is DashboardClientCredentials => - 'awsCredentials' in dashboardClientConfiguration && 'awsRegion' in dashboardClientConfiguration; + 'awsCredentials' in dashboardClientConfiguration && + 'awsRegion' in dashboardClientConfiguration; -export const getClients = (dashboardClientConfiguration: DashboardClientConfiguration): DashboardIotSiteWiseClients => { - if (!isCredentials(dashboardClientConfiguration)) return dashboardClientConfiguration; +export const getClients = ( + dashboardClientConfiguration: DashboardClientConfiguration +): DashboardIotSiteWiseClients => { + if (!isCredentials(dashboardClientConfiguration)) + return dashboardClientConfiguration; const iotEventsClient = new IoTEventsClient({ credentials: dashboardClientConfiguration.awsCredentials, diff --git a/packages/dashboard/src/components/dashboard/getQueries.ts b/packages/dashboard/src/components/dashboard/getQueries.ts index 6a26ed6db..2e837df83 100644 --- a/packages/dashboard/src/components/dashboard/getQueries.ts +++ b/packages/dashboard/src/components/dashboard/getQueries.ts @@ -1,9 +1,16 @@ -import { DashboardClientConfiguration, DashboardIotSiteWiseQueries } from '~/types'; +import { + DashboardClientConfiguration, + DashboardIotSiteWiseQueries, +} from '~/types'; import { initialize } from '@iot-app-kit/source-iotsitewise'; import { getClients } from './getClients'; -export const getQueries = (dashboardClientConfiguration: DashboardClientConfiguration): DashboardIotSiteWiseQueries => { - const { iotEventsClient, iotSiteWiseClient } = getClients(dashboardClientConfiguration); +export const getQueries = ( + dashboardClientConfiguration: DashboardClientConfiguration +): DashboardIotSiteWiseQueries => { + const { iotEventsClient, iotSiteWiseClient } = getClients( + dashboardClientConfiguration + ); if (!iotEventsClient || !iotSiteWiseClient) { throw new Error('Could not initialize iot sitewise query.'); diff --git a/packages/dashboard/src/components/dashboard/index.test.tsx b/packages/dashboard/src/components/dashboard/index.test.tsx index 5818935f4..be388c593 100644 --- a/packages/dashboard/src/components/dashboard/index.test.tsx +++ b/packages/dashboard/src/components/dashboard/index.test.tsx @@ -1,5 +1,8 @@ import { render } from '@testing-library/react'; -import { createMockIoTEventsSDK, createMockSiteWiseSDK } from '@iot-app-kit/testing-util'; +import { + createMockIoTEventsSDK, + createMockSiteWiseSDK, +} from '@iot-app-kit/testing-util'; import Dashboard from './index'; import React from 'react'; @@ -20,8 +23,11 @@ it('renders', function () { }} clientConfiguration={{ iotEventsClient: createMockIoTEventsSDK(), - iotSiteWiseClient: createMockSiteWiseSDK() as unknown as IoTSiteWiseClient, - iotTwinMakerClient: { send: jest.fn() } as unknown as IoTTwinMakerClient, + iotSiteWiseClient: + createMockSiteWiseSDK() as unknown as IoTSiteWiseClient, + iotTwinMakerClient: { + send: jest.fn(), + } as unknown as IoTTwinMakerClient, }} /> ); @@ -45,8 +51,11 @@ it('renders in readonly initially', function () { }} clientConfiguration={{ iotEventsClient: createMockIoTEventsSDK(), - iotSiteWiseClient: createMockSiteWiseSDK() as unknown as IoTSiteWiseClient, - iotTwinMakerClient: { send: jest.fn() } as unknown as IoTTwinMakerClient, + iotSiteWiseClient: + createMockSiteWiseSDK() as unknown as IoTSiteWiseClient, + iotTwinMakerClient: { + send: jest.fn(), + } as unknown as IoTTwinMakerClient, }} initialViewMode='preview' /> @@ -73,7 +82,9 @@ it('renders dashboard name', function () { clientConfiguration={{ iotEventsClient: createMockIoTEventsSDK(), iotSiteWiseClient: createMockSiteWiseSDK() as IoTSiteWiseClient, - iotTwinMakerClient: { send: jest.fn() } as unknown as IoTTwinMakerClient, + iotTwinMakerClient: { + send: jest.fn(), + } as unknown as IoTTwinMakerClient, }} /> ); @@ -96,7 +107,9 @@ it('renders without dashboard name', function () { clientConfiguration={{ iotEventsClient: createMockIoTEventsSDK(), iotSiteWiseClient: createMockSiteWiseSDK() as IoTSiteWiseClient, - iotTwinMakerClient: { send: jest.fn() } as unknown as IoTTwinMakerClient, + iotTwinMakerClient: { + send: jest.fn(), + } as unknown as IoTTwinMakerClient, }} /> ); diff --git a/packages/dashboard/src/components/dashboard/index.tsx b/packages/dashboard/src/components/dashboard/index.tsx index 7caff49ad..81faa7e7e 100644 --- a/packages/dashboard/src/components/dashboard/index.tsx +++ b/packages/dashboard/src/components/dashboard/index.tsx @@ -9,7 +9,11 @@ import InternalDashboard from '../internalDashboard'; import { configureDashboardStore, toDashboardState } from '~/store'; import { useDashboardPlugins } from '~/customization/api'; -import type { DashboardClientConfiguration, DashboardConfiguration, DashboardSave } from '~/types'; +import type { + DashboardClientConfiguration, + DashboardConfiguration, + DashboardSave, +} from '~/types'; import { ClientContext } from './clientContext'; import { QueryContext } from './queryContext'; import { getClients } from './getClients'; @@ -48,7 +52,12 @@ const Dashboard: React.FC = ({ - + = ({ enableKeyboardEvents: true, }} > - } /> + } + /> diff --git a/packages/dashboard/src/components/dashboard/queryContext.ts b/packages/dashboard/src/components/dashboard/queryContext.ts index 1d49e1476..1cfa1e7d8 100644 --- a/packages/dashboard/src/components/dashboard/queryContext.ts +++ b/packages/dashboard/src/components/dashboard/queryContext.ts @@ -1,17 +1,33 @@ import { createContext, useContext } from 'react'; import { assetModelQueryToSiteWiseAssetQuery } from '~/customization/widgets/utils/assetModelQueryToAssetQuery'; -import { DashboardIotSiteWiseQueries, IoTSiteWiseDataStreamQuery } from '~/types'; +import { + DashboardIotSiteWiseQueries, + IoTSiteWiseDataStreamQuery, +} from '~/types'; -export const QueryContext = createContext>({}); +export const QueryContext = createContext>( + {} +); -export const useQueries = ({ assets = [], properties = [], assetModels = [] }: IoTSiteWiseDataStreamQuery = {}) => { +export const useQueries = ({ + assets = [], + properties = [], + assetModels = [], +}: IoTSiteWiseDataStreamQuery = {}) => { const { iotSiteWiseQuery } = useContext(QueryContext); - if (iotSiteWiseQuery == null || (assets.length === 0 && properties.length === 0 && assetModels.length === 0)) { + if ( + iotSiteWiseQuery == null || + (assets.length === 0 && properties.length === 0 && assetModels.length === 0) + ) { return []; } - const mappedQuery = assetModelQueryToSiteWiseAssetQuery({ assetModels, assets, properties }); + const mappedQuery = assetModelQueryToSiteWiseAssetQuery({ + assetModels, + assets, + properties, + }); const queries = [iotSiteWiseQuery.timeSeriesData(mappedQuery)] ?? []; diff --git a/packages/dashboard/src/components/dashboard/view.test.tsx b/packages/dashboard/src/components/dashboard/view.test.tsx index bf6a588e3..10c3e2836 100644 --- a/packages/dashboard/src/components/dashboard/view.test.tsx +++ b/packages/dashboard/src/components/dashboard/view.test.tsx @@ -1,5 +1,8 @@ import { render } from '@testing-library/react'; -import { createMockIoTEventsSDK, createMockSiteWiseSDK } from '@iot-app-kit/testing-util'; +import { + createMockIoTEventsSDK, + createMockSiteWiseSDK, +} from '@iot-app-kit/testing-util'; import { type IoTTwinMakerClient } from '@aws-sdk/client-iottwinmaker'; import { type IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; @@ -19,8 +22,11 @@ it('renders', function () { }} clientConfiguration={{ iotEventsClient: createMockIoTEventsSDK(), - iotSiteWiseClient: createMockSiteWiseSDK() as unknown as IoTSiteWiseClient, - iotTwinMakerClient: { send: jest.fn() } as unknown as IoTTwinMakerClient, + iotSiteWiseClient: + createMockSiteWiseSDK() as unknown as IoTSiteWiseClient, + iotTwinMakerClient: { + send: jest.fn(), + } as unknown as IoTTwinMakerClient, }} /> ); diff --git a/packages/dashboard/src/components/dashboard/view.tsx b/packages/dashboard/src/components/dashboard/view.tsx index c3f72ca68..c10accf6a 100644 --- a/packages/dashboard/src/components/dashboard/view.tsx +++ b/packages/dashboard/src/components/dashboard/view.tsx @@ -8,7 +8,10 @@ import InternalDashboard from '../internalDashboard'; import { configureDashboardStore, toDashboardState } from '~/store'; import { useDashboardPlugins } from '~/customization/api'; -import type { DashboardClientConfiguration, DashboardConfiguration } from '~/types'; +import type { + DashboardClientConfiguration, + DashboardConfiguration, +} from '~/types'; import { ClientContext } from './clientContext'; import { QueryContext } from './queryContext'; import { getClients } from './getClients'; @@ -22,14 +25,22 @@ export type DashboardViewProperties = { dashboardConfiguration: DashboardConfiguration; }; -const DashboardView: React.FC = ({ clientConfiguration, dashboardConfiguration }) => { +const DashboardView: React.FC = ({ + clientConfiguration, + dashboardConfiguration, +}) => { // Adding Dnd provider because custom widgets may have a drag and drop context useDashboardPlugins(); return ( - + = ({ componentTag, messageOverrides }) => { +const DragLayerWidget: React.FC = ({ + componentTag, + messageOverrides, +}) => { const grid = useSelector((state: DashboardState) => state.grid); const widgetPreset = widgetCreator(grid)(componentTag); @@ -29,7 +34,10 @@ const DragLayerWidget: React.FC = ({ componentTag, message return (
    ); diff --git a/packages/dashboard/src/components/dragLayer/index.tsx b/packages/dashboard/src/components/dragLayer/index.tsx index 312752832..cd4870570 100644 --- a/packages/dashboard/src/components/dragLayer/index.tsx +++ b/packages/dashboard/src/components/dragLayer/index.tsx @@ -9,7 +9,10 @@ import DashboardWidget from './components/widget'; import './index.css'; import { DefaultDashboardMessages } from '~/messages'; -const getItemStyles = (initialOffset: XYCoord | null, currentOffset: XYCoord | null) => { +const getItemStyles = ( + initialOffset: XYCoord | null, + currentOffset: XYCoord | null +) => { if (!initialOffset || !currentOffset) { return { display: 'none', @@ -30,15 +33,16 @@ export type CustomDragLayerProps = { }; const CustomDragLayer: React.FC = ({ onDrag }) => { - const { itemType, isDragging, item, initialOffset, currentOffset } = useDragLayer((monitor) => { - return { - item: monitor.getItem(), - itemType: monitor.getItemType(), - initialOffset: monitor.getInitialSourceClientOffset(), - currentOffset: monitor.getSourceClientOffset(), - isDragging: monitor.isDragging(), - }; - }); + const { itemType, isDragging, item, initialOffset, currentOffset } = + useDragLayer((monitor) => { + return { + item: monitor.getItem(), + itemType: monitor.getItemType(), + initialOffset: monitor.getInitialSourceClientOffset(), + currentOffset: monitor.getSourceClientOffset(), + isDragging: monitor.isDragging(), + }; + }); useEffect(() => { onDrag(isDragging); @@ -47,7 +51,12 @@ const CustomDragLayer: React.FC = ({ onDrag }) => { const layer = () => { switch (itemType) { case ItemTypes.Component: - return ; + return ( + + ); default: return null; } diff --git a/packages/dashboard/src/components/grid/gestureable.tsx b/packages/dashboard/src/components/grid/gestureable.tsx index 2d683c44c..d95eaad85 100644 --- a/packages/dashboard/src/components/grid/gestureable.tsx +++ b/packages/dashboard/src/components/grid/gestureable.tsx @@ -34,18 +34,24 @@ export const GestureableGrid: React.FC = ({ }) => { const { width, height, cellSize, enabled } = grid; - const { dragRef, dropRef, isOver, onPointerDown, onPointerUp } = useGridDragAndDrop({ - readOnly, - enabled, - click, - drag, - dragStart, - dragEnd, - drop, - }); + const { dragRef, dropRef, isOver, onPointerDown, onPointerUp } = + useGridDragAndDrop({ + readOnly, + enabled, + click, + drag, + dragStart, + dragEnd, + drop, + }); return ( -
    +
    { drop: (item: ComponentPaletteDraggable, monitor) => { const initialClientOffset = monitor.getInitialClientOffset(); // where the cursor was in the viewport when the drag started; const clientOffset = monitor.getClientOffset(); // where cursor is in the viewport when drop occurs; - const gridRect = document.getElementById(DASHBOARD_CONTAINER_ID)?.getBoundingClientRect(); + const gridRect = document + .getElementById(DASHBOARD_CONTAINER_ID) + ?.getBoundingClientRect(); const itemRect = item.rect; - if (!initialClientOffset || !clientOffset || !gridRect || !itemRect) return; + if (!initialClientOffset || !clientOffset || !gridRect || !itemRect) + return; // find cursor position in the grid const gridOffset = { diff --git a/packages/dashboard/src/components/grid/gestures/useGridDragAndDrop.ts b/packages/dashboard/src/components/grid/gestures/useGridDragAndDrop.ts index d53912b6f..932b1f3a9 100644 --- a/packages/dashboard/src/components/grid/gestures/useGridDragAndDrop.ts +++ b/packages/dashboard/src/components/grid/gestures/useGridDragAndDrop.ts @@ -30,12 +30,13 @@ export const useGridDragAndDrop = ({ }: GridDragAndDropProps) => { const union = useKeyPress('shift'); - const { target, dashboardGrid, setCancelClick, onPointerDown, onPointerUp } = usePointerTracker({ - readOnly, - enabled, - union, - click, - }); + const { target, dashboardGrid, setCancelClick, onPointerDown, onPointerUp } = + usePointerTracker({ + readOnly, + enabled, + union, + click, + }); const { dragRef } = useDragMonitor({ readOnly, enabled: enabled && !union, diff --git a/packages/dashboard/src/components/grid/gestures/usePointerTracker.ts b/packages/dashboard/src/components/grid/gestures/usePointerTracker.ts index da0076306..913838d23 100644 --- a/packages/dashboard/src/components/grid/gestures/usePointerTracker.ts +++ b/packages/dashboard/src/components/grid/gestures/usePointerTracker.ts @@ -1,6 +1,9 @@ import { PointerEventHandler, useState } from 'react'; import { PointClickEvent } from './types'; -import { DASHBOARD_CONTAINER_ID, getDashboardPosition } from '../getDashboardPosition'; +import { + DASHBOARD_CONTAINER_ID, + getDashboardPosition, +} from '../getDashboardPosition'; import { endTracker, startTracker } from './positionTracker'; import { MouseClick } from '~/types'; @@ -21,7 +24,12 @@ export type PointerTrackerProps = { * Handles the point click gesture * */ -export const usePointerTracker = ({ readOnly, enabled, union, click }: PointerTrackerProps) => { +export const usePointerTracker = ({ + readOnly, + enabled, + union, + click, +}: PointerTrackerProps) => { const [dashboardGrid, setDashboardGrid] = useState(null); const [target, setTarget] = useState(); const [cancelClick, setCancelClick] = useState(false); @@ -30,7 +38,9 @@ export const usePointerTracker = ({ readOnly, enabled, union, click }: PointerTr if (readOnly) return; setTarget(e.target); setCancelClick(false); - const dashboardGrid = document.getElementById(DASHBOARD_CONTAINER_ID)?.getBoundingClientRect(); + const dashboardGrid = document + .getElementById(DASHBOARD_CONTAINER_ID) + ?.getBoundingClientRect(); if (dashboardGrid) { setDashboardGrid(dashboardGrid); } diff --git a/packages/dashboard/src/components/grid/getDashboardPosition.ts b/packages/dashboard/src/components/grid/getDashboardPosition.ts index d1f9ed209..9a9220960 100644 --- a/packages/dashboard/src/components/grid/getDashboardPosition.ts +++ b/packages/dashboard/src/components/grid/getDashboardPosition.ts @@ -4,9 +4,18 @@ import type { Position } from '~/types'; export const DASHBOARD_CONTAINER_ID = 'container'; const getOffsets = ( - event: React.MouseEvent | React.TouchEvent | React.PointerEvent | MouseEvent | TouchEvent | PointerEvent + event: + | React.MouseEvent + | React.TouchEvent + | React.PointerEvent + | MouseEvent + | TouchEvent + | PointerEvent ) => { - if ((window.TouchEvent && event instanceof TouchEvent) || (event as React.TouchEvent).touches) { + if ( + (window.TouchEvent && event instanceof TouchEvent) || + (event as React.TouchEvent).touches + ) { const ev = (event as TouchEvent).touches[0]; const rect = (ev.target as HTMLElement).getBoundingClientRect(); @@ -31,7 +40,13 @@ const getOffsets = ( }; export const getDashboardPosition = ( - event: React.MouseEvent | React.TouchEvent | React.PointerEvent | MouseEvent | TouchEvent | PointerEvent + event: + | React.MouseEvent + | React.TouchEvent + | React.PointerEvent + | MouseEvent + | TouchEvent + | PointerEvent ): Position => { const { offsetX, offsetY } = getOffsets(event); let totalOffsetX = offsetX; diff --git a/packages/dashboard/src/components/grid/grid.tsx b/packages/dashboard/src/components/grid/grid.tsx index ab7cbdb50..1ff2ef1dc 100644 --- a/packages/dashboard/src/components/grid/grid.tsx +++ b/packages/dashboard/src/components/grid/grid.tsx @@ -49,12 +49,21 @@ export type GridProps = PropsWithChildren<{ cellSize: number; showGuides: boolean; }>; -export const Grid: React.FC = ({ highlighted, showGuides, width, height, cellSize, children }) => ( +export const Grid: React.FC = ({ + highlighted, + showGuides, + width, + height, + cellSize, + children, +}) => (
    ; -export const ReadOnlyGrid: React.FC = ({ width, height, cellSize, children }) => ( +export const ReadOnlyGrid: React.FC = ({ + width, + height, + cellSize, + children, +}) => ( = ({ width, height, cellSize, showGuides, highlighted, children }) => { +export const SizedGrid: React.FC = ({ + width, + height, + cellSize, + showGuides, + highlighted, + children, +}) => { const gridComponent = ( { it('should open collapsed panel when icon is clicked', async () => { const side = 'right'; let isPanelCollapsed = true; - const onCollapsedPanelClick = jest.fn().mockImplementation(() => (isPanelCollapsed = !isPanelCollapsed)); + const onCollapsedPanelClick = jest + .fn() + .mockImplementation(() => (isPanelCollapsed = !isPanelCollapsed)); const collapsedElement = ( { it('should close expanded panel when button is clicked', async () => { const side = 'left'; let isPanelCollapsed = false; - const onCollapsedPanelClick = jest.fn().mockImplementation(() => (isPanelCollapsed = !isPanelCollapsed)); + const onCollapsedPanelClick = jest + .fn() + .mockImplementation(() => (isPanelCollapsed = !isPanelCollapsed)); const collapsedElement = ( ( -
    +
    ); const expandedPanel = (
    -
    +
    -
    +
    {props.panelContent}
    @@ -81,7 +90,11 @@ export function CollapsiblePanel(props: CollapsiblePanelProps) { className='side_panels_collapsed_style' onClick={props.onCollapsedPanelClick} > - {props.icon} + {props.icon}
    ); @@ -90,7 +103,9 @@ export function CollapsiblePanel(props: CollapsiblePanelProps) {
    { return ( -
    +
    - Line widget light icon + Line widget light icon
    Drag and drop your widget to the canvas.
    diff --git a/packages/dashboard/src/components/internalDashboard/dashboardHeader.tsx b/packages/dashboard/src/components/internalDashboard/dashboardHeader.tsx index 381f8ef50..e813e3144 100644 --- a/packages/dashboard/src/components/internalDashboard/dashboardHeader.tsx +++ b/packages/dashboard/src/components/internalDashboard/dashboardHeader.tsx @@ -3,7 +3,12 @@ import React, { memo } from 'react'; import { Box, Header, SpaceBetween } from '@cloudscape-design/components'; import { TimeSelection } from '@iot-app-kit/react-components'; -import { colorChartsLineGrid, spaceScaledXs, spaceScaledXxxl, spaceScaledXxxs } from '@cloudscape-design/design-tokens'; +import { + colorChartsLineGrid, + spaceScaledXs, + spaceScaledXxxl, + spaceScaledXxxs, +} from '@cloudscape-design/design-tokens'; import Actions from '../actions'; import type { DashboardSave, DashboardWidget } from '~/types'; diff --git a/packages/dashboard/src/components/internalDashboard/gestures/determineTargetGestures.ts b/packages/dashboard/src/components/internalDashboard/gestures/determineTargetGestures.ts index a0d21d6ad..01621cc5f 100644 --- a/packages/dashboard/src/components/internalDashboard/gestures/determineTargetGestures.ts +++ b/packages/dashboard/src/components/internalDashboard/gestures/determineTargetGestures.ts @@ -41,8 +41,13 @@ export const determineTargetGestures = ( let widgetId: string | null = null; let targetElement = target as HTMLElement; - while ((targetElement.getAttribute(GESTURE_ATTRIBUTE) as GestureAttribute) !== 'grid') { - const attribute = targetElement.getAttribute(GESTURE_ATTRIBUTE) as GestureAttribute; + while ( + (targetElement.getAttribute(GESTURE_ATTRIBUTE) as GestureAttribute) !== + 'grid' + ) { + const attribute = targetElement.getAttribute( + GESTURE_ATTRIBUTE + ) as GestureAttribute; if (attribute === 'resize') { isOnResizeHandle = true; diff --git a/packages/dashboard/src/components/internalDashboard/gestures/index.spec.tsx b/packages/dashboard/src/components/internalDashboard/gestures/index.spec.tsx index aaa0724a0..e29a81abc 100644 --- a/packages/dashboard/src/components/internalDashboard/gestures/index.spec.tsx +++ b/packages/dashboard/src/components/internalDashboard/gestures/index.spec.tsx @@ -14,7 +14,9 @@ import type { DashboardState } from '~/store/state'; const TestProvider: React.FC<{ storeArgs?: RecursivePartial; children: ReactNode; -}> = ({ storeArgs, children }) => {children}; +}> = ({ storeArgs, children }) => ( + {children} +); const widgetGestureAttribute = gestureable('widget'); const resizeGestureAttribute = gestureable('resize'); @@ -22,12 +24,24 @@ const anchorAttribute = anchorable('bottom'); const idAttribute = idable('widget-1'); const mockWidget = document.createElement('div'); -mockWidget.setAttribute(Object.keys(widgetGestureAttribute)[0], Object.values(widgetGestureAttribute)[0]); -mockWidget.setAttribute(Object.keys(idAttribute)[0], Object.values(idAttribute)[0]); +mockWidget.setAttribute( + Object.keys(widgetGestureAttribute)[0], + Object.values(widgetGestureAttribute)[0] +); +mockWidget.setAttribute( + Object.keys(idAttribute)[0], + Object.values(idAttribute)[0] +); const mockAnchor = document.createElement('div'); -mockAnchor.setAttribute(Object.keys(resizeGestureAttribute)[0], Object.values(resizeGestureAttribute)[0]); -mockAnchor.setAttribute(Object.keys(anchorAttribute)[0], Object.values(anchorAttribute)[0]); +mockAnchor.setAttribute( + Object.keys(resizeGestureAttribute)[0], + Object.values(resizeGestureAttribute)[0] +); +mockAnchor.setAttribute( + Object.keys(anchorAttribute)[0], + Object.values(anchorAttribute)[0] +); const mockGrid = document.createElement('div'); @@ -62,7 +76,18 @@ it('sets the active gesture to move when moving a selected widget', () => { () => useGestures({ dashboardWidgets: MockDashboardFactory.get().widgets, - selectedWidgets: [{ id: 'widget-1', x: 0, y: 0, z: 0, height: 1, width: 1, type: 'kpi', properties: {} }], + selectedWidgets: [ + { + id: 'widget-1', + x: 0, + y: 0, + z: 0, + height: 1, + width: 1, + type: 'kpi', + properties: {}, + }, + ], cellSize: 1, }), { wrapper: ({ children }) => } @@ -88,7 +113,18 @@ it('sets the active gesture to resize when moving an anchor', () => { () => useGestures({ dashboardWidgets: MockDashboardFactory.get().widgets, - selectedWidgets: [{ id: 'widget-1', x: 0, y: 0, z: 0, height: 1, width: 1, type: 'kpi', properties: {} }], + selectedWidgets: [ + { + id: 'widget-1', + x: 0, + y: 0, + z: 0, + height: 1, + width: 1, + type: 'kpi', + properties: {}, + }, + ], cellSize: 1, }), { wrapper: ({ children }) => } @@ -114,7 +150,18 @@ it('sets the active gesture to select when moving on the grid', () => { () => useGestures({ dashboardWidgets: MockDashboardFactory.get().widgets, - selectedWidgets: [{ id: 'widget-1', x: 0, y: 0, z: 0, height: 1, width: 1, type: 'kpi', properties: {} }], + selectedWidgets: [ + { + id: 'widget-1', + x: 0, + y: 0, + z: 0, + height: 1, + width: 1, + type: 'kpi', + properties: {}, + }, + ], cellSize: 1, }), { wrapper: ({ children }) => } @@ -140,7 +187,18 @@ it('resets the active gesture after the gesture ends', () => { () => useGestures({ dashboardWidgets: MockDashboardFactory.get().widgets, - selectedWidgets: [{ id: 'widget-1', x: 0, y: 0, z: 0, height: 1, width: 1, type: 'kpi', properties: {} }], + selectedWidgets: [ + { + id: 'widget-1', + x: 0, + y: 0, + z: 0, + height: 1, + width: 1, + type: 'kpi', + properties: {}, + }, + ], cellSize: 1, }), { wrapper: ({ children }) => } diff --git a/packages/dashboard/src/components/internalDashboard/gestures/index.ts b/packages/dashboard/src/components/internalDashboard/gestures/index.ts index 646ea82cf..8af036c03 100644 --- a/packages/dashboard/src/components/internalDashboard/gestures/index.ts +++ b/packages/dashboard/src/components/internalDashboard/gestures/index.ts @@ -14,10 +14,22 @@ type GestureHooksProps = { cellSize: DashboardState['grid']['cellSize']; }; -export const useGestures = ({ dashboardWidgets, selectedWidgets, cellSize }: GestureHooksProps) => { - const [activeGesture, setActiveGesture] = useState(undefined); +export const useGestures = ({ + dashboardWidgets, + selectedWidgets, + cellSize, +}: GestureHooksProps) => { + const [activeGesture, setActiveGesture] = useState( + undefined + ); - const { userSelection, onPointSelect, onSelectionStart, onSelectionUpdate, onSelectionEnd } = useSelectionGestures({ + const { + userSelection, + onPointSelect, + onSelectionStart, + onSelectionUpdate, + onSelectionEnd, + } = useSelectionGestures({ setActiveGesture, dashboardWidgets, cellSize: cellSize, @@ -41,10 +53,18 @@ export const useGestures = ({ dashboardWidgets, selectedWidgets, cellSize }: Ges * Note: isOnSelection refers to the cursor being in a location which is over the selection rect but not * over a selected widget. To understand if we are on a widget in the selection we must use the widget id */ - const { isOnResizeHandle, isOnSelection, isOnWidget, widgetId, isUnion, anchor } = - determineTargetGestures(dragEvent); + const { + isOnResizeHandle, + isOnSelection, + isOnWidget, + widgetId, + isUnion, + anchor, + } = determineTargetGestures(dragEvent); - const isOnWidgetInSelection = selectedWidgets.some((widget) => widget.id === widgetId); + const isOnWidgetInSelection = selectedWidgets.some( + (widget) => widget.id === widgetId + ); const isMoveGesture = !isUnion && (isOnWidget || isOnSelection); diff --git a/packages/dashboard/src/components/internalDashboard/gestures/useMove.spec.tsx b/packages/dashboard/src/components/internalDashboard/gestures/useMove.spec.tsx index 74560ea09..90a9b8be6 100644 --- a/packages/dashboard/src/components/internalDashboard/gestures/useMove.spec.tsx +++ b/packages/dashboard/src/components/internalDashboard/gestures/useMove.spec.tsx @@ -23,7 +23,9 @@ jest.mock('../../../store/actions', () => { const TestProvider: React.FC<{ storeArgs?: RecursivePartial; children: ReactNode; -}> = ({ storeArgs, children }) => {children}; +}> = ({ storeArgs, children }) => ( + {children} +); it('sets the gesture to move when performing a move gesture', () => { const setActiveGesture = jest.fn(); @@ -47,7 +49,10 @@ it('sets the gesture to move when performing a move gesture', () => { }); it('dispatches the move action on gesture move update and end', () => { - (onMoveWidgetsAction as jest.Mock).mockImplementation(() => ({ type: '', payload: {} })); + (onMoveWidgetsAction as jest.Mock).mockImplementation(() => ({ + type: '', + payload: {}, + })); const setActiveGesture = jest.fn(); diff --git a/packages/dashboard/src/components/internalDashboard/gestures/useMove.ts b/packages/dashboard/src/components/internalDashboard/gestures/useMove.ts index 0fa89ea4c..6d40a8727 100644 --- a/packages/dashboard/src/components/internalDashboard/gestures/useMove.ts +++ b/packages/dashboard/src/components/internalDashboard/gestures/useMove.ts @@ -13,7 +13,11 @@ type MoveHooksProps = { cellSize: DashboardState['grid']['cellSize']; }; -export const useMoveGestures = ({ setActiveGesture, selectedWidgets, cellSize }: MoveHooksProps) => { +export const useMoveGestures = ({ + setActiveGesture, + selectedWidgets, + cellSize, +}: MoveHooksProps) => { const dispatch = useDispatch(); const moveWidgets = useCallback( diff --git a/packages/dashboard/src/components/internalDashboard/gestures/useResize.spec.tsx b/packages/dashboard/src/components/internalDashboard/gestures/useResize.spec.tsx index 283519cb5..75aa672b1 100644 --- a/packages/dashboard/src/components/internalDashboard/gestures/useResize.spec.tsx +++ b/packages/dashboard/src/components/internalDashboard/gestures/useResize.spec.tsx @@ -27,7 +27,9 @@ beforeEach(() => { const TestProvider: React.FC<{ storeArgs?: RecursivePartial; children: ReactNode; -}> = ({ storeArgs, children }) => {children}; +}> = ({ storeArgs, children }) => ( + {children} +); it('sets the gesture to resize when performing a resize gesture', () => { const setActiveGesture = jest.fn(); @@ -51,7 +53,10 @@ it('sets the gesture to resize when performing a resize gesture', () => { }); it('dispatches the resize action on gesture resize update and end', () => { - (onResizeWidgetsAction as jest.Mock).mockImplementation(() => ({ type: '', payload: {} })); + (onResizeWidgetsAction as jest.Mock).mockImplementation(() => ({ + type: '', + payload: {}, + })); const setActiveGesture = jest.fn(); @@ -105,7 +110,10 @@ it('dispatches the resize action on gesture resize update and end', () => { }); it('doesnt dispatch the resize action on gesture resize update and end if there is no anchor', () => { - (onResizeWidgetsAction as jest.Mock).mockImplementation(() => ({ type: '', payload: {} })); + (onResizeWidgetsAction as jest.Mock).mockImplementation(() => ({ + type: '', + payload: {}, + })); const setActiveGesture = jest.fn(); diff --git a/packages/dashboard/src/components/internalDashboard/gestures/useResize.ts b/packages/dashboard/src/components/internalDashboard/gestures/useResize.ts index 83160ea44..60bfb51f4 100644 --- a/packages/dashboard/src/components/internalDashboard/gestures/useResize.ts +++ b/packages/dashboard/src/components/internalDashboard/gestures/useResize.ts @@ -14,7 +14,11 @@ type ResizeHooksProps = { cellSize: DashboardState['grid']['cellSize']; }; -export const useResizeGestures = ({ setActiveGesture, selectedWidgets, cellSize }: ResizeHooksProps) => { +export const useResizeGestures = ({ + setActiveGesture, + selectedWidgets, + cellSize, +}: ResizeHooksProps) => { const dispatch = useDispatch(); const resizeWidgets = useCallback( diff --git a/packages/dashboard/src/components/internalDashboard/gestures/useSelection.spec.tsx b/packages/dashboard/src/components/internalDashboard/gestures/useSelection.spec.tsx index 3e38a8f97..9b8badc0f 100644 --- a/packages/dashboard/src/components/internalDashboard/gestures/useSelection.spec.tsx +++ b/packages/dashboard/src/components/internalDashboard/gestures/useSelection.spec.tsx @@ -12,7 +12,9 @@ import type { DashboardState } from '~/store/state'; const TestProvider: React.FC<{ storeArgs?: RecursivePartial; children: ReactNode; -}> = ({ storeArgs, children }) => {children}; +}> = ({ storeArgs, children }) => ( + {children} +); it('returns user selection when performing a selection gesture', () => { const setActiveGesture = jest.fn(); diff --git a/packages/dashboard/src/components/internalDashboard/gestures/useSelection.ts b/packages/dashboard/src/components/internalDashboard/gestures/useSelection.ts index c76895141..316eb6fff 100644 --- a/packages/dashboard/src/components/internalDashboard/gestures/useSelection.ts +++ b/packages/dashboard/src/components/internalDashboard/gestures/useSelection.ts @@ -13,7 +13,11 @@ type SelectionHooksProps = { cellSize: DashboardState['grid']['cellSize']; }; -export const useSelectionGestures = ({ setActiveGesture, dashboardWidgets, cellSize }: SelectionHooksProps) => { +export const useSelectionGestures = ({ + setActiveGesture, + dashboardWidgets, + cellSize, +}: SelectionHooksProps) => { const dispatch = useDispatch(); const selectWidgets = useCallback( (widgets: DashboardWidget[], union: boolean) => { @@ -27,7 +31,9 @@ export const useSelectionGestures = ({ setActiveGesture, dashboardWidgets, cellS [dispatch] ); - const [userSelection, setUserSelection] = useState(undefined); + const [userSelection, setUserSelection] = useState( + undefined + ); const onPointSelect = useCallback( ({ position, union }: { position: Position; union: boolean }) => { diff --git a/packages/dashboard/src/components/internalDashboard/index.test.tsx b/packages/dashboard/src/components/internalDashboard/index.test.tsx index 96f0e8f96..fe90c3206 100644 --- a/packages/dashboard/src/components/internalDashboard/index.test.tsx +++ b/packages/dashboard/src/components/internalDashboard/index.test.tsx @@ -161,7 +161,9 @@ it.skip('empty state within the dashboard when no widget is selected', function }; render( - + = ({ onSave, editable, name, propertiesPanel }) => { +const InternalDashboard: React.FC = ({ + onSave, + editable, + name, + propertiesPanel, +}) => { const { iotSiteWiseClient, iotTwinMakerClient } = useClients(); /** * disable user select styles on drag to prevent highlighting of text under the pointer */ - const [userSelect, setUserSelect] = useState(defaultUserSelect); + const [userSelect, setUserSelect] = + useState(defaultUserSelect); /** * Store variables */ - const dashboardConfiguration = useSelector((state: DashboardState) => state.dashboardConfiguration); - const dashboardWidgets = useSelector((state: DashboardState) => state.dashboardConfiguration.widgets); + const dashboardConfiguration = useSelector( + (state: DashboardState) => state.dashboardConfiguration + ); + const dashboardWidgets = useSelector( + (state: DashboardState) => state.dashboardConfiguration.widgets + ); const grid = useSelector((state: DashboardState) => state.grid); const cellSize = useSelector((state: DashboardState) => state.grid.cellSize); - const copiedWidgets = useSelector((state: DashboardState) => state.copiedWidgets); + const copiedWidgets = useSelector( + (state: DashboardState) => state.copiedWidgets + ); const readOnly = useSelector((state: DashboardState) => state.readOnly); const selectedWidgets = useSelectedWidgets(); - const significantDigits = useSelector((state: DashboardState) => state.significantDigits); + const significantDigits = useSelector( + (state: DashboardState) => state.significantDigits + ); const { assetModelId, hasModelBasedQuery } = useModelBasedQuery(); const hasValidAssetModelData = !!(hasModelBasedQuery && assetModelId); - const [viewFrame, setViewFrameElement] = useState(undefined); + const [viewFrame, setViewFrameElement] = useState( + undefined + ); const [visible, setVisible] = useState(false); const dispatch = useDispatch(); @@ -154,7 +174,14 @@ const InternalDashboard: React.FC = ({ onSave, edit /** * setup gesture handling for grid */ - const { activeGesture, userSelection, onPointClick, onGestureStart, onGestureUpdate, onGestureEnd } = useGestures({ + const { + activeGesture, + userSelection, + onPointClick, + onGestureStart, + onGestureUpdate, + onGestureEnd, + } = useGestures({ dashboardWidgets, selectedWidgets, cellSize, @@ -248,14 +275,23 @@ const InternalDashboard: React.FC = ({ onSave, edit } >
    - setUserSelect(isDragging ? disabledUserSelect : defaultUserSelect)} /> + + setUserSelect(isDragging ? disabledUserSelect : defaultUserSelect) + } + />
    } + leftPane={ + + } centerPane={
    = ({ onSave, edit {!widgetLength && } - {activeGesture === 'select' && } + {activeGesture === 'select' && ( + + )}
    @@ -293,7 +331,10 @@ const InternalDashboard: React.FC = ({ onSave, edit >
    {hasValidAssetModelData && ( -
    +
    @@ -319,7 +360,9 @@ const InternalDashboard: React.FC = ({ onSave, edit {readOnly ? ReadOnlyComponent : EditComponent} 1 ? 's' : ''}?`} + headerTitle={`Delete selected widget${ + selectedWidgets.length > 1 ? 's' : '' + }?`} cancelTitle='Cancel' submitTitle='Delete' description={ diff --git a/packages/dashboard/src/components/internalDashboard/keyboardShortcuts.test.tsx b/packages/dashboard/src/components/internalDashboard/keyboardShortcuts.test.tsx index b13831709..a7fb17e5d 100644 --- a/packages/dashboard/src/components/internalDashboard/keyboardShortcuts.test.tsx +++ b/packages/dashboard/src/components/internalDashboard/keyboardShortcuts.test.tsx @@ -9,7 +9,11 @@ import { act } from '@testing-library/react'; import InternalDashboard from './index'; import { configureDashboardStore } from '../../store'; -import { onBringWidgetsToFrontAction, onSelectWidgetsAction, onSendWidgetsToBackAction } from '../../store/actions'; +import { + onBringWidgetsToFrontAction, + onSelectWidgetsAction, + onSendWidgetsToBackAction, +} from '../../store/actions'; jest.mock('../../store/actions', () => { const originalModule = jest.requireActual('../../store/actions'); @@ -30,7 +34,13 @@ beforeEach(() => { jest.clearAllMocks(); }); -const renderDashboardAndPressKey = ({ key, meta }: { key: string; meta: boolean }) => { +const renderDashboardAndPressKey = ({ + key, + meta, +}: { + key: string; + meta: boolean; +}) => { const container = document.createElement('div'); document.body.appendChild(container); @@ -62,15 +72,30 @@ const renderDashboardAndPressKey = ({ key, meta }: { key: string; meta: boolean act(() => { if (meta) document.body.dispatchEvent( - new KeyboardEvent('keydown', { key: 'Control', metaKey: meta, ctrlKey: meta, bubbles: true }) + new KeyboardEvent('keydown', { + key: 'Control', + metaKey: meta, + ctrlKey: meta, + bubbles: true, + }) ); - document.body.dispatchEvent(new KeyboardEvent('keydown', { key, metaKey: meta, ctrlKey: meta, bubbles: true })); + document.body.dispatchEvent( + new KeyboardEvent('keydown', { + key, + metaKey: meta, + ctrlKey: meta, + bubbles: true, + }) + ); }); }; // TODO: fix these tests (likely need to mock TwinMaker client) it.skip('can clear the selection', () => { - (onSelectWidgetsAction as jest.Mock).mockImplementation(() => ({ type: '', payload: {} })); + (onSelectWidgetsAction as jest.Mock).mockImplementation(() => ({ + type: '', + payload: {}, + })); renderDashboardAndPressKey({ key: 'Escape', meta: false }); @@ -81,7 +106,10 @@ it.skip('can clear the selection', () => { }); it.skip('can send the selection to the back', () => { - (onSendWidgetsToBackAction as jest.Mock).mockImplementation(() => ({ type: '', payload: {} })); + (onSendWidgetsToBackAction as jest.Mock).mockImplementation(() => ({ + type: '', + payload: {}, + })); renderDashboardAndPressKey({ key: '[', meta: false }); @@ -90,7 +118,10 @@ it.skip('can send the selection to the back', () => { // TODO: fix these tests (likely need to mock TwinMaker client) it.skip('can bring the selection to the front', () => { - (onBringWidgetsToFrontAction as jest.Mock).mockImplementation(() => ({ type: '', payload: {} })); + (onBringWidgetsToFrontAction as jest.Mock).mockImplementation(() => ({ + type: '', + payload: {}, + })); renderDashboardAndPressKey({ key: ']', meta: false }); diff --git a/packages/dashboard/src/components/internalDashboard/keyboardShortcuts.ts b/packages/dashboard/src/components/internalDashboard/keyboardShortcuts.ts index c4a7ff18f..bab40f580 100644 --- a/packages/dashboard/src/components/internalDashboard/keyboardShortcuts.ts +++ b/packages/dashboard/src/components/internalDashboard/keyboardShortcuts.ts @@ -17,7 +17,9 @@ type useKeyboardShortcutsProps = { deleteWidgets?: () => void; }; -export const useKeyboardShortcuts = ({ deleteWidgets: handleDeleteWidgetModal }: useKeyboardShortcutsProps) => { +export const useKeyboardShortcuts = ({ + deleteWidgets: handleDeleteWidgetModal, +}: useKeyboardShortcutsProps) => { const dispatch = useDispatch(); const selectedWidgets = useSelectedWidgets(); @@ -75,7 +77,10 @@ export const useKeyboardShortcuts = ({ deleteWidgets: handleDeleteWidgetModal }: (e.target.id === DASHBOARD_CONTAINER_ID || e.target === document.body); useKeyPress('esc', { filter: keyPressFilter, callback: onClearSelection }); - useKeyPress('backspace, del', { filter: keyPressFilter, callback: deleteWidgets }); + useKeyPress('backspace, del', { + filter: keyPressFilter, + callback: deleteWidgets, + }); useKeyPress('mod+c', { filter: keyPressFilter, callback: copyWidgets }); useKeyPress('mod+v', { filter: keyPressFilter, callback: pasteWidgets }); useKeyPress('[', { filter: keyPressFilter, callback: sendWidgetsToBack }); diff --git a/packages/dashboard/src/components/internalDashboard/useLayers.spec.tsx b/packages/dashboard/src/components/internalDashboard/useLayers.spec.tsx index 285534745..f04ade2e5 100644 --- a/packages/dashboard/src/components/internalDashboard/useLayers.spec.tsx +++ b/packages/dashboard/src/components/internalDashboard/useLayers.spec.tsx @@ -4,7 +4,10 @@ import { Provider } from 'react-redux'; import { configureDashboardStore } from '~/store'; import { useLayers } from './useLayers'; -import { MockDashboardFactory, MockWidgetFactory } from '../../../testing/mocks'; +import { + MockDashboardFactory, + MockWidgetFactory, +} from '../../../testing/mocks'; import type { ReactNode } from 'react'; import type { RecursivePartial } from '~/types'; import type { DashboardState } from '~/store/state'; @@ -12,18 +15,26 @@ import type { DashboardState } from '~/store/state'; const TestProvider: React.FC<{ storeArgs?: RecursivePartial; children: ReactNode; -}> = ({ storeArgs, children }) => {children}; +}> = ({ storeArgs, children }) => ( + {children} +); it('has default layers', () => { - const { result } = renderHook(() => useLayers(), { wrapper: ({ children }) => }); + const { result } = renderHook(() => useLayers(), { + wrapper: ({ children }) => , + }); expect(result.current.selectionBoxLayer).toBeGreaterThan(0); expect(result.current.userSelectionLayer).toBeGreaterThan(0); expect(result.current.contextMenuLayer).toBeGreaterThan(0); expect(result.current.selectionGestureLayer).toBeLessThan(0); - expect(result.current.userSelectionLayer).toBeGreaterThan(result.current.selectionBoxLayer); - expect(result.current.contextMenuLayer).toBeGreaterThan(result.current.userSelectionLayer); + expect(result.current.userSelectionLayer).toBeGreaterThan( + result.current.selectionBoxLayer + ); + expect(result.current.contextMenuLayer).toBeGreaterThan( + result.current.userSelectionLayer + ); }); it('has updates the layers to be greater than the highest widget in the dashboard configuration', () => { @@ -37,7 +48,12 @@ it('has updates the layers to be greater than the highest widget in the dashboar ], }); const { result } = renderHook(() => useLayers(), { - wrapper: ({ children }) => , + wrapper: ({ children }) => ( + + ), }); expect(result.current.selectionBoxLayer).toBeGreaterThan(zTest); @@ -45,6 +61,10 @@ it('has updates the layers to be greater than the highest widget in the dashboar expect(result.current.contextMenuLayer).toBeGreaterThan(zTest); expect(result.current.selectionGestureLayer).toBeLessThan(zTest); - expect(result.current.userSelectionLayer).toBeGreaterThan(result.current.selectionBoxLayer); - expect(result.current.contextMenuLayer).toBeGreaterThan(result.current.userSelectionLayer); + expect(result.current.userSelectionLayer).toBeGreaterThan( + result.current.selectionBoxLayer + ); + expect(result.current.contextMenuLayer).toBeGreaterThan( + result.current.userSelectionLayer + ); }); diff --git a/packages/dashboard/src/components/internalDashboard/useLayers.ts b/packages/dashboard/src/components/internalDashboard/useLayers.ts index 49a2f66e5..19d754bb7 100644 --- a/packages/dashboard/src/components/internalDashboard/useLayers.ts +++ b/packages/dashboard/src/components/internalDashboard/useLayers.ts @@ -17,7 +17,9 @@ const layers: Layers = { }; export const useLayers = (): Layers => { - const widgets = useSelector((state: DashboardState) => state.dashboardConfiguration.widgets); + const widgets = useSelector( + (state: DashboardState) => state.dashboardConfiguration.widgets + ); const top = max(widgets.map(({ z }) => z)) ?? 0; const bottom = min(widgets.map(({ z }) => z)) ?? 0; diff --git a/packages/dashboard/src/components/palette/component.tsx b/packages/dashboard/src/components/palette/component.tsx index 9f8cb51a6..a7e1a62e7 100644 --- a/packages/dashboard/src/components/palette/component.tsx +++ b/packages/dashboard/src/components/palette/component.tsx @@ -12,7 +12,11 @@ type PaletteComponentProps = { IconComponent: React.FC; }; -const PaletteComponent: React.FC = ({ componentTag, name, IconComponent }) => { +const PaletteComponent: React.FC = ({ + componentTag, + name, + IconComponent, +}) => { const node = useRef(null); const [_, dragRef] = useDrag( @@ -21,7 +25,9 @@ const PaletteComponent: React.FC = ({ componentTag, name, item: (): ComponentPaletteDraggable => { return { componentTag, - rect: node.current ? (node.current as Element).getBoundingClientRect() : null, // Used to determine the cursor offset from the upper left corner on drop + rect: node.current + ? (node.current as Element).getBoundingClientRect() + : null, // Used to determine the cursor offset from the upper left corner on drop }; }, }), diff --git a/packages/dashboard/src/components/palette/icons/index.tsx b/packages/dashboard/src/components/palette/icons/index.tsx index 9a277afe2..1905ea059 100644 --- a/packages/dashboard/src/components/palette/icons/index.tsx +++ b/packages/dashboard/src/components/palette/icons/index.tsx @@ -15,7 +15,10 @@ type PaletteComponentIconProps = { Icon: React.FC; widgetName: string; }; -const PaletteComponentIcon: React.FC = ({ Icon, widgetName }) => { +const PaletteComponentIcon: React.FC = ({ + Icon, + widgetName, +}) => { const tooltipStyle = { fontSize: spaceScaledM, color: colorBackgroundHomeHeader, diff --git a/packages/dashboard/src/components/palette/index.test.tsx b/packages/dashboard/src/components/palette/index.test.tsx index cb8642c44..cfc115baf 100644 --- a/packages/dashboard/src/components/palette/index.test.tsx +++ b/packages/dashboard/src/components/palette/index.test.tsx @@ -35,7 +35,9 @@ const renderDashboard = (state?: RecursivePartial) => { const store = configureDashboardStore(state); const renderResults = render( - + {
      {ComponentLibraryComponentOrdering.map((widgetType) => { - const [name, iconComponent] = ComponentLibraryComponentMap[widgetType]; + const [name, iconComponent] = + ComponentLibraryComponentMap[widgetType]; return ( - + ); })}
    diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelDataStreamExplorer.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelDataStreamExplorer.tsx index db6c77f3d..b571c204d 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelDataStreamExplorer.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelDataStreamExplorer.tsx @@ -6,9 +6,15 @@ import Box from '@cloudscape-design/components/box'; import { AssetModelExplorer } from './assetModelExplorer/assetModelExplorer'; import { AssetModelPropertiesExplorer } from './assetModelPropertiesExplorer/assetModelPropertiesExplorer'; -import { createInitialAssetModelSummary, useSelectedAssetModel } from './useSelectedAssetModel'; +import { + createInitialAssetModelSummary, + useSelectedAssetModel, +} from './useSelectedAssetModel'; import { HorizontalDivider } from '~/components/divider/horizontalDivider'; -import { createInitialAssetModelProperties, useSelectedAssetModelProperties } from './useSelectedAssetModelProperties'; +import { + createInitialAssetModelProperties, + useSelectedAssetModelProperties, +} from './useSelectedAssetModelProperties'; import { createInitialAsset, useSelectedAsset } from './useSelectedAsset'; import { createAssetModelQuery } from './createAssetModelQuery'; import { createNonNullableList } from '~/helpers/lists/createNonNullableList'; @@ -21,18 +27,31 @@ export interface AssetModelDataStreamExplorerProps { client: IoTSiteWiseClient; } -export const AssetModelDataStreamExplorer = ({ client }: AssetModelDataStreamExplorerProps) => { +export const AssetModelDataStreamExplorer = ({ + client, +}: AssetModelDataStreamExplorerProps) => { const metricsRecorder = getPlugin('metricsRecorder'); - const { assetModelId, assetIds, clearModelBasedWidgets, updateSelectedAsset } = useModelBasedQuery(); - const { assetModels, updateAssetModels, modelBasedWidgetsSelected } = useModelBasedQuerySelection(); + const { + assetModelId, + assetIds, + clearModelBasedWidgets, + updateSelectedAsset, + } = useModelBasedQuery(); + const { assetModels, updateAssetModels, modelBasedWidgetsSelected } = + useModelBasedQuerySelection(); const { propertyIds } = getAssetModelQueryInformation(assetModels); - const [selectedAssetModel, selectAssetModel] = useSelectedAssetModel(createInitialAssetModelSummary(assetModelId)); - const [selectedAsset, selectAsset] = useSelectedAsset(createInitialAsset(assetIds?.at(0))); - const [selectedAssetModelProperties, selectAssetModelProperties] = useSelectedAssetModelProperties( - createInitialAssetModelProperties(propertyIds) + const [selectedAssetModel, selectAssetModel] = useSelectedAssetModel( + createInitialAssetModelSummary(assetModelId) ); + const [selectedAsset, selectAsset] = useSelectedAsset( + createInitialAsset(assetIds?.at(0)) + ); + const [selectedAssetModelProperties, selectAssetModelProperties] = + useSelectedAssetModelProperties( + createInitialAssetModelProperties(propertyIds) + ); /** * update every model based query widget @@ -55,7 +74,9 @@ export const AssetModelDataStreamExplorer = ({ client }: AssetModelDataStreamExp createAssetModelQuery({ assetModelId: selectedAssetModel.id, assetId: selectedAsset?.id, - assetModelPropertyIds: createNonNullableList(selectedAssetModelProperties.map(({ id }) => id)), + assetModelPropertyIds: createNonNullableList( + selectedAssetModelProperties.map(({ id }) => id) + ), }) ); metricsRecorder?.record({ @@ -86,7 +107,9 @@ export const AssetModelDataStreamExplorer = ({ client }: AssetModelDataStreamExp client={client} onSave={onSave} saveDisabled={!modelBasedWidgetsSelected} - onSelect={(assetModelProperties) => selectAssetModelProperties(assetModelProperties)} + onSelect={(assetModelProperties) => + selectAssetModelProperties(assetModelProperties) + } /> )} diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelExplorer.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelExplorer.tsx index 572d80bbe..5b5046f97 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelExplorer.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelExplorer.tsx @@ -3,7 +3,10 @@ import React from 'react'; import { AssetModelSelection } from './assetModelSelection/assetModelSelection'; import { AssetModelSelected } from './assetModelSelection/assetModelSelected'; import { IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; -import { SelectedAssetModel, UpdateSelectedAssetModel } from '../useSelectedAssetModel'; +import { + SelectedAssetModel, + UpdateSelectedAssetModel, +} from '../useSelectedAssetModel'; import { SelectedAsset, UpdateSelectedAsset } from '../useSelectedAsset'; type AssetModelExplorerOptions = { diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelSelection/assetModelSelect.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelSelection/assetModelSelect.tsx index 79c2a0937..972ae6462 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelSelection/assetModelSelect.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelSelection/assetModelSelect.tsx @@ -2,11 +2,17 @@ import React from 'react'; import FormField from '@cloudscape-design/components/form-field'; import Select, { SelectProps } from '@cloudscape-design/components/select'; -import { AssetModelSummary, IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; +import { + AssetModelSummary, + IoTSiteWiseClient, +} from '@aws-sdk/client-iotsitewise'; import { useAssetModels } from '../useAssetModels/useAssetModels'; import { NonCancelableEventHandler } from '@cloudscape-design/components/internal/events'; import { OptionsLoadItemsDetail } from '@cloudscape-design/components/internal/components/dropdown/interfaces'; -import { SelectedAssetModel, UpdateSelectedAssetModel } from '../../useSelectedAssetModel'; +import { + SelectedAssetModel, + UpdateSelectedAssetModel, +} from '../../useSelectedAssetModel'; type AssetModelSelectOptions = { selectedAssetModel?: SelectedAssetModel; @@ -37,7 +43,11 @@ const mapAssetModelToOption = (assetModel: AssetModelSummary) => ({ value: assetModel?.id, }); -export const AssetModelSelect = ({ client, selectedAssetModel, onSelectAssetModel }: AssetModelSelectOptions) => { +export const AssetModelSelect = ({ + client, + selectedAssetModel, + onSelectAssetModel, +}: AssetModelSelectOptions) => { const { assetModelSummaries, // status, @@ -50,7 +60,9 @@ export const AssetModelSelect = ({ client, selectedAssetModel, onSelectAssetMode refetch, } = useAssetModels({ client }); - const selectedAssetModelOption = selectedAssetModel ? mapAssetModelToOption(selectedAssetModel) : null; + const selectedAssetModelOption = selectedAssetModel + ? mapAssetModelToOption(selectedAssetModel) + : null; const assetModelOptions = assetModelSummaries.map(mapAssetModelToOption); @@ -64,7 +76,9 @@ export const AssetModelSelect = ({ client, selectedAssetModel, onSelectAssetMode }; const onChange: NonCancelableEventHandler = (e) => { - const selected = assetModelSummaries.find((ams) => ams?.id === e.detail.selectedOption.value); + const selected = assetModelSummaries.find( + (ams) => ams?.id === e.detail.selectedOption.value + ); if (!selected) return; onSelectAssetModel(selected); }; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelSelection/assetModelSelected.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelSelection/assetModelSelected.tsx index 7481a6357..1b85df09b 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelSelection/assetModelSelected.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelSelection/assetModelSelected.tsx @@ -7,7 +7,11 @@ import { spaceScaledXxs } from '@cloudscape-design/design-tokens'; import { VerticalDivider } from '~/components/divider/verticalDivider'; import { SelectedAssetModel } from '../../useSelectedAssetModel'; -import { AssetSummary, DescribeAssetCommandOutput, IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; +import { + AssetSummary, + DescribeAssetCommandOutput, + IoTSiteWiseClient, +} from '@aws-sdk/client-iotsitewise'; // import { AssetForAssetModelSelect } from '../../assetsForAssetModelSelect/assetForAssetModelSelect'; import { AssetForAssetModelSelectForm } from '../../assetsForAssetModelSelect/assetForAssetModelSelectForm'; import { SelectedAsset, UpdateSelectedAsset } from '../../useSelectedAsset'; @@ -54,7 +58,10 @@ export const AssetModelSelected = ({ }: AssetModelSelectedOptions) => { const { visible, onHide, onShow } = useModalVisibility(); - const { assetModel } = useAssetModel({ assetModelId: selectedAssetModel?.id, client }); + const { assetModel } = useAssetModel({ + assetModelId: selectedAssetModel?.id, + client, + }); const { asset } = useAsset({ assetId: selectedAsset?.id, client }); @@ -67,7 +74,9 @@ export const AssetModelSelected = ({ }} > Selected asset model icon - + Asset model: @@ -84,7 +93,11 @@ export const AssetModelSelected = ({ client={client} /> - +
    ); }; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelSelection/assetModelSelection.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelSelection/assetModelSelection.tsx index cc903f0c2..133f06b03 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelSelection/assetModelSelection.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelSelection/assetModelSelection.tsx @@ -8,8 +8,16 @@ import { AssetModelSelect } from './assetModelSelect'; import { HorizontalDivider } from '~/components/divider/horizontalDivider'; import { AssetModelSave } from './assetModelSave'; import { IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; -import { SelectedAssetModel, UpdateSelectedAssetModel, useSelectedAssetModel } from '../../useSelectedAssetModel'; -import { SelectedAsset, UpdateSelectedAsset, useSelectedAsset } from '../../useSelectedAsset'; +import { + SelectedAssetModel, + UpdateSelectedAssetModel, + useSelectedAssetModel, +} from '../../useSelectedAssetModel'; +import { + SelectedAsset, + UpdateSelectedAsset, + useSelectedAsset, +} from '../../useSelectedAsset'; import { AssetForAssetModelSelectForm } from '../../assetsForAssetModelSelect/assetForAssetModelSelectForm'; type AssetModelSelectionOptions = { @@ -27,8 +35,10 @@ export const AssetModelSelection = ({ setSelectedAsset, client, }: AssetModelSelectionOptions) => { - const [currentSelectedAssetModel, selectCurrentAssetModel] = useSelectedAssetModel(selectedAssetModel); - const [currentSelectedAsset, selectCurrentAsset] = useSelectedAsset(selectedAsset); + const [currentSelectedAssetModel, selectCurrentAssetModel] = + useSelectedAssetModel(selectedAssetModel); + const [currentSelectedAsset, selectCurrentAsset] = + useSelectedAsset(selectedAsset); const onSave = () => { onSelectAssetModel(currentSelectedAssetModel); @@ -38,9 +48,12 @@ export const AssetModelSelection = ({ return ( - Dynamic asset visualizations allow you to build one visualization to represent any asset of a specified asset - model.{' '} - + Dynamic asset visualizations allow you to build one visualization to + represent any asset of a specified asset model.{' '} + Learn more diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelSelection/resetAssetModel/resetAssetModelModal.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelSelection/resetAssetModel/resetAssetModelModal.tsx index 92e7b80a1..c36968912 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelSelection/resetAssetModel/resetAssetModelModal.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/assetModelSelection/resetAssetModel/resetAssetModelModal.tsx @@ -38,7 +38,8 @@ export const ResetAssetModelModal = ({ type='warning' header='Removing this asset model will remove associated parameters from widgets on this dashboard.' > - This action cannot be undone. Once reset, you may associate this dashboard with a different asset model. + This action cannot be undone. Once reset, you may associate this + dashboard with a different asset model. ); diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/useAssetModels/getAssetModelRequest.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/useAssetModels/getAssetModelRequest.ts index f55bd25d2..cbb42b9aa 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/useAssetModels/getAssetModelRequest.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/useAssetModels/getAssetModelRequest.ts @@ -1,11 +1,22 @@ -import { ListAssetModelsCommand, type IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; +import { + ListAssetModelsCommand, + type IoTSiteWiseClient, +} from '@aws-sdk/client-iotsitewise'; export class GetAssetModelsRequest { readonly #command: ListAssetModelsCommand; readonly #client: IoTSiteWiseClient; readonly #signal: AbortSignal | undefined; - constructor({ nextToken, client, signal }: { nextToken?: string; client: IoTSiteWiseClient; signal?: AbortSignal }) { + constructor({ + nextToken, + client, + signal, + }: { + nextToken?: string; + client: IoTSiteWiseClient; + signal?: AbortSignal; + }) { this.#command = this.#createCommand(nextToken); this.#client = client; this.#signal = signal; @@ -13,7 +24,9 @@ export class GetAssetModelsRequest { public async send() { try { - const response = await this.#client.send(this.#command, { abortSignal: this.#signal }); + const response = await this.#client.send(this.#command, { + abortSignal: this.#signal, + }); return response; } catch (error) { diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/useAssetModels/useAssetModels.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/useAssetModels/useAssetModels.ts index c354a2a1b..860776baf 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/useAssetModels/useAssetModels.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelExplorer/useAssetModels/useAssetModels.ts @@ -32,7 +32,9 @@ export function useAssetModels({ client, fetchAll }: UseAssetModelsOptions) { if (fetchAll && hasNextPage) fetchNextPage(); - const assetModelSummaries = createNonNullableList(assetModelResponses.flatMap((res) => res.assetModelSummaries)); + const assetModelSummaries = createNonNullableList( + assetModelResponses.flatMap((res) => res.assetModelSummaries) + ); return { assetModelSummaries, @@ -52,7 +54,9 @@ export const createQueryFn = (client: IoTSiteWiseClient) => { return async ({ pageParam: nextToken, signal, - }: QueryFunctionContext>) => { + }: QueryFunctionContext< + ReturnType + >) => { const request = new GetAssetModelsRequest({ nextToken, client, signal }); const response = await request.send(); diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesExplorer.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesExplorer.tsx index b1ddff1c7..c7c1e3c5d 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesExplorer.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesExplorer.tsx @@ -1,6 +1,9 @@ import React from 'react'; -import { AssetModelPropertySummary, IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; +import { + AssetModelPropertySummary, + IoTSiteWiseClient, +} from '@aws-sdk/client-iotsitewise'; import { useAssetModelProperties } from './useAssetModelProperties/useAssetModelProperties'; import { SelectedAssetModel } from '../useSelectedAssetModel'; import { AssetModelPropertiesTable } from './assetModelPropertiesTable/assetModelPropertiesTable'; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTable.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTable.tsx index ab0b1561b..dbb3628ad 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTable.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTable.tsx @@ -19,7 +19,9 @@ import { ResourceExplorerFooter } from '../../../footer/footer'; export interface AssetTableProps { onClickNextPage: () => void; - onSelectAssetModelProperties: (assetModelProperty: AssetModelPropertySummary[]) => void; + onSelectAssetModelProperties: ( + assetModelProperty: AssetModelPropertySummary[] + ) => void; assetModelProperties: AssetModelPropertySummary[]; selectedAssetModelProperties: SelectedAssetModelProperties; isLoading: boolean; @@ -48,21 +50,24 @@ export function AssetModelPropertiesTable({ resourceName: 'asset model properties', }); - const { items, collectionProps, paginationProps, propertyFilterProps, actions } = useCollection( - assetModelProperties, - { - propertyFiltering: { - filteringProperties: ASSET_MODEL_PROPERTIES_TABLE_FILTERING_PROPERTIES, - }, - pagination: { pageSize: preferences.pageSize }, - selection: { - defaultSelectedItems: selectedAssetModelProperties, - trackBy: ({ id = '' }) => id, - keepSelection: true, - }, - sorting: {}, - } - ); + const { + items, + collectionProps, + paginationProps, + propertyFilterProps, + actions, + } = useCollection(assetModelProperties, { + propertyFiltering: { + filteringProperties: ASSET_MODEL_PROPERTIES_TABLE_FILTERING_PROPERTIES, + }, + pagination: { pageSize: preferences.pageSize }, + selection: { + defaultSelectedItems: selectedAssetModelProperties, + trackBy: ({ id = '' }) => id, + keepSelection: true, + }, + sorting: {}, + }); function handleClickNextPage() { onClickNextPage(); @@ -76,7 +81,8 @@ export function AssetModelPropertiesTable({ isEqual ); - const columnDefinitionFactory = new AssetModelPropertiesTableColumnDefinitionsFactory(); + const columnDefinitionFactory = + new AssetModelPropertiesTableColumnDefinitionsFactory(); return ( } - filter={} + empty={ + + } + filter={ + + } footer={ actions.setSelectedItems([])} resetDisabled={collectionProps.selectedItems?.length === 0} @@ -130,7 +142,10 @@ export function AssetModelPropertiesTable({ /> } preferences={ - + } ariaLabels={{ resizerRoleDescription: 'Resize button', diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTableColumnDefinitionsFactory.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTableColumnDefinitionsFactory.tsx index 37cce4961..a66a0c6b8 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTableColumnDefinitionsFactory.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTableColumnDefinitionsFactory.tsx @@ -1,7 +1,8 @@ import { AssetModelPropertySummary } from '@aws-sdk/client-iotsitewise'; import { type TableProps } from '@cloudscape-design/components/table'; -type AssetModelPropertiesTableColumnDefinitions = TableProps['columnDefinitions']; +type AssetModelPropertiesTableColumnDefinitions = + TableProps['columnDefinitions']; export class AssetModelPropertiesTableColumnDefinitionsFactory { public create(): AssetModelPropertiesTableColumnDefinitions { diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTableEmptyState.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTableEmptyState.tsx index c38812a3c..4d5753851 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTableEmptyState.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTableEmptyState.tsx @@ -8,7 +8,10 @@ type AssetModelPropertiesTableEmptyStateOptions = { retry: () => void; }; -export function AssetModelPropertiesTableEmptyState({ isError, retry }: AssetModelPropertiesTableEmptyStateOptions) { +export function AssetModelPropertiesTableEmptyState({ + isError, + retry, +}: AssetModelPropertiesTableEmptyStateOptions) { const emptyState = ( <> No asset model properties. diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTableFilter.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTableFilter.tsx index 2d9a5fdad..1a2944538 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTableFilter.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTableFilter.tsx @@ -1,18 +1,26 @@ -import PropertyFilter, { type PropertyFilterProps } from '@cloudscape-design/components/property-filter'; +import PropertyFilter, { + type PropertyFilterProps, +} from '@cloudscape-design/components/property-filter'; import React from 'react'; -export const ASSET_MODEL_PROPERTIES_TABLE_FILTERING_PROPERTIES: PropertyFilterProps['filteringProperties'] = [ - { - key: 'name', - propertyLabel: 'Name', - groupValuesLabel: 'Property names', - operators: ['=', '!=', ':', '!:'], - }, -]; +export const ASSET_MODEL_PROPERTIES_TABLE_FILTERING_PROPERTIES: PropertyFilterProps['filteringProperties'] = + [ + { + key: 'name', + propertyLabel: 'Name', + groupValuesLabel: 'Property names', + operators: ['=', '!=', ':', '!:'], + }, + ]; -export type AssetModelPropertiesTablePropertyFilterProps = Omit; +export type AssetModelPropertiesTablePropertyFilterProps = Omit< + PropertyFilterProps, + 'i18nStrings' +>; -export function AssetModelPropertiesTablePropertyFilter(props: AssetModelPropertiesTablePropertyFilterProps) { +export function AssetModelPropertiesTablePropertyFilter( + props: AssetModelPropertiesTablePropertyFilterProps +) { return ( `Remove token ${token.propertyKey} ${token.operator} ${token.value}`, + removeTokenButtonAriaLabel: (token) => + `Remove token ${token.propertyKey} ${token.operator} ${token.value}`, enteredTextLabel: (text) => `Use: "${text}"`, }} /> diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTableHeader.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTableHeader.tsx index 596c00416..46382a19d 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTableHeader.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTableHeader.tsx @@ -14,7 +14,11 @@ export function AssetModelPropertiesTableHeader({
    Asset model properties
    diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTablePagination.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTablePagination.tsx index 7341d1902..4ea581468 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTablePagination.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/assetModelPropertiesTable/assetModelPropertiesTablePagination.tsx @@ -1,9 +1,13 @@ -import Pagination, { type PaginationProps } from '@cloudscape-design/components/pagination'; +import Pagination, { + type PaginationProps, +} from '@cloudscape-design/components/pagination'; import React from 'react'; export type AssetModelPropertiesTablePaginationProps = PaginationProps; -export function AssetModelPropertiesTablePagination({ ...props }: AssetModelPropertiesTablePaginationProps) { +export function AssetModelPropertiesTablePagination({ + ...props +}: AssetModelPropertiesTablePaginationProps) { return ( ; type Preferences

    = P; -export interface AssetModelPropertiesTablePreferencesProps

    { +export interface AssetModelPropertiesTablePreferencesProps< + P extends AllPreferences +> { preferences: Preferences

    ; updatePreferences: (preferences: Preferences

    ) => void; } @@ -28,7 +30,10 @@ export function AssetModelPropertiesTablePreferences

    ({ }} pageSizePreference={{ title: 'Select page size', - options: SUPPORTED_PAGE_SIZES.map((size) => ({ value: size, label: size.toString() })), + options: SUPPORTED_PAGE_SIZES.map((size) => ({ + value: size, + label: size.toString(), + })), }} wrapLinesPreference={{ label: 'Wrap lines', @@ -56,7 +61,8 @@ export function AssetModelPropertiesTablePreferences

    ({ stickyColumnsPreference={{ firstColumns: { title: 'Stick first column(s)', - description: 'Keep the first column(s) visible while horizontally scrolling the table content.', + description: + 'Keep the first column(s) visible while horizontally scrolling the table content.', options: [ { label: 'None', value: 0 }, { label: 'First column', value: 1 }, diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/useAssetModelProperties/assetModelPropertiesQueryKeyFactory.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/useAssetModelProperties/assetModelPropertiesQueryKeyFactory.ts index 7f76f6293..a58e9368b 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/useAssetModelProperties/assetModelPropertiesQueryKeyFactory.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/useAssetModelProperties/assetModelPropertiesQueryKeyFactory.ts @@ -6,7 +6,9 @@ export class AssetModelPropertiesCacheKeyFactory { } create() { - const cacheKey = [{ resource: 'asset model properties', assetModelId: this.#assetModelId }] as const; + const cacheKey = [ + { resource: 'asset model properties', assetModelId: this.#assetModelId }, + ] as const; return cacheKey; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/useAssetModelProperties/getAssetModelPropertiesRequest.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/useAssetModelProperties/getAssetModelPropertiesRequest.ts index ca5bb9c06..e6c8e8ff0 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/useAssetModelProperties/getAssetModelPropertiesRequest.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/useAssetModelProperties/getAssetModelPropertiesRequest.ts @@ -1,4 +1,7 @@ -import { ListAssetModelPropertiesCommand, type IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; +import { + ListAssetModelPropertiesCommand, + type IoTSiteWiseClient, +} from '@aws-sdk/client-iotsitewise'; export class GetAssetModelPropertiesRequest { readonly #command: ListAssetModelPropertiesCommand; @@ -23,7 +26,9 @@ export class GetAssetModelPropertiesRequest { public async send() { try { - const response = await this.#client.send(this.#command, { abortSignal: this.#signal }); + const response = await this.#client.send(this.#command, { + abortSignal: this.#signal, + }); return response; } catch (error) { @@ -31,7 +36,13 @@ export class GetAssetModelPropertiesRequest { } } - #createCommand({ assetModelId, nextToken }: { assetModelId: string; nextToken?: string }) { + #createCommand({ + assetModelId, + nextToken, + }: { + assetModelId: string; + nextToken?: string; + }) { const command = new ListAssetModelPropertiesCommand({ assetModelId, nextToken, diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/useAssetModelProperties/useAssetModelProperties.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/useAssetModelProperties/useAssetModelProperties.ts index 553ebe9bb..21c8a7684 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/useAssetModelProperties/useAssetModelProperties.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetModelPropertiesExplorer/useAssetModelProperties/useAssetModelProperties.ts @@ -11,7 +11,10 @@ export interface UseAssetModelProperties { } /** Use an AWS IoT SiteWise asset description. */ -export function useAssetModelProperties({ assetModelId, client }: UseAssetModelProperties) { +export function useAssetModelProperties({ + assetModelId, + client, +}: UseAssetModelProperties) { const cacheKeyFactory = new AssetModelPropertiesCacheKeyFactory(assetModelId); const { @@ -34,7 +37,9 @@ export function useAssetModelProperties({ assetModelId, client }: UseAssetModelP }); const assetModelPropertySummaries = createNonNullableList( - assetModelPropertyResponses.flatMap((res) => res.assetModelPropertySummaries) + assetModelPropertyResponses.flatMap( + (res) => res.assetModelPropertySummaries + ) ); return { @@ -51,17 +56,28 @@ export function useAssetModelProperties({ assetModelId, client }: UseAssetModelP }; } -const isEnabled = (assetModelId?: string): assetModelId is string => Boolean(assetModelId); +const isEnabled = (assetModelId?: string): assetModelId is string => + Boolean(assetModelId); export const createQueryFn = (client: IoTSiteWiseClient) => { return async ({ queryKey: [{ assetModelId }], pageParam: nextToken, signal, - }: QueryFunctionContext>) => { - invariant(isEnabled(assetModelId), 'Expected assetModelId to be defined as required by the enabled flag.'); + }: QueryFunctionContext< + ReturnType + >) => { + invariant( + isEnabled(assetModelId), + 'Expected assetModelId to be defined as required by the enabled flag.' + ); - const request = new GetAssetModelPropertiesRequest({ assetModelId, nextToken, client, signal }); + const request = new GetAssetModelPropertiesRequest({ + assetModelId, + nextToken, + client, + signal, + }); const response = await request.send(); diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/assetForAssetModelSelect.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/assetForAssetModelSelect.tsx index a9eb9895a..c9dafc8f9 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/assetForAssetModelSelect.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/assetForAssetModelSelect.tsx @@ -5,7 +5,10 @@ import Select, { SelectProps } from '@cloudscape-design/components/select'; import { NonCancelableEventHandler } from '@cloudscape-design/components/internal/events'; import { OptionsLoadItemsDetail } from '@cloudscape-design/components/internal/components/dropdown/interfaces'; import { SelectedAsset } from '../useSelectedAsset'; -import { isEnabled, useAssetsForAssetModel } from './useAssetsForAssetModel/useAssetsForAssetModel'; +import { + isEnabled, + useAssetsForAssetModel, +} from './useAssetsForAssetModel/useAssetsForAssetModel'; export type AssetForAssetModelSelectOptions = { assetModelId?: string; @@ -57,7 +60,9 @@ export const AssetForAssetModelSelect = ({ refetch, } = useAssetsForAssetModel({ assetModelId, client }); - const selectedAssetOption = selectedAsset ? mapAssetToOption(selectedAsset) : null; + const selectedAssetOption = selectedAsset + ? mapAssetToOption(selectedAsset) + : null; const assetOptions = assetSummaries.map(mapAssetToOption); @@ -71,7 +76,9 @@ export const AssetForAssetModelSelect = ({ }; const onChange: NonCancelableEventHandler = (e) => { - const selected = assetSummaries.find((ams) => ams?.id === e.detail.selectedOption.value); + const selected = assetSummaries.find( + (ams) => ams?.id === e.detail.selectedOption.value + ); if (!selected) return; onSelectAsset(selected); }; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/assetForAssetModelSelectForm.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/assetForAssetModelSelectForm.tsx index 7ea4ac77a..46867309a 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/assetForAssetModelSelectForm.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/assetForAssetModelSelectForm.tsx @@ -1,10 +1,18 @@ import React from 'react'; import FormField from '@cloudscape-design/components/form-field'; -import { AssetForAssetModelSelect, AssetForAssetModelSelectOptions } from './assetForAssetModelSelect'; +import { + AssetForAssetModelSelect, + AssetForAssetModelSelectOptions, +} from './assetForAssetModelSelect'; -export const AssetForAssetModelSelectForm = (props: AssetForAssetModelSelectOptions) => ( - +export const AssetForAssetModelSelectForm = ( + props: AssetForAssetModelSelectOptions +) => ( + ); diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/useAssetsForAssetModel/assetsForAssetModelQueryKeyFactory.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/useAssetsForAssetModel/assetsForAssetModelQueryKeyFactory.ts index ef9c4df44..a2b05804f 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/useAssetsForAssetModel/assetsForAssetModelQueryKeyFactory.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/useAssetsForAssetModel/assetsForAssetModelQueryKeyFactory.ts @@ -6,7 +6,9 @@ export class AssetsForAssetModelCacheKeyFactory { } create() { - const cacheKey = [{ resource: 'assets for asset model', assetModelId: this.#assetModelId }] as const; + const cacheKey = [ + { resource: 'assets for asset model', assetModelId: this.#assetModelId }, + ] as const; return cacheKey; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/useAssetsForAssetModel/getAssetsForAssetModelRequest.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/useAssetsForAssetModel/getAssetsForAssetModelRequest.ts index 3f0e5dad3..35e2bd018 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/useAssetsForAssetModel/getAssetsForAssetModelRequest.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/useAssetsForAssetModel/getAssetsForAssetModelRequest.ts @@ -1,4 +1,7 @@ -import { ListAssetsCommand, type IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; +import { + ListAssetsCommand, + type IoTSiteWiseClient, +} from '@aws-sdk/client-iotsitewise'; export class GetAssetsForAssetModelRequest { readonly #command: ListAssetsCommand; @@ -23,7 +26,9 @@ export class GetAssetsForAssetModelRequest { public async send() { try { - const response = await this.#client.send(this.#command, { abortSignal: this.#signal }); + const response = await this.#client.send(this.#command, { + abortSignal: this.#signal, + }); return response; } catch (error) { @@ -31,7 +36,13 @@ export class GetAssetsForAssetModelRequest { } } - #createCommand({ assetModelId, nextToken }: { assetModelId: string; nextToken?: string }) { + #createCommand({ + assetModelId, + nextToken, + }: { + assetModelId: string; + nextToken?: string; + }) { const command = new ListAssetsCommand({ assetModelId, nextToken, diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/useAssetsForAssetModel/useAssetsForAssetModel.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/useAssetsForAssetModel/useAssetsForAssetModel.ts index e33bc5754..a5de443f7 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/useAssetsForAssetModel/useAssetsForAssetModel.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/assetsForAssetModelSelect/useAssetsForAssetModel/useAssetsForAssetModel.ts @@ -12,7 +12,11 @@ export interface UseAssetModelsOptions { } /** Use an AWS IoT SiteWise asset description. */ -export function useAssetsForAssetModel({ client, assetModelId, fetchAll }: UseAssetModelsOptions) { +export function useAssetsForAssetModel({ + client, + assetModelId, + fetchAll, +}: UseAssetModelsOptions) { const cacheKeyFactory = new AssetsForAssetModelCacheKeyFactory(assetModelId); const { @@ -35,7 +39,9 @@ export function useAssetsForAssetModel({ client, assetModelId, fetchAll }: UseAs if (fetchAll && hasNextPage) fetchNextPage(); - const assetSummaries = createNonNullableList(assetsResponses.flatMap((res) => res.assetSummaries)); + const assetSummaries = createNonNullableList( + assetsResponses.flatMap((res) => res.assetSummaries) + ); return { assetSummaries, @@ -51,17 +57,28 @@ export function useAssetsForAssetModel({ client, assetModelId, fetchAll }: UseAs }; } -export const isEnabled = (assetModelId?: string): assetModelId is string => Boolean(assetModelId); +export const isEnabled = (assetModelId?: string): assetModelId is string => + Boolean(assetModelId); export const createQueryFn = (client: IoTSiteWiseClient) => { return async ({ queryKey: [{ assetModelId }], pageParam: nextToken, signal, - }: QueryFunctionContext>) => { - invariant(isEnabled(assetModelId), 'Expected assetModelId to be defined as required by the enabled flag.'); + }: QueryFunctionContext< + ReturnType + >) => { + invariant( + isEnabled(assetModelId), + 'Expected assetModelId to be defined as required by the enabled flag.' + ); - const request = new GetAssetsForAssetModelRequest({ assetModelId, nextToken, client, signal }); + const request = new GetAssetsForAssetModelRequest({ + assetModelId, + nextToken, + client, + signal, + }); const response = await request.send(); diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/getAssetModelQueryInformation.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/getAssetModelQueryInformation.ts index 7ed710b27..18a8d7cb1 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/getAssetModelQueryInformation.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/getAssetModelQueryInformation.ts @@ -1,12 +1,16 @@ import { SiteWiseAssetModelQuery } from '@iot-app-kit/source-iotsitewise'; -export const getAssetModelQueryInformation = (assetModels: SiteWiseAssetModelQuery['assetModels']) => { +export const getAssetModelQueryInformation = ( + assetModels: SiteWiseAssetModelQuery['assetModels'] +) => { // Current implementation only allows for 1 asset model query and 1 asset model const firstAssetModel = assetModels.at(0); const assetModelId = firstAssetModel?.assetModelId; const assetId = firstAssetModel?.assetIds?.at(0); - const propertyIds = firstAssetModel?.properties.map(({ propertyId }) => propertyId); + const propertyIds = firstAssetModel?.properties.map( + ({ propertyId }) => propertyId + ); return { assetModelId, assetId, diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/modelBasedQuery/findModelBasedQueryWidgets.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/modelBasedQuery/findModelBasedQueryWidgets.ts index cbe21db83..c014edb00 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/modelBasedQuery/findModelBasedQueryWidgets.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/modelBasedQuery/findModelBasedQueryWidgets.ts @@ -3,12 +3,18 @@ import { DashboardState } from '~/store/state'; import { DashboardWidget } from '~/types'; export type QueryConfigWidget = DashboardWidget; -export const isQueryWidget = (w: DashboardWidget): w is QueryConfigWidget => 'queryConfig' in w.properties; +export const isQueryWidget = (w: DashboardWidget): w is QueryConfigWidget => + 'queryConfig' in w.properties; -export const findModelBasedQueryWidgets = (dashboardConfiguration: DashboardState['dashboardConfiguration']) => +export const findModelBasedQueryWidgets = ( + dashboardConfiguration: DashboardState['dashboardConfiguration'] +) => dashboardConfiguration.widgets .filter(isQueryWidget) - .filter((w) => (w.properties.queryConfig.query?.assetModels ?? []).length > 0); + .filter( + (w) => (w.properties.queryConfig.query?.assetModels ?? []).length > 0 + ); -export const hasModelBasedQuery = (dashboardConfiguration: DashboardState['dashboardConfiguration']) => - findModelBasedQueryWidgets(dashboardConfiguration).length > 0; +export const hasModelBasedQuery = ( + dashboardConfiguration: DashboardState['dashboardConfiguration'] +) => findModelBasedQueryWidgets(dashboardConfiguration).length > 0; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/modelBasedQuery/useModelBasedQuery.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/modelBasedQuery/useModelBasedQuery.ts index 72f94192a..75553d23d 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/modelBasedQuery/useModelBasedQuery.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/modelBasedQuery/useModelBasedQuery.ts @@ -1,7 +1,11 @@ import { DashboardState } from '~/store/state'; import { isEqual } from 'lodash'; import { useDispatch, useSelector } from 'react-redux'; -import { QueryConfigWidget, findModelBasedQueryWidgets, hasModelBasedQuery } from './findModelBasedQueryWidgets'; +import { + QueryConfigWidget, + findModelBasedQueryWidgets, + hasModelBasedQuery, +} from './findModelBasedQueryWidgets'; import { useCallback, useMemo } from 'react'; import { onUpdateWidgetsAction } from '~/store/actions'; import { AssetSummary } from '@aws-sdk/client-iotsitewise'; @@ -12,10 +16,19 @@ export const useModelBasedQuery = () => { (dashboardState: DashboardState) => dashboardState.dashboardConfiguration, isEqual ); - const modelBasedWidgets = useMemo(() => findModelBasedQueryWidgets(dashboardConfiguration), [dashboardConfiguration]); + const modelBasedWidgets = useMemo( + () => findModelBasedQueryWidgets(dashboardConfiguration), + [dashboardConfiguration] + ); const hasModelBasedQueryWidgets = hasModelBasedQuery(dashboardConfiguration); - const firstWidget = useMemo(() => modelBasedWidgets.at(0), [modelBasedWidgets]); - const assetModel = useMemo(() => (firstWidget?.properties.queryConfig.query?.assetModels ?? []).at(0), [firstWidget]); + const firstWidget = useMemo( + () => modelBasedWidgets.at(0), + [modelBasedWidgets] + ); + const assetModel = useMemo( + () => (firstWidget?.properties.queryConfig.query?.assetModels ?? []).at(0), + [firstWidget] + ); const updateModelBasedWidgets = useCallback( (updatedWidgets: QueryConfigWidget[]) => { @@ -29,43 +42,50 @@ export const useModelBasedQuery = () => { ); const clearModelBasedWidgets = () => { - const clearedModelBasedWidgets = modelBasedWidgets.map(({ properties, ...rest }) => ({ - ...rest, - properties: { - ...properties, - queryConfig: { - ...properties.queryConfig, - query: { - ...properties.queryConfig.query, - assetModels: [], + const clearedModelBasedWidgets = modelBasedWidgets.map( + ({ properties, ...rest }) => ({ + ...rest, + properties: { + ...properties, + queryConfig: { + ...properties.queryConfig, + query: { + ...properties.queryConfig.query, + assetModels: [], + }, }, }, - }, - })); + }) + ); updateModelBasedWidgets(clearedModelBasedWidgets); }; const updateSelectedAsset = useCallback( (updatedSelectedAsset?: AssetSummary) => { - if (!updatedSelectedAsset || updatedSelectedAsset.id === undefined) return; + if (!updatedSelectedAsset || updatedSelectedAsset.id === undefined) + return; const id = updatedSelectedAsset.id; - const updatedSelectedAssets = modelBasedWidgets.map(({ properties, ...rest }) => ({ - ...rest, - properties: { - ...properties, - queryConfig: { - ...properties.queryConfig, - query: { - ...properties.queryConfig.query, - assetModels: (properties.queryConfig.query?.assetModels ?? []).map((assetModel) => ({ - ...assetModel, - assetIds: [id], - })), + const updatedSelectedAssets = modelBasedWidgets.map( + ({ properties, ...rest }) => ({ + ...rest, + properties: { + ...properties, + queryConfig: { + ...properties.queryConfig, + query: { + ...properties.queryConfig.query, + assetModels: ( + properties.queryConfig.query?.assetModels ?? [] + ).map((assetModel) => ({ + ...assetModel, + assetIds: [id], + })), + }, }, }, - }, - })); + }) + ); updateModelBasedWidgets(updatedSelectedAssets); }, diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/modelBasedQuery/useModelBasedQuerySelection.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/modelBasedQuery/useModelBasedQuerySelection.ts index 04571440f..dfe297a82 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/modelBasedQuery/useModelBasedQuerySelection.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/modelBasedQuery/useModelBasedQuerySelection.ts @@ -17,8 +17,14 @@ const mergeAssetModelProperties = ( ...assetModel, properties: assetModel.properties.map((property) => { const currentProperty = currentAssetModels - .find((currentAssetModel) => currentAssetModel.assetModelId === assetModel.assetModelId) - ?.properties.find((currentAssetModelProperty) => currentAssetModelProperty.propertyId === property.propertyId); + .find( + (currentAssetModel) => + currentAssetModel.assetModelId === assetModel.assetModelId + ) + ?.properties.find( + (currentAssetModelProperty) => + currentAssetModelProperty.propertyId === property.propertyId + ); return { ...currentProperty, ...property, @@ -45,9 +51,13 @@ export const useModelBasedQuerySelection = () => { (_properties, updatedProperties) => updatedProperties ) ?? [undefined, noop]; - const properties = propertiesMaybe ? maybeWithDefault(defaultQuery, propertiesMaybe) ?? defaultQuery : defaultQuery; + const properties = propertiesMaybe + ? maybeWithDefault(defaultQuery, propertiesMaybe) ?? defaultQuery + : defaultQuery; - const updateAssetModels = (updatedAssetModels: AssetModelQuery[] | undefined) => { + const updateAssetModels = ( + updatedAssetModels: AssetModelQuery[] | undefined + ) => { // Only allow updates for a single widget type at a time. // This is necessary because xy-plot query config has a different structure if (!selectionType || !isJust(selectionType)) return; @@ -61,9 +71,11 @@ export const useModelBasedQuerySelection = () => { if (selectionType.value === 'xy-plot') { const styledQuery = styledQueryWidgetOnDrop( { - ...compositeWidgetForAggregationInformation.properties.queryConfig.query, + ...compositeWidgetForAggregationInformation.properties.queryConfig + .query, assetModels: mergeAssetModelProperties( - compositeWidgetForAggregationInformation.properties.queryConfig.query, + compositeWidgetForAggregationInformation.properties.queryConfig + .query, updatedAssetModels ), }, @@ -75,7 +87,8 @@ export const useModelBasedQuerySelection = () => { ...properties.queryConfig, query: { ...properties.queryConfig.query, - assetModels: (styledQuery as unknown as IoTSiteWiseDataStreamQuery).assetModels, + assetModels: (styledQuery as unknown as IoTSiteWiseDataStreamQuery) + .assetModels, }, }, }; @@ -87,9 +100,11 @@ export const useModelBasedQuerySelection = () => { queryConfig: { ...compositeWidgetForAggregationInformation.properties.queryConfig, query: { - ...compositeWidgetForAggregationInformation.properties.queryConfig.query, + ...compositeWidgetForAggregationInformation.properties.queryConfig + .query, assetModels: mergeAssetModelProperties( - compositeWidgetForAggregationInformation.properties.queryConfig.query, + compositeWidgetForAggregationInformation.properties.queryConfig + .query, updatedAssetModels ), }, diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/useSelectedAsset.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/useSelectedAsset.ts index 9dcad2850..bf89e2edd 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/useSelectedAsset.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/useSelectedAsset.ts @@ -2,7 +2,9 @@ import { AssetSummary } from '@aws-sdk/client-iotsitewise'; import { useState } from 'react'; export const useSelectedAsset = (initialSelectedAsset?: AssetSummary) => { - const [selectedAsset, setSelectedAsset] = useState(initialSelectedAsset); + const [selectedAsset, setSelectedAsset] = useState( + initialSelectedAsset + ); return [selectedAsset, setSelectedAsset] as const; }; @@ -10,4 +12,5 @@ export const useSelectedAsset = (initialSelectedAsset?: AssetSummary) => { export type SelectedAsset = ReturnType[0]; export type UpdateSelectedAsset = ReturnType[1]; -export const createInitialAsset = (assetId?: string) => (assetId ? ({ id: assetId } as AssetSummary) : undefined); +export const createInitialAsset = (assetId?: string) => + assetId ? ({ id: assetId } as AssetSummary) : undefined; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/useSelectedAssetModel.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/useSelectedAssetModel.ts index dda84ae60..bedb75176 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/useSelectedAssetModel.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/useSelectedAssetModel.ts @@ -1,15 +1,20 @@ import { AssetModelSummary } from '@aws-sdk/client-iotsitewise'; import { useState } from 'react'; -export const useSelectedAssetModel = (initialSelectedAssetModel?: AssetModelSummary) => { - const [selectedAssetModel, setSelectedAssetModel] = useState( - initialSelectedAssetModel - ); +export const useSelectedAssetModel = ( + initialSelectedAssetModel?: AssetModelSummary +) => { + const [selectedAssetModel, setSelectedAssetModel] = useState< + AssetModelSummary | undefined + >(initialSelectedAssetModel); return [selectedAssetModel, setSelectedAssetModel] as const; }; export type SelectedAssetModel = ReturnType[0]; -export type UpdateSelectedAssetModel = ReturnType[1]; +export type UpdateSelectedAssetModel = ReturnType< + typeof useSelectedAssetModel +>[1]; -export const createInitialAssetModelSummary = (id?: string) => (id ? ({ id } as AssetModelSummary) : undefined); +export const createInitialAssetModelSummary = (id?: string) => + id ? ({ id } as AssetModelSummary) : undefined; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/useSelectedAssetModelProperties.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/useSelectedAssetModelProperties.ts index c90e4321d..7e5785706 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/useSelectedAssetModelProperties.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/assetModelDataStreamExplorer/useSelectedAssetModelProperties.ts @@ -3,7 +3,9 @@ import { AssetModelPropertySummary } from '@aws-sdk/client-iotsitewise'; import { isEqual } from 'lodash'; import { useCustomCompareEffect } from 'react-use'; -export const useSelectedAssetModelProperties = (initialAssetModelProperties: AssetModelPropertySummary[] = []) => { +export const useSelectedAssetModelProperties = ( + initialAssetModelProperties: AssetModelPropertySummary[] = [] +) => { const [selectedAssetModelProperties, setSelectedAssetModelProperties] = useState(initialAssetModelProperties); @@ -15,11 +17,18 @@ export const useSelectedAssetModelProperties = (initialAssetModelProperties: Ass isEqual ); - return [selectedAssetModelProperties, setSelectedAssetModelProperties] as const; + return [ + selectedAssetModelProperties, + setSelectedAssetModelProperties, + ] as const; }; -export type SelectedAssetModelProperties = ReturnType[0]; -export type UpdateSelectedAssetModelProperties = ReturnType[1]; +export type SelectedAssetModelProperties = ReturnType< + typeof useSelectedAssetModelProperties +>[0]; +export type UpdateSelectedAssetModelProperties = ReturnType< + typeof useSelectedAssetModelProperties +>[1]; export const createInitialAssetModelProperties = (properties?: string[]) => properties?.map((id) => ({ id } as AssetModelPropertySummary)) ?? []; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/constants.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/constants.ts index 07ddf0986..072eb2e8d 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/constants.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/constants.ts @@ -1,3 +1,5 @@ export const MAX_QUERY_STATEMENT_LENGTH = 1000; -export const infoMessage = 'To find you assests, make sure to enter the exact model name or characters.'; -export const errorMessage = 'Advanced search not supported, no workspace available.'; +export const infoMessage = + 'To find you assests, make sure to enter the exact model name or characters.'; +export const errorMessage = + 'Advanced search not supported, no workspace available.'; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/dataStreamSearch.test.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/dataStreamSearch.test.tsx index b68c67c2f..765ffd690 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/dataStreamSearch.test.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/dataStreamSearch.test.tsx @@ -15,25 +15,39 @@ describe('DataStreamSearch component', () => { test('renders error and info component when no workspaceis available', async () => { render( - + ); - const errorMessage = screen.getByText('Advanced search not supported, no workspace available.'); + const errorMessage = screen.getByText( + 'Advanced search not supported, no workspace available.' + ); expect(errorMessage).toBeInTheDocument(); - const infoMessage = screen.getByText('To find you assests, make sure to enter the exact model name or characters.'); + const infoMessage = screen.getByText( + 'To find you assests, make sure to enter the exact model name or characters.' + ); expect(infoMessage).toBeInTheDocument(); }); test('expects to have an external link ', () => { render( - + ); - expect(screen.getByRole('link', { name: 'Learn more about creating a workspace' })).toHaveAttribute( + expect( + screen.getByRole('link', { + name: 'Learn more about creating a workspace', + }) + ).toHaveAttribute( 'href', 'https://docs.aws.amazon.com/iot-twinmaker/latest/guide/twinmaker-gs-workspace.html' ); diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/dataStreamSearch.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/dataStreamSearch.tsx index 4961de31f..6554f863f 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/dataStreamSearch.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/dataStreamSearch.tsx @@ -1,5 +1,10 @@ import { type IoTTwinMakerClient } from '@aws-sdk/client-iottwinmaker'; -import { Button, Form, Header, SpaceBetween } from '@cloudscape-design/components'; +import { + Button, + Form, + Header, + SpaceBetween, +} from '@cloudscape-design/components'; import React, { useState } from 'react'; import { useForm } from 'react-hook-form'; @@ -14,7 +19,10 @@ export interface DataStreamSearchProps { onSubmit: ({ workspace, searchQuery }: SearchFields) => void; } -export const DataStreamSearch = ({ onSubmit, client }: DataStreamSearchProps) => { +export const DataStreamSearch = ({ + onSubmit, + client, +}: DataStreamSearchProps) => { const metricsRecorder = getPlugin('metricsRecorder'); const { control, handleSubmit } = useForm({ defaultValues: { workspace: null, searchQuery: '' }, @@ -24,7 +32,10 @@ export const DataStreamSearch = ({ onSubmit, client }: DataStreamSearchProps) => return ( -

    +
    Search modeled data streams
    @@ -53,7 +64,11 @@ export const DataStreamSearch = ({ onSubmit, client }: DataStreamSearchProps) => > - + diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/executeQueryRequest.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/executeQueryRequest.ts index 022b267c0..f7204e4d2 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/executeQueryRequest.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/executeQueryRequest.ts @@ -22,14 +22,20 @@ export class ExecuteQueryRequest { client: IoTTwinMakerClient; signal?: AbortSignal; }) { - this.#command = this.#createCommand({ queryStatement, workspaceId, nextToken }); + this.#command = this.#createCommand({ + queryStatement, + workspaceId, + nextToken, + }); this.#client = client; this.#signal = signal; } public async send() { try { - const response = await this.#client.send(this.#command, { abortSignal: this.#signal }); + const response = await this.#client.send(this.#command, { + abortSignal: this.#signal, + }); return response; } catch (error) { diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/index.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/index.ts index 08f419e6e..c952d0cb1 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/index.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/index.ts @@ -1 +1,4 @@ -export { DataStreamSearch, type DataStreamSearchProps } from './dataStreamSearch'; +export { + DataStreamSearch, + type DataStreamSearchProps, +} from './dataStreamSearch'; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/queryResponseProcessor.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/queryResponseProcessor.ts index 0b99c0883..f7741be0c 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/queryResponseProcessor.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/queryResponseProcessor.ts @@ -16,8 +16,12 @@ const MODEL_MAP = { }; export class QueryResponseProcessor { - public static process(response: ExecuteQueryCommandOutput): ModeledDataStream[] { - const modeledDataStreams = this.#convertResponseToModeledDataStreams(response.rows ?? []); + public static process( + response: ExecuteQueryCommandOutput + ): ModeledDataStream[] { + const modeledDataStreams = this.#convertResponseToModeledDataStreams( + response.rows ?? [] + ); return modeledDataStreams; } @@ -29,7 +33,10 @@ export class QueryResponseProcessor { rows // Filter out any invalid rows. Situation has occured where `null` was being returned for propertyId and propertyName which causes downstream bugs. .filter( - (row) => row.rowData && row.rowData.length === 4 && row.rowData.every((cell) => typeof cell === 'string') + (row) => + row.rowData && + row.rowData.length === 4 && + row.rowData.every((cell) => typeof cell === 'string') ) .map((row) => this.#convertRowToModeledDataStream(row.rowData as Model)) ); diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/queryStatementFactory.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/queryStatementFactory.ts index 3bb2610fd..eb3a281a6 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/queryStatementFactory.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/queryStatementFactory.ts @@ -43,7 +43,10 @@ export class QueryStatementFactory { } #replaceWildcardCharacters(searchQuery: string): string { - const searchQueryWithValidWildcardCharacters = searchQuery.replaceAll('*', '%'); + const searchQueryWithValidWildcardCharacters = searchQuery.replaceAll( + '*', + '%' + ); return searchQueryWithValidWildcardCharacters; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/search.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/search.ts index ae8bdbb64..84414b0c8 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/search.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/search.ts @@ -1,4 +1,7 @@ -import { type ExecuteQueryCommandOutput, type IoTTwinMakerClient } from '@aws-sdk/client-iottwinmaker'; +import { + type ExecuteQueryCommandOutput, + type IoTTwinMakerClient, +} from '@aws-sdk/client-iottwinmaker'; import { ExecuteQueryRequest } from './executeQueryRequest'; import { QueryStatementFactory } from './queryStatementFactory'; @@ -19,7 +22,13 @@ export async function search({ const queryStatementFactory = new QueryStatementFactory(searchQuery); const queryStatement = queryStatementFactory.create(); - const request = new ExecuteQueryRequest({ workspaceId, queryStatement, nextToken, client, signal }); + const request = new ExecuteQueryRequest({ + workspaceId, + queryStatement, + nextToken, + client, + signal, + }); const response = await request.send(); return response; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/searchQueryInput.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/searchQueryInput.tsx index 6bf458d68..d06120db6 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/searchQueryInput.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/searchQueryInput.tsx @@ -9,7 +9,10 @@ export interface SearchQueryInputProps { disabled?: boolean; } -export const SearchQueryInput = ({ control, disabled }: SearchQueryInputProps) => { +export const SearchQueryInput = ({ + control, + disabled, +}: SearchQueryInputProps) => { const MIN_SEARCH_QUERY_LENGTH = 1; const MAX_SEARCH_QUERY_LENGTH = 48; @@ -19,8 +22,14 @@ export const SearchQueryInput = ({ control, disabled }: SearchQueryInputProps) = name='searchQuery' rules={{ required: 'Search query must be between 1 and 48 characters.', - minLength: { value: MIN_SEARCH_QUERY_LENGTH, message: 'Search query is too short.' }, - maxLength: { value: MAX_SEARCH_QUERY_LENGTH, message: 'Search query is too long.' }, + minLength: { + value: MIN_SEARCH_QUERY_LENGTH, + message: 'Search query is too short.', + }, + maxLength: { + value: MAX_SEARCH_QUERY_LENGTH, + message: 'Search query is too long.', + }, }} render={({ field, fieldState }) => ( [{ ...CACHE_KEYS.all[0], resource: 'searches' }] as const, - search: ({ workspaceId, searchQuery }: { workspaceId: string; searchQuery: string }) => - [{ ...CACHE_KEYS.searches()[0], workspaceId, searchQuery }] as const, + search: ({ + workspaceId, + searchQuery, + }: { + workspaceId: string; + searchQuery: string; + }) => [{ ...CACHE_KEYS.searches()[0], workspaceId, searchQuery }] as const, }; interface UseSearchProps { @@ -20,17 +25,29 @@ interface UseSearchProps { } /** Use to send a search request. */ -export function useSearch({ workspaceId, searchQuery, client }: UseSearchProps) { - const { data, hasNextPage, isFetching, fetchNextPage, isError } = useInfiniteQuery({ - enabled: searchQuery !== '', - queryKey: CACHE_KEYS.search({ workspaceId, searchQuery }), - queryFn: createQueryFn(client), - getNextPageParam: ({ nextToken }) => nextToken, - }); +export function useSearch({ + workspaceId, + searchQuery, + client, +}: UseSearchProps) { + const { data, hasNextPage, isFetching, fetchNextPage, isError } = + useInfiniteQuery({ + enabled: searchQuery !== '', + queryKey: CACHE_KEYS.search({ workspaceId, searchQuery }), + queryFn: createQueryFn(client), + getNextPageParam: ({ nextToken }) => nextToken, + }); - const modeledDataStreams = data?.pages.flatMap(({ modeledDataStreams }) => modeledDataStreams) ?? []; + const modeledDataStreams = + data?.pages.flatMap(({ modeledDataStreams }) => modeledDataStreams) ?? []; - return { modeledDataStreams, hasNextPage, isFetching, fetchNextPage, isError }; + return { + modeledDataStreams, + hasNextPage, + isFetching, + fetchNextPage, + isError, + }; } function createQueryFn(client: IoTTwinMakerClient) { @@ -39,7 +56,13 @@ function createQueryFn(client: IoTTwinMakerClient) { pageParam: nextToken, signal, }: QueryFunctionContext>) { - const response = await search({ workspaceId, searchQuery, nextToken, client, signal }); + const response = await search({ + workspaceId, + searchQuery, + nextToken, + client, + signal, + }); const modeledDataStreams = QueryResponseProcessor.process(response); diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceErrorState.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceErrorState.tsx index 4b642a190..6df4aba60 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceErrorState.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceErrorState.tsx @@ -5,12 +5,25 @@ import { infoMessage, errorMessage } from './constants'; export const WorkspaceErrorState = () => { return ( - + {infoMessage} - + {errorMessage}  - + Learn more about creating a workspace diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceSelector/index.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceSelector/index.ts index 13c4dfee6..afed5f9a4 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceSelector/index.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceSelector/index.ts @@ -1 +1,4 @@ -export { WorkspaceSelector, type WorkspaceSelectorProps } from './workspaceSelector'; +export { + WorkspaceSelector, + type WorkspaceSelectorProps, +} from './workspaceSelector'; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceSelector/listWorkspacesRequest.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceSelector/listWorkspacesRequest.ts index 62ffee98d..fba3c2f69 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceSelector/listWorkspacesRequest.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceSelector/listWorkspacesRequest.ts @@ -1,11 +1,20 @@ -import { paginateListWorkspaces, type IoTTwinMakerClient } from '@aws-sdk/client-iottwinmaker'; +import { + paginateListWorkspaces, + type IoTTwinMakerClient, +} from '@aws-sdk/client-iottwinmaker'; export class ListWorkspacesRequest { readonly #MAX_WORKSPACES_PAGE_SIZE = 200; readonly #paginator: ReturnType; readonly #signal: AbortSignal | undefined; - constructor({ client, signal }: { client: IoTTwinMakerClient; signal?: AbortSignal }) { + constructor({ + client, + signal, + }: { + client: IoTTwinMakerClient; + signal?: AbortSignal; + }) { this.#paginator = this.#createPaginator(client); this.#signal = signal; } @@ -28,7 +37,10 @@ export class ListWorkspacesRequest { } #createPaginator(client: IoTTwinMakerClient) { - const paginator = paginateListWorkspaces({ pageSize: this.#MAX_WORKSPACES_PAGE_SIZE, client }, {}); + const paginator = paginateListWorkspaces( + { pageSize: this.#MAX_WORKSPACES_PAGE_SIZE, client }, + {} + ); return paginator; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceSelector/useWorkspaces.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceSelector/useWorkspaces.ts index 108907872..a01a47778 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceSelector/useWorkspaces.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceSelector/useWorkspaces.ts @@ -7,7 +7,8 @@ const TWIN_MAKER_CACHE_KEY = [{ service: 'iottwinmaker' }] as const; const CACHE_KEYS = { all: [{ ...TWIN_MAKER_CACHE_KEY[0], key: 'workspaces' }] as const, - workspaces: () => [{ ...CACHE_KEYS.all[0], resource: 'workspace summary' }] as const, + workspaces: () => + [{ ...CACHE_KEYS.all[0], resource: 'workspace summary' }] as const, }; export interface UseWorkspacesOptions { @@ -24,7 +25,9 @@ export function useWorkspaces({ client }: UseWorkspacesOptions) { } function createQueryFn(client: IoTTwinMakerClient) { - return async function ({ signal }: QueryFunctionContext>) { + return async function ({ + signal, + }: QueryFunctionContext>) { const request = new ListWorkspacesRequest({ client, signal }); const response = await request.send(); diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceSelector/workspaceSelector.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceSelector/workspaceSelector.tsx index 2ebae793a..d1e8c561e 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceSelector/workspaceSelector.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/dataStreamSearch/workspaceSelector/workspaceSelector.tsx @@ -18,7 +18,11 @@ export interface WorkspaceSelectorProps { OnGettingError: (isError: boolean) => void; } -export const WorkspaceSelector = ({ control, client, OnGettingError }: WorkspaceSelectorProps) => { +export const WorkspaceSelector = ({ + control, + client, + OnGettingError, +}: WorkspaceSelectorProps) => { const { workspaces, status } = useWorkspaces({ client }); const workspaceOptions = createWorkspaceOptions(workspaces); @@ -53,7 +57,9 @@ export const WorkspaceSelector = ({ control, client, OnGettingError }: Workspace ); }; -function createWorkspaceOptions(workspaces: NonNullable['workspaces']>) { +function createWorkspaceOptions( + workspaces: NonNullable['workspaces']> +) { const workspaceOptionFactory = new WorkspaceOptionFactory(); const workspaceOptions = workspaces.map(workspaceOptionFactory.create); diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/index.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/index.ts index 5dec0f19f..d3c52e445 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/index.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/index.ts @@ -1 +1,4 @@ -export { IoTSiteWiseQueryEditor, type IoTSiteWiseQueryEditorProps } from './iotSiteWiseQueryEditor'; +export { + IoTSiteWiseQueryEditor, + type IoTSiteWiseQueryEditorProps, +} from './iotSiteWiseQueryEditor'; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/iotSiteWiseQueryEditor.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/iotSiteWiseQueryEditor.tsx index 177c14669..640e43a1f 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/iotSiteWiseQueryEditor.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/iotSiteWiseQueryEditor.tsx @@ -22,19 +22,27 @@ export function IoTSiteWiseQueryEditor({ iotSiteWiseClient, iotTwinMakerClient, }: IoTSiteWiseQueryEditorProps) { - function handleClickAddModeledDataStreams(newModeledDataStreams: ModeledDataStream[]) { + function handleClickAddModeledDataStreams( + newModeledDataStreams: ModeledDataStream[] + ) { onUpdateQuery((currentQuery) => { const queryExtender = new QueryExtender(currentQuery); - const updatedQuery = queryExtender.extendAssetQueries(newModeledDataStreams); + const updatedQuery = queryExtender.extendAssetQueries( + newModeledDataStreams + ); return updatedQuery; }); } - function handleClickAddUnmodeledDataStreams(newUnmodeledDataStreams: UnmodeledDataStream[]) { + function handleClickAddUnmodeledDataStreams( + newUnmodeledDataStreams: UnmodeledDataStream[] + ) { onUpdateQuery((currentQuery) => { const queryExtender = new QueryExtender(currentQuery); - const updatedQuery = queryExtender.extendPropertyAliasQueries(newUnmodeledDataStreams); + const updatedQuery = queryExtender.extendPropertyAliasQueries( + newUnmodeledDataStreams + ); return updatedQuery; }); @@ -55,7 +63,10 @@ export function IoTSiteWiseQueryEditor({ label: 'Unmodeled', id: 'explore-unmodeled-tab', content: ( - + ), }; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetExplorer.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetExplorer.tsx index 4771b5b24..44405ec53 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetExplorer.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetExplorer.tsx @@ -1,4 +1,7 @@ -import { type AssetSummary, type IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; +import { + type AssetSummary, + type IoTSiteWiseClient, +} from '@aws-sdk/client-iotsitewise'; import React from 'react'; import { AssetTable } from './assetTable'; @@ -12,7 +15,11 @@ export interface AssetExplorerProps { } /** User interface element enabling the exploration and selection of assets. */ -export function AssetExplorer({ onSelect, isWithoutHeader, client }: AssetExplorerProps) { +export function AssetExplorer({ + onSelect, + isWithoutHeader, + client, +}: AssetExplorerProps) { const [parentAssetId, setParentAssetId] = useParentAssetId(); const { assets, diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTable.test.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTable.test.tsx index 6872a42a7..e8d66f4dc 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTable.test.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTable.test.tsx @@ -32,9 +32,15 @@ describe('AssetTableColumnDefinitionsFactory', () => { expect(columnDefinitions.some((def) => def.id === 'arn')).toBe(true); expect(columnDefinitions.some((def) => def.id === 'id')).toBe(true); expect(columnDefinitions.some((def) => def.id === 'name')).toBe(true); - expect(columnDefinitions.some((def) => def.id === 'description')).toBe(true); - expect(columnDefinitions.some((def) => def.id === 'creationDate')).toBe(true); - expect(columnDefinitions.some((def) => def.id === 'lastUpdateDate')).toBe(true); + expect(columnDefinitions.some((def) => def.id === 'description')).toBe( + true + ); + expect(columnDefinitions.some((def) => def.id === 'creationDate')).toBe( + true + ); + expect(columnDefinitions.some((def) => def.id === 'lastUpdateDate')).toBe( + true + ); }); }); }); diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTable.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTable.tsx index 2647585c9..2ee2dab77 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTable.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTable.tsx @@ -1,4 +1,7 @@ -import { type AssetSummary, type IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; +import { + type AssetSummary, + type IoTSiteWiseClient, +} from '@aws-sdk/client-iotsitewise'; import { useCollection } from '@cloudscape-design/collection-hooks'; import Table from '@cloudscape-design/components/table'; import React from 'react'; @@ -7,7 +10,10 @@ import { AssetTableEmptyState } from './assetTableEmptyState'; import { AssetTableHeader } from './assetTableHeader'; import { AssetTablePagination } from './assetTablePagination'; import { AssetTablePreferences } from './assetTablePreferences'; -import { AssetTablePropertyFilter, ASSET_TABLE_FILTERING_PROPERTIES } from './assetTablePropertyFilter'; +import { + AssetTablePropertyFilter, + ASSET_TABLE_FILTERING_PROPERTIES, +} from './assetTablePropertyFilter'; import { AssetTableColumnDefinitionsFactory } from './assetTableColumnDefinitionsFactory'; import { AssetTableNameLink } from './assetTableNameLink'; import { AssetTableHierarchyPath } from './assetTableHierarchyPath'; @@ -44,7 +50,13 @@ export function AssetTable({ resourceName: 'asset', }); - const { items, collectionProps, paginationProps, propertyFilterProps, actions } = useCollection(assets, { + const { + items, + collectionProps, + paginationProps, + propertyFilterProps, + actions, + } = useCollection(assets, { propertyFiltering: { filteringProperties: ASSET_TABLE_FILTERING_PROPERTIES, }, @@ -115,7 +127,11 @@ export function AssetTable({ totalItemCount={collectionProps.totalItemsCount ?? 0} /> )} - + } pagination={ @@ -126,11 +142,18 @@ export function AssetTable({ onNextPageClick={handleClickNextPage} /> } - preferences={} + preferences={ + + } ariaLabels={{ resizerRoleDescription: 'Resize button', itemSelectionLabel: (isNotSelected, asset) => - isNotSelected ? `Select asset ${asset.name}` : `Deselect asset ${asset.name}`, + isNotSelected + ? `Select asset ${asset.name}` + : `Deselect asset ${asset.name}`, }} /> ); diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableColumnDefinitionsFactory.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableColumnDefinitionsFactory.tsx index 6346e3a5b..bf8de815d 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableColumnDefinitionsFactory.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableColumnDefinitionsFactory.tsx @@ -5,7 +5,8 @@ import React from 'react'; import type { AssetTableNameLinkProps } from './assetTableNameLink'; import { getFormattedDateTime } from '~/components/util/dateTimeUtil'; -type AssetTableColumnDefinitions = TableProps['columnDefinitions']; +type AssetTableColumnDefinitions = + TableProps['columnDefinitions']; export class AssetTableColumnDefinitionsFactory { readonly #NameLink: React.ElementType; @@ -60,7 +61,11 @@ export class AssetTableColumnDefinitionsFactory { header: 'Name', cell: ({ name, id, hierarchies = [] }) => { return hierarchies.length > 0 ? ( - + ) : ( name ); @@ -81,7 +86,8 @@ export class AssetTableColumnDefinitionsFactory { return { id: 'creationDate', header: 'Creation Date', - cell: ({ creationDate }) => (creationDate ? getFormattedDateTime(creationDate) : '-'), + cell: ({ creationDate }) => + creationDate ? getFormattedDateTime(creationDate) : '-', sortingField: 'creationDate', }; } @@ -90,7 +96,8 @@ export class AssetTableColumnDefinitionsFactory { return { id: 'lastUpdateDate', header: 'Last Update Date', - cell: ({ lastUpdateDate }) => (lastUpdateDate ? getFormattedDateTime(lastUpdateDate) : '-'), + cell: ({ lastUpdateDate }) => + lastUpdateDate ? getFormattedDateTime(lastUpdateDate) : '-', sortingField: 'lastUpdateDate', }; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableHeader.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableHeader.tsx index 86b361dbf..71315dfe2 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableHeader.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableHeader.tsx @@ -6,12 +6,19 @@ interface AssetTableHeaderProps { selectedItemCount?: number; } -export function AssetTableHeader({ totalItemCount, selectedItemCount }: AssetTableHeaderProps) { +export function AssetTableHeader({ + totalItemCount, + selectedItemCount, +}: AssetTableHeaderProps) { return (
    Browse assets
    diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableHierarchyPath/assetTableHierarchyPath.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableHierarchyPath/assetTableHierarchyPath.tsx index d6d417706..3e2ab6d38 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableHierarchyPath/assetTableHierarchyPath.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableHierarchyPath/assetTableHierarchyPath.tsx @@ -17,8 +17,15 @@ export interface AssetTableHierarchyPathProps { * displays the asset's parents up to the root of the asset hierarchy and * features clickable navigation to the parent assets. */ -export function AssetTableHierarchyPath({ parentAssetId, onClickAssetName, client }: AssetTableHierarchyPathProps) { - const { hierarchyPathCrumbs } = useHierarchyCrumbs({ assetId: parentAssetId, client }); +export function AssetTableHierarchyPath({ + parentAssetId, + onClickAssetName, + client, +}: AssetTableHierarchyPathProps) { + const { hierarchyPathCrumbs } = useHierarchyCrumbs({ + assetId: parentAssetId, + client, + }); return ( >['assetSummaries']>> { + public async send(): Promise< + NonNullable< + Awaited>['assetSummaries'] + > + > { const parentAssets = await this.#recursivelyGetParentAssets(this.#assetId); return parentAssets ?? []; @@ -21,8 +33,12 @@ export class ListParentAssetsRequest { async #recursivelyGetParentAssets( assetId: string, - parentAssets: Awaited>['assetSummaries'] = [] - ): Promise>['assetSummaries']> { + parentAssets: Awaited< + ReturnType + >['assetSummaries'] = [] + ): Promise< + Awaited>['assetSummaries'] + > { const parentAsset = await this.#getParentAsset(assetId); // termination condition @@ -31,11 +47,18 @@ export class ListParentAssetsRequest { } // build up the list of parent assets recursively - return this.#recursivelyGetParentAssets(parentAsset.id, [parentAsset, ...parentAssets]); + return this.#recursivelyGetParentAssets(parentAsset.id, [ + parentAsset, + ...parentAssets, + ]); } async #getParentAsset(assetId: string) { - const request = new GetParentAssetRequest({ assetId, client: this.#client, signal: this.#signal }); + const request = new GetParentAssetRequest({ + assetId, + client: this.#client, + signal: this.#signal, + }); const { assetSummaries: [parentAsset] = [] } = await request.send(); return parentAsset; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableHierarchyPath/useHierarchyCrumbs.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableHierarchyPath/useHierarchyCrumbs.ts index a6b6d9039..fa39cc3e2 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableHierarchyPath/useHierarchyCrumbs.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableHierarchyPath/useHierarchyCrumbs.ts @@ -1,4 +1,7 @@ -import { type AssetSummary, type IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; +import { + type AssetSummary, + type IoTSiteWiseClient, +} from '@aws-sdk/client-iotsitewise'; import { useQuery, type QueryFunctionContext } from '@tanstack/react-query'; import invariant from 'tiny-invariant'; @@ -13,7 +16,10 @@ export interface UseHierarchyCrumbsProps { } /** Use the hierarchy path for an asset with a given asset ID. */ -export function useHierarchyCrumbs({ assetId, client }: UseHierarchyCrumbsProps) { +export function useHierarchyCrumbs({ + assetId, + client, +}: UseHierarchyCrumbsProps) { const { asset: { assetName = '' } = {} } = useAsset({ assetId, client }); const cacheKeyFactory = new HierarchyPathCacheKeyFactory(assetId); @@ -27,8 +33,11 @@ export function useHierarchyCrumbs({ assetId, client }: UseHierarchyCrumbsProps) }); const loadingCrumb = isFetching ? [{ href: '', text: 'Loading...' }] : []; - const ancestorCrumbs = !isFetching ? [{ href: '', text: 'Root' }, ...crumbs] : []; - const parentAssetCrumb = !isFetching && assetId ? [{ href: assetId, text: assetName }] : []; + const ancestorCrumbs = !isFetching + ? [{ href: '', text: 'Root' }, ...crumbs] + : []; + const parentAssetCrumb = + !isFetching && assetId ? [{ href: assetId, text: assetName }] : []; const hierarchyPathCrumbs: { href: string; text: string }[] = [ ...loadingCrumb, @@ -48,7 +57,10 @@ function createQueryFn(client: IoTSiteWiseClient) { queryKey: [{ assetId }], signal, }: QueryFunctionContext>) { - invariant(isEnabled(assetId), 'Expected assetId to be defined given the enabled condition.'); + invariant( + isEnabled(assetId), + 'Expected assetId to be defined given the enabled condition.' + ); const request = new ListParentAssetsRequest({ assetId, client, signal }); const response = await request.send(); @@ -57,8 +69,13 @@ function createQueryFn(client: IoTSiteWiseClient) { }; } -function transformAssetsToCrumbs(assets: AssetSummary[]): { href: string; text: string }[] { - const crumbs = assets.map(({ id = '', name = '' }) => ({ href: id, text: name })); +function transformAssetsToCrumbs( + assets: AssetSummary[] +): { href: string; text: string }[] { + const crumbs = assets.map(({ id = '', name = '' }) => ({ + href: id, + text: name, + })); return crumbs; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableNameLink.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableNameLink.tsx index e7fc16c4a..ce5a7f27e 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableNameLink.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTableNameLink.tsx @@ -7,7 +7,11 @@ export interface AssetTableNameLinkProps { updateParentAssetId: (assetId?: string) => void; } -export function AssetTableNameLink({ assetId, assetName, updateParentAssetId }: AssetTableNameLinkProps) { +export function AssetTableNameLink({ + assetId, + assetName, + updateParentAssetId, +}: AssetTableNameLinkProps) { return ( ({ }} pageSizePreference={{ title: 'Select page size', - options: SUPPORTED_PAGE_SIZES.map((size) => ({ value: size, label: size.toString() })), + options: SUPPORTED_PAGE_SIZES.map((size) => ({ + value: size, + label: size.toString(), + })), }} wrapLinesPreference={{ label: 'Wrap lines', @@ -57,7 +60,8 @@ export function AssetTablePreferences

    ({ stickyColumnsPreference={{ firstColumns: { title: 'Stick first column(s)', - description: 'Keep the first column(s) visible while horizontally scrolling the table content.', + description: + 'Keep the first column(s) visible while horizontally scrolling the table content.', options: [ { label: 'None', value: 0 }, { label: 'First column', value: 1 }, diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTablePropertyFilter.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTablePropertyFilter.tsx index efacb981b..60bae3645 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTablePropertyFilter.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/assetTable/assetTablePropertyFilter.tsx @@ -1,22 +1,28 @@ -import PropertyFilter, { type PropertyFilterProps } from '@cloudscape-design/components/property-filter'; +import PropertyFilter, { + type PropertyFilterProps, +} from '@cloudscape-design/components/property-filter'; import React from 'react'; -export const ASSET_TABLE_FILTERING_PROPERTIES: PropertyFilterProps['filteringProperties'] = [ - { - key: 'name', - propertyLabel: 'Name', - groupValuesLabel: 'Property names', - operators: ['=', '!=', ':', '!:'], - }, - { - key: 'description', - propertyLabel: 'Description', - groupValuesLabel: 'Property descriptions', - operators: ['=', '!=', ':', '!:'], - }, -]; +export const ASSET_TABLE_FILTERING_PROPERTIES: PropertyFilterProps['filteringProperties'] = + [ + { + key: 'name', + propertyLabel: 'Name', + groupValuesLabel: 'Property names', + operators: ['=', '!=', ':', '!:'], + }, + { + key: 'description', + propertyLabel: 'Description', + groupValuesLabel: 'Property descriptions', + operators: ['=', '!=', ':', '!:'], + }, + ]; -export type AssetTablePropertyFilterProps = Omit; +export type AssetTablePropertyFilterProps = Omit< + PropertyFilterProps, + 'i18nStrings' +>; export function AssetTablePropertyFilter(props: AssetTablePropertyFilterProps) { return ( @@ -54,7 +60,8 @@ export function AssetTablePropertyFilter(props: AssetTablePropertyFilterProps) { tokenLimitShowMore: 'Show more', tokenLimitShowFewer: 'Show fewer', clearFiltersText: 'Clear filters', - removeTokenButtonAriaLabel: (token) => `Remove token ${token.propertyKey} ${token.operator} ${token.value}`, + removeTokenButtonAriaLabel: (token) => + `Remove token ${token.propertyKey} ${token.operator} ${token.value}`, enteredTextLabel: (text) => `Use: "${text}"`, }} /> diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAsset/getAssetRequest.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAsset/getAssetRequest.ts index 7ed351d12..20f787873 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAsset/getAssetRequest.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAsset/getAssetRequest.ts @@ -1,11 +1,22 @@ -import { DescribeAssetCommand, type IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; +import { + DescribeAssetCommand, + type IoTSiteWiseClient, +} from '@aws-sdk/client-iotsitewise'; export class GetAssetRequest { readonly #command: DescribeAssetCommand; readonly #client: IoTSiteWiseClient; readonly #signal: AbortSignal | undefined; - constructor({ assetId, client, signal }: { assetId: string; client: IoTSiteWiseClient; signal?: AbortSignal }) { + constructor({ + assetId, + client, + signal, + }: { + assetId: string; + client: IoTSiteWiseClient; + signal?: AbortSignal; + }) { this.#command = this.#createCommand(assetId); this.#client = client; this.#signal = signal; @@ -13,7 +24,9 @@ export class GetAssetRequest { public async send() { try { - const response = await this.#client.send(this.#command, { abortSignal: this.#signal }); + const response = await this.#client.send(this.#command, { + abortSignal: this.#signal, + }); return response; } catch (error) { diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAsset/useAsset.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAsset/useAsset.ts index e65501942..96b3cbb2d 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAsset/useAsset.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAsset/useAsset.ts @@ -36,7 +36,10 @@ export function createQueryFn(client: IoTSiteWiseClient) { queryKey: [{ assetId }], signal, }: QueryFunctionContext>) { - invariant(isEnabled(assetId), 'Expected assetId to be defined as required by the enabled flag.'); + invariant( + isEnabled(assetId), + 'Expected assetId to be defined as required by the enabled flag.' + ); const request = new GetAssetRequest({ assetId, client, signal }); const response = await request.send(); diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/index.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/index.ts index cd69ffa8c..4e1287606 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/index.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/index.ts @@ -1 +1,4 @@ -export { useAssets, type UseAssetsOptions as UseAssetsProps } from './useAssets'; +export { + useAssets, + type UseAssetsOptions as UseAssetsProps, +} from './useAssets'; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useAssets.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useAssets.ts index 73f4c15f4..135ff8551 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useAssets.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useAssets.ts @@ -36,5 +36,13 @@ export function useAssets({ assetId, client }: UseAssetsOptions) { const fetchNextPage = assetId ? () => undefined : fetchNextPageRootAssets; const hasNextPage = assetId ? false : hasNextPageRootAssets; - return { assets, isError, isFetching, isLoading, isSuccess, fetchNextPage, hasNextPage }; + return { + assets, + isError, + isFetching, + isLoading, + isSuccess, + fetchNextPage, + hasNextPage, + }; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useChildAssets/childAssetCacheKeyFactory.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useChildAssets/childAssetCacheKeyFactory.ts index ca7fd1804..be4f68a9e 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useChildAssets/childAssetCacheKeyFactory.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useChildAssets/childAssetCacheKeyFactory.ts @@ -6,7 +6,9 @@ export class ChildAssetCacheKeyFactory { } create(hierarchyId: string) { - const cacheKey = [{ resource: 'child asset', assetId: this.#assetId, hierarchyId }] as const; + const cacheKey = [ + { resource: 'child asset', assetId: this.#assetId, hierarchyId }, + ] as const; return cacheKey; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useChildAssets/listChildAssetsRequest.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useChildAssets/listChildAssetsRequest.ts index 580df405d..3a8a42d29 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useChildAssets/listChildAssetsRequest.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useChildAssets/listChildAssetsRequest.ts @@ -1,4 +1,7 @@ -import { paginateListAssociatedAssets, type IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; +import { + paginateListAssociatedAssets, + type IoTSiteWiseClient, +} from '@aws-sdk/client-iotsitewise'; export class ListChildAssetsRequest { readonly #MAX_CHILD_ASSETS_PAGE_SIZE = 250; @@ -23,7 +26,11 @@ export class ListChildAssetsRequest { this.#hierarchyId = hierarchyId; this.#paginator = paginateListAssociatedAssets( { pageSize: this.#MAX_CHILD_ASSETS_PAGE_SIZE, client }, - { assetId, hierarchyId, traversalDirection: this.#CHILD_ASSETS_LIST_TRAVERSAL_DIRECTION } + { + assetId, + hierarchyId, + traversalDirection: this.#CHILD_ASSETS_LIST_TRAVERSAL_DIRECTION, + } ); this.#signal = signal; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useChildAssets/useChildAssets.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useChildAssets/useChildAssets.ts index 7cbcbdff3..254369a07 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useChildAssets/useChildAssets.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useChildAssets/useChildAssets.ts @@ -1,4 +1,7 @@ -import { IoTSiteWiseClient, type AssetSummary } from '@aws-sdk/client-iotsitewise'; +import { + IoTSiteWiseClient, + type AssetSummary, +} from '@aws-sdk/client-iotsitewise'; import { useQueries, type QueryFunctionContext } from '@tanstack/react-query'; import invariant from 'tiny-invariant'; @@ -13,7 +16,10 @@ export interface UseChildAssetsOptions { /** Use the list of child assets for an asset with a given asset ID. */ export function useChildAssets({ assetId, client }: UseChildAssetsOptions) { - const { asset: { assetHierarchies = [] } = {} } = useAsset({ assetId, client }); + const { asset: { assetHierarchies = [] } = {} } = useAsset({ + assetId, + client, + }); const hierarchyIds = selectHierarchyIds(assetHierarchies); const cacheKeyFactory = new ChildAssetCacheKeyFactory(assetId); @@ -36,7 +42,10 @@ export function useChildAssets({ assetId, client }: UseChildAssetsOptions) { return { childAssets, isError, isFetching, isLoading, isSuccess }; } -function isEnabled(assetId: string | undefined, hierarchyId: string | undefined) { +function isEnabled( + assetId: string | undefined, + hierarchyId: string | undefined +) { return isAssetId(assetId) && isHierarchyId(hierarchyId); } @@ -50,7 +59,9 @@ function isHierarchyId(hierarchyId: string | undefined): hierarchyId is string { /** List all of the IDs across hierarchies. */ function selectHierarchyIds(hierarchies: { id?: string }[]): string[] { - const hierarchiesWithIds: { id: string }[] = hierarchies.filter((h): h is { id: string } => Boolean(h?.id)); + const hierarchiesWithIds: { id: string }[] = hierarchies.filter( + (h): h is { id: string } => Boolean(h?.id) + ); const hierarchyIds: string[] = hierarchiesWithIds.map(({ id }) => id); return hierarchyIds; @@ -61,10 +72,21 @@ function createQueryFn(client: IoTSiteWiseClient) { queryKey: [{ assetId, hierarchyId }], signal, }: QueryFunctionContext>) { - invariant(isAssetId(assetId), 'Expected asset ID to be defined as required by the enabled flag.'); - invariant(isHierarchyId(hierarchyId), 'Expected hierarchy ID to be defined as required by the enabled flag.'); + invariant( + isAssetId(assetId), + 'Expected asset ID to be defined as required by the enabled flag.' + ); + invariant( + isHierarchyId(hierarchyId), + 'Expected hierarchy ID to be defined as required by the enabled flag.' + ); - const request = new ListChildAssetsRequest({ assetId, hierarchyId, client, signal }); + const request = new ListChildAssetsRequest({ + assetId, + hierarchyId, + client, + signal, + }); const response = await request.send(); return response; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useRootAssets/listRootAssetsRequest.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useRootAssets/listRootAssetsRequest.ts index 7e5e06a23..59d095b64 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useRootAssets/listRootAssetsRequest.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useRootAssets/listRootAssetsRequest.ts @@ -1,11 +1,22 @@ -import { ListAssetsCommand, type IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; +import { + ListAssetsCommand, + type IoTSiteWiseClient, +} from '@aws-sdk/client-iotsitewise'; export class ListRootAssetsRequest { readonly #command: ListAssetsCommand; readonly #client: IoTSiteWiseClient; readonly #signal: AbortSignal | undefined; - constructor({ nextToken, client, signal }: { nextToken?: string; client: IoTSiteWiseClient; signal?: AbortSignal }) { + constructor({ + nextToken, + client, + signal, + }: { + nextToken?: string; + client: IoTSiteWiseClient; + signal?: AbortSignal; + }) { this.#command = this.#createCommand(nextToken); this.#client = client; this.#signal = signal; @@ -13,7 +24,9 @@ export class ListRootAssetsRequest { public async send() { try { - const response = await this.#client.send(this.#command, { abortSignal: this.#signal }); + const response = await this.#client.send(this.#command, { + abortSignal: this.#signal, + }); return response; } catch (error) { diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useRootAssets/useRootAssets.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useRootAssets/useRootAssets.ts index 1bec77a46..a23aea8a3 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useRootAssets/useRootAssets.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/assetExplorer/useAssets/useRootAssets/useRootAssets.ts @@ -1,5 +1,11 @@ -import { type AssetSummary, type IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; -import { useInfiniteQuery, type QueryFunctionContext } from '@tanstack/react-query'; +import { + type AssetSummary, + type IoTSiteWiseClient, +} from '@aws-sdk/client-iotsitewise'; +import { + useInfiniteQuery, + type QueryFunctionContext, +} from '@tanstack/react-query'; import { ListRootAssetsRequest } from './listRootAssetsRequest'; import { RootAssetCacheKeyFactory } from './rootAssetCacheKeyFactory'; @@ -28,9 +34,21 @@ export function useRootAssets({ client }: UseRootAssetsOptions) { getNextPageParam: ({ nextToken }) => nextToken, }); - const rootAssets: AssetSummary[] = rootAssetPages.flatMap(({ assetSummaries = [] }) => assetSummaries); + const rootAssets: AssetSummary[] = rootAssetPages.flatMap( + ({ assetSummaries = [] }) => assetSummaries + ); - return { rootAssets, hasNextPage, fetchNextPage, isFetching, isSuccess, status, isError, isLoading, error }; + return { + rootAssets, + hasNextPage, + fetchNextPage, + isFetching, + isSuccess, + status, + isError, + isLoading, + error, + }; } function createUseListRootAssetsQueryFn(client: IoTSiteWiseClient) { diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/browseSearchToggle.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/browseSearchToggle.tsx index c05db0da6..dbfc8847e 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/browseSearchToggle.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/browseSearchToggle.tsx @@ -11,11 +11,16 @@ export interface BrowseSearchToggleProps { onChange: (selectedSegment: SegmentId) => void; } -export function BrowseSearchToggle({ selectedSegment, onChange }: BrowseSearchToggleProps) { +export function BrowseSearchToggle({ + selectedSegment, + onChange, +}: BrowseSearchToggleProps) { return ( onChange(selectedSegment as SegmentId)} + onChange={({ detail: { selectedId: selectedSegment } }) => + onChange(selectedSegment as SegmentId) + } options={[ { text: 'Browse', id: BROWSE_SEGMENT_ID }, { text: 'Search', id: SEARCH_SEGMENT_ID }, @@ -26,7 +31,8 @@ export function BrowseSearchToggle({ selectedSegment, onChange }: BrowseSearchTo export function useBrowseSearchToggle() { const DEFAULT_SEGMENT_ID = BROWSE_SEGMENT_ID; - const [selectedSegment, setSelectedSegment] = useState(DEFAULT_SEGMENT_ID); + const [selectedSegment, setSelectedSegment] = + useState(DEFAULT_SEGMENT_ID); return { selectedSegment, onChangeSegment: setSelectedSegment }; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/index.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/index.ts index 3d9c9eb95..3afa421fa 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/index.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/index.ts @@ -1 +1,4 @@ -export { ModeledDataStreamQueryEditor, type ModeledDataStreamQueryEditorProps } from './modeledDataStreamQueryEditor'; +export { + ModeledDataStreamQueryEditor, + type ModeledDataStreamQueryEditorProps, +} from './modeledDataStreamQueryEditor'; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/index.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/index.ts index 3d33b8aa9..925dab473 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/index.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/index.ts @@ -1 +1,4 @@ -export { ModeledDataStreamExplorer, type ModeledDataStreamExplorerProps } from './modeledDataStreamExplorer'; +export { + ModeledDataStreamExplorer, + type ModeledDataStreamExplorerProps, +} from './modeledDataStreamExplorer'; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamExplorer.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamExplorer.tsx index d06619cc1..7f024d27c 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamExplorer.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamExplorer.tsx @@ -7,7 +7,9 @@ import type { ModeledDataStream } from './types'; import { SelectedAsset } from '../types'; export interface ModeledDataStreamExplorerProps { - onClickAddModeledDataStreams: (modeledDataStreams: ModeledDataStream[]) => void; + onClickAddModeledDataStreams: ( + modeledDataStreams: ModeledDataStream[] + ) => void; onClickNextPage?: () => void; selectedAsset?: SelectedAsset; dataStreams?: ModeledDataStream[]; @@ -37,7 +39,9 @@ export function ModeledDataStreamExplorer({ assetProps: selectedAsset ? [selectedAsset] : [], client, }); - const modeledDataStreamsTitle = isSearchError ? `Search result for "${searchQuery}" (0)` : 'Modeled data streams (0)'; + const modeledDataStreamsTitle = isSearchError + ? `Search result for "${searchQuery}" (0)` + : 'Modeled data streams (0)'; return ( ['columnDefinitions'] { return [ { @@ -31,7 +34,9 @@ export function createModeledDataStreamColumnDefinitions( header: 'Latest value time', cell: ({ latestValueTime }) => { if (latestValueTime && isNumeric(latestValueTime)) { - return getFormattedDateTimeFromEpoch(round(latestValueTime, significantDigits)); + return getFormattedDateTimeFromEpoch( + round(latestValueTime, significantDigits) + ); } return getFormattedDateTimeFromEpoch(latestValueTime); }, @@ -65,5 +70,8 @@ export function createModeledDataStreamColumnDefinitions( } export const modeledDataStreamExplorerColumnDefinitions: TableProps< - ModeledDataStream & { latestValue?: number | string | boolean; latestValueTime: number } + ModeledDataStream & { + latestValue?: number | string | boolean; + latestValueTime: number; + } >['columnDefinitions'] = []; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/index.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/index.ts index 97074faae..f86fe95b0 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/index.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/index.ts @@ -1 +1,4 @@ -export { ModeledDataStreamTable, type ModeledDataStreamTableProps } from './modeledDataStreamTable'; +export { + ModeledDataStreamTable, + type ModeledDataStreamTableProps, +} from './modeledDataStreamTable'; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTable.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTable.tsx index 982f97ba4..f1e9574ce 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTable.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTable.tsx @@ -25,7 +25,9 @@ import { getPlugin } from '@iot-app-kit/core'; import { isInValidProperty } from './util/resourceExplorerTableLabels'; export interface ModeledDataStreamTableProps { - onClickAddModeledDataStreams: (modeledDataStreams: ModeledDataStream[]) => void; + onClickAddModeledDataStreams: ( + modeledDataStreams: ModeledDataStream[] + ) => void; onClickNextPage?: () => void; selectedAsset?: SelectedAsset; modeledDataStreams: ModeledDataStream[]; @@ -48,8 +50,12 @@ export function ModeledDataStreamTable({ modeledDataStreamsTitle, }: ModeledDataStreamTableProps) { const metricsRecorder = getPlugin('metricsRecorder'); - const significantDigits = useSelector((state: DashboardState) => state.significantDigits); - const selectedWidgets = useSelector((state: DashboardState) => state.selectedWidgets); + const significantDigits = useSelector( + (state: DashboardState) => state.significantDigits + ); + const selectedWidgets = useSelector( + (state: DashboardState) => state.selectedWidgets + ); const [preferences, setPreferences] = useExplorerPreferences({ defaultVisibleContent: ['name', 'latestValue'], @@ -58,7 +64,8 @@ export function ModeledDataStreamTable({ const { getLatestValue } = useLatestValues({ isEnabled: - preferences.visibleContent.includes('latestValue') || preferences.visibleContent.includes('latestValueTime'), + preferences.visibleContent.includes('latestValue') || + preferences.visibleContent.includes('latestValueTime'), dataStreams: modeledDataStreams, client, }); @@ -66,22 +73,30 @@ export function ModeledDataStreamTable({ const modeledDataStreamsWithLatestValues = modeledDataStreams?.map((item) => ({ ...item, - latestValue: getLatestValue({ assetId: item.assetId, propertyId: item.propertyId } as ModeledDataStream)?.value, - latestValueTime: getLatestValue({ assetId: item.assetId, propertyId: item.propertyId } as ModeledDataStream) - ?.timestamp, + latestValue: getLatestValue({ + assetId: item.assetId, + propertyId: item.propertyId, + } as ModeledDataStream)?.value, + latestValueTime: getLatestValue({ + assetId: item.assetId, + propertyId: item.propertyId, + } as ModeledDataStream)?.timestamp, })) ?? []; - const { items, collectionProps, paginationProps, propertyFilterProps, actions } = useCollection( - modeledDataStreamsWithLatestValues, - { - propertyFiltering: { - filteringProperties: MODELED_DATA_STREAM_TABLE_FILTERING_PROPERTIES, - }, - pagination: { pageSize: preferences.pageSize }, - selection: { keepSelection: true, trackBy: 'name' }, - sorting: {}, - } - ); + const { + items, + collectionProps, + paginationProps, + propertyFilterProps, + actions, + } = useCollection(modeledDataStreamsWithLatestValues, { + propertyFiltering: { + filteringProperties: MODELED_DATA_STREAM_TABLE_FILTERING_PROPERTIES, + }, + pagination: { pageSize: preferences.pageSize }, + selection: { keepSelection: true, trackBy: 'name' }, + sorting: {}, + }); /** * Reset selected items if the user changes the asset @@ -114,10 +129,20 @@ export function ModeledDataStreamTable({ }, }; - const propertySelectionLabel = (selectedItems: ModeledDataStream[], modeledDataStream: ModeledDataStream) => { - const isPropertySelected = selectedItems?.find((item) => item.propertyId === modeledDataStream.propertyId); + const propertySelectionLabel = ( + selectedItems: ModeledDataStream[], + modeledDataStream: ModeledDataStream + ) => { + const isPropertySelected = selectedItems?.find( + (item) => item.propertyId === modeledDataStream.propertyId + ); - if (isInValidProperty(modeledDataStream.dataType, selectedWidgets?.at(0)?.type)) { + if ( + isInValidProperty( + modeledDataStream.dataType, + selectedWidgets?.at(0)?.type + ) + ) { return `${modeledDataStream.dataType} data not supported for the selected widget`; } else if (!isPropertySelected) { return `Select modeled data stream ${modeledDataStream.name}`; @@ -134,7 +159,9 @@ export function ModeledDataStreamTable({

    `${item.assetId}---${item.propertyId}`} variant='embedded' loading={isLoading} @@ -145,9 +172,19 @@ export function ModeledDataStreamTable({ stripedRows={preferences.stripedRows} wrapLines={preferences.wrapLines} stickyColumns={preferences.stickyColumns} - isItemDisabled={(item) => isInValidProperty(item.dataType, selectedWidgets?.at(0)?.type)} - empty={} - filter={modeledDataStreams.length > 0 && } + isItemDisabled={(item) => + isInValidProperty(item.dataType, selectedWidgets?.at(0)?.type) + } + empty={ + + } + filter={ + modeledDataStreams.length > 0 && ( + + ) + } header={ actions.setSelectedItems([])} - addDisabled={collectionProps.selectedItems?.length === 0 || selectedWidgets.length !== 1} + addDisabled={ + collectionProps.selectedItems?.length === 0 || + selectedWidgets.length !== 1 + } onAdd={() => { - onClickAddModeledDataStreams(collectionProps.selectedItems as unknown as ModeledDataStream[]); + onClickAddModeledDataStreams( + collectionProps.selectedItems as unknown as ModeledDataStream[] + ); metricsRecorder?.record({ metricName: 'ModeledDataStreamAdd', metricValue: 1, @@ -169,15 +211,25 @@ export function ModeledDataStreamTable({ /> } pagination={ - + + } + preferences={ + } - preferences={} ariaLabels={{ itemSelectionLabel: ({ selectedItems }, modeledDataStream) => propertySelectionLabel([...selectedItems], modeledDataStream), resizerRoleDescription: 'Resize button', allItemsSelectionLabel: ({ selectedItems }) => - selectedItems.length !== items.length ? 'Select modeled data stream' : 'Deselect modeled data stream', + selectedItems.length !== items.length + ? 'Select modeled data stream' + : 'Deselect modeled data stream', }} /> ); diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTableEmptyState.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTableEmptyState.tsx index 0fe3e85cb..2096b045b 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTableEmptyState.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTableEmptyState.tsx @@ -5,7 +5,9 @@ interface ModeledDataStreamTableEmptyStateProps { isAssetSelected: boolean; } -export function ModeledDataStreamTableEmptyState({ isAssetSelected }: ModeledDataStreamTableEmptyStateProps) { +export function ModeledDataStreamTableEmptyState({ + isAssetSelected, +}: ModeledDataStreamTableEmptyStateProps) { return ( No modeled data streams diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTableHeader.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTableHeader.tsx index 02219bff4..c197b3463 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTableHeader.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTableHeader.tsx @@ -6,12 +6,19 @@ interface ModeledDataStreamTableHeaderProps { selectedItemCount?: number; } -export function ModeledDataStreamTableHeader({ totalItemCount, selectedItemCount }: ModeledDataStreamTableHeaderProps) { +export function ModeledDataStreamTableHeader({ + totalItemCount, + selectedItemCount, +}: ModeledDataStreamTableHeaderProps) { return (
    Modeled data streams
    diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTablePagination.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTablePagination.tsx index 029b0a852..874ac6048 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTablePagination.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTablePagination.tsx @@ -1,8 +1,12 @@ -import Pagination, { type PaginationProps } from '@cloudscape-design/components/pagination'; +import Pagination, { + type PaginationProps, +} from '@cloudscape-design/components/pagination'; import React from 'react'; type ModeledDataStreamTablePaginationProps = PaginationProps; -export function ModeledDataStreamTablePagination({ ...props }: ModeledDataStreamTablePaginationProps) { +export function ModeledDataStreamTablePagination({ + ...props +}: ModeledDataStreamTablePaginationProps) { return ; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTablePreferences.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTablePreferences.tsx index 48e9a7460..0b0ccaf29 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTablePreferences.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTablePreferences.tsx @@ -28,7 +28,10 @@ export function ModeledDataStreamTablePreferences

    ({ }} pageSizePreference={{ title: 'Select page size', - options: SUPPORTED_PAGE_SIZES.map((size) => ({ value: size, label: size.toString() })), + options: SUPPORTED_PAGE_SIZES.map((size) => ({ + value: size, + label: size.toString(), + })), }} wrapLinesPreference={{ label: 'Wrap lines', diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTablePropertyFilter.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTablePropertyFilter.tsx index 69d30bbe7..4198b2df8 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTablePropertyFilter.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/modeledDataStreamTablePropertyFilter.tsx @@ -1,48 +1,56 @@ -import PropertyFilter, { type PropertyFilterProps } from '@cloudscape-design/components/property-filter'; +import PropertyFilter, { + type PropertyFilterProps, +} from '@cloudscape-design/components/property-filter'; import React from 'react'; -export const MODELED_DATA_STREAM_TABLE_FILTERING_PROPERTIES: PropertyFilterProps['filteringProperties'] = [ - { - key: 'name', - propertyLabel: 'Name', - groupValuesLabel: 'Property names', - operators: ['=', '!=', ':', '!:'], - }, - { - key: 'assetName', - propertyLabel: 'Asset name', - groupValuesLabel: 'Asset names', - operators: ['=', '!=', ':', '!:'], - }, - { - key: 'dataType', - propertyLabel: 'Data type', - groupValuesLabel: 'Data types', - operators: ['=', '!=', ':', '!:'], - }, - { - key: 'dataTypeSpec', - propertyLabel: 'Data type spec', - groupValuesLabel: 'Data type specs', - operators: ['=', '!=', ':', '!:'], - }, - { - key: 'unit', - propertyLabel: 'Unit', - groupValuesLabel: 'Units', - operators: ['=', '!=', ':', '!:'], - }, - { - key: 'latestValue', - propertyLabel: 'Latest value', - groupValuesLabel: 'Latest values', - operators: ['=', '!=', '>', '>=', '<', '<='], - }, -]; +export const MODELED_DATA_STREAM_TABLE_FILTERING_PROPERTIES: PropertyFilterProps['filteringProperties'] = + [ + { + key: 'name', + propertyLabel: 'Name', + groupValuesLabel: 'Property names', + operators: ['=', '!=', ':', '!:'], + }, + { + key: 'assetName', + propertyLabel: 'Asset name', + groupValuesLabel: 'Asset names', + operators: ['=', '!=', ':', '!:'], + }, + { + key: 'dataType', + propertyLabel: 'Data type', + groupValuesLabel: 'Data types', + operators: ['=', '!=', ':', '!:'], + }, + { + key: 'dataTypeSpec', + propertyLabel: 'Data type spec', + groupValuesLabel: 'Data type specs', + operators: ['=', '!=', ':', '!:'], + }, + { + key: 'unit', + propertyLabel: 'Unit', + groupValuesLabel: 'Units', + operators: ['=', '!=', ':', '!:'], + }, + { + key: 'latestValue', + propertyLabel: 'Latest value', + groupValuesLabel: 'Latest values', + operators: ['=', '!=', '>', '>=', '<', '<='], + }, + ]; -type ModeledDataStreamPropertyFilterProps = Omit; +type ModeledDataStreamPropertyFilterProps = Omit< + PropertyFilterProps, + 'i18nStrings' +>; -export function ModeledDataStreamTablePropertyFilter(props: ModeledDataStreamPropertyFilterProps) { +export function ModeledDataStreamTablePropertyFilter( + props: ModeledDataStreamPropertyFilterProps +) { return ( <> Filter by: @@ -54,9 +62,11 @@ export function ModeledDataStreamTablePropertyFilter(props: ModeledDataStreamPro filteringFinishedText='End of results' filteringEmpty='No suggestions found' i18nStrings={{ - filteringAriaLabel: 'Filter modeled data streams by text, property, or value', + filteringAriaLabel: + 'Filter modeled data streams by text, property, or value', dismissAriaLabel: 'Dismiss', - filteringPlaceholder: 'Filter modeled data streams by text, property, or value', + filteringPlaceholder: + 'Filter modeled data streams by text, property, or value', groupValuesText: 'Values', groupPropertiesText: 'Properties', operatorsText: 'Operators', @@ -80,7 +90,8 @@ export function ModeledDataStreamTablePropertyFilter(props: ModeledDataStreamPro tokenLimitShowMore: 'Show more', tokenLimitShowFewer: 'Show fewer', clearFiltersText: 'Clear filters', - removeTokenButtonAriaLabel: (token) => `Remove token ${token.propertyKey} ${token.operator} ${token.value}`, + removeTokenButtonAriaLabel: (token) => + `Remove token ${token.propertyKey} ${token.operator} ${token.value}`, enteredTextLabel: (text) => `Use: "${text}"`, }} /> diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/util/resourceExplorerTableLabels.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/util/resourceExplorerTableLabels.tsx index c274389d8..f01448145 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/util/resourceExplorerTableLabels.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/modeledDataStreamTable/util/resourceExplorerTableLabels.tsx @@ -1,6 +1,9 @@ import { ModeledDataStream } from '../../types'; -export const isInValidProperty = (dataType: ModeledDataStream['dataType'], widgetType?: string) => { +export const isInValidProperty = ( + dataType: ModeledDataStream['dataType'], + widgetType?: string +) => { if (!widgetType || !dataType) return false; const isNumericDataType = dataType === 'DOUBLE' || dataType === 'INTEGER'; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/types.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/types.ts index 7baf9ba29..baa1fb707 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/types.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/types.ts @@ -1,6 +1,8 @@ import type { DescribeAssetCommandOutput } from '@aws-sdk/client-iotsitewise'; -type AssetProperty = NonNullable[number]; +type AssetProperty = NonNullable< + DescribeAssetCommandOutput['assetProperties'] +>[number]; export type ModeledDataStream = { assetId: NonNullable; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/useModeledDataStreams/listModeledDataStreamsRequest.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/useModeledDataStreams/listModeledDataStreamsRequest.ts index 17551f5df..90465ac84 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/useModeledDataStreams/listModeledDataStreamsRequest.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/useModeledDataStreams/listModeledDataStreamsRequest.ts @@ -1,4 +1,8 @@ -import { DescribeAssetCommand, IoTSiteWiseClient, type DescribeAssetCommandOutput } from '@aws-sdk/client-iotsitewise'; +import { + DescribeAssetCommand, + IoTSiteWiseClient, + type DescribeAssetCommandOutput, +} from '@aws-sdk/client-iotsitewise'; import type { ModeledDataStream } from '../types'; import { createNonNullableList } from '~/helpers/lists/createNonNullableList'; import { DEFAULT_STRING } from '~/components/queryEditor/contants'; @@ -8,7 +12,15 @@ export class ListModeledDataStreamsRequest { readonly #client: IoTSiteWiseClient; readonly #signal: AbortSignal | undefined; - constructor({ assetId, client, signal }: { assetId: string; client: IoTSiteWiseClient; signal?: AbortSignal }) { + constructor({ + assetId, + client, + signal, + }: { + assetId: string; + client: IoTSiteWiseClient; + signal?: AbortSignal; + }) { this.#command = this.#createCommand(assetId); this.#client = client; this.#signal = signal; @@ -53,31 +65,36 @@ export class ListModeledDataStreamsRequest { assetId: NonNullable; assetName: NonNullable; assetProperties: NonNullable; - assetCompositeModels: NonNullable; + assetCompositeModels: NonNullable< + DescribeAssetCommandOutput['assetCompositeModels'] + >; }): ModeledDataStream[] { // there may be multiple composite models with their own properties lists - const compositeProperties = assetCompositeModels.flatMap(({ properties }) => properties); + const compositeProperties = assetCompositeModels.flatMap( + ({ properties }) => properties + ); const allProperties = [...assetProperties, ...compositeProperties]; const nonNullableProperties = createNonNullableList(allProperties); // we add the assetId and assetName to provide consumers of the data with additional context - const allPropertiesWithAssetDetail = nonNullableProperties.map( - ({ - id: propertyId = DEFAULT_STRING, - name = DEFAULT_STRING, - unit = DEFAULT_STRING, - dataType = undefined as NonNullable, - dataTypeSpec = DEFAULT_STRING, - }) => ({ - assetId, - assetName, - propertyId, - name, - unit, - dataType, - dataTypeSpec, - }) - ); + const allPropertiesWithAssetDetail = + nonNullableProperties.map( + ({ + id: propertyId = DEFAULT_STRING, + name = DEFAULT_STRING, + unit = DEFAULT_STRING, + dataType = undefined as NonNullable, + dataTypeSpec = DEFAULT_STRING, + }) => ({ + assetId, + assetName, + propertyId, + name, + unit, + dataType, + dataTypeSpec, + }) + ); return allPropertiesWithAssetDetail; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/useModeledDataStreams/listModeledDataStreamsRequestWithCompositeModels.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/useModeledDataStreams/listModeledDataStreamsRequestWithCompositeModels.ts index e2e03782f..a5ea4c780 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/useModeledDataStreams/listModeledDataStreamsRequestWithCompositeModels.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/useModeledDataStreams/listModeledDataStreamsRequestWithCompositeModels.ts @@ -17,8 +17,12 @@ import { Paginator } from '@aws-sdk/types'; import { type SelectedAsset } from '../../types'; export class listModeledDataStreamsRequestWithCompositeModels { - readonly #listAssetPropertyPaginator: Paginator; - readonly #listAssetModelPropertyPaginator: Paginator; + readonly #listAssetPropertyPaginator: Paginator< + ListAssetPropertiesCommandOutput | undefined + >; + readonly #listAssetModelPropertyPaginator: Paginator< + ListAssetModelPropertiesCommandOutput | undefined + >; #signal: AbortSignal | undefined; constructor({ @@ -34,10 +38,11 @@ export class listModeledDataStreamsRequestWithCompositeModels { { client }, { assetId: selectedAsset.assetId, filter: 'ALL' } ); - this.#listAssetModelPropertyPaginator = this.#createAssetModelPropertyPaginator( - { client }, - { assetModelId: selectedAsset.assetModelId, filter: 'ALL' } - ); + this.#listAssetModelPropertyPaginator = + this.#createAssetModelPropertyPaginator( + { client }, + { assetModelId: selectedAsset.assetModelId, filter: 'ALL' } + ); this.#signal = signal; } @@ -54,12 +59,15 @@ export class listModeledDataStreamsRequestWithCompositeModels { } //get all assetModelProperties - const assetModelPropertiesMap: { [x: string]: AssetModelPropertySummary } = {}; + const assetModelPropertiesMap: { + [x: string]: AssetModelPropertySummary; + } = {}; for await (const result of this.#listAssetModelPropertyPaginator) { if (this.#signal?.aborted) { break; } - const assetModelPropertySummaries = result?.assetModelPropertySummaries ?? []; + const assetModelPropertySummaries = + result?.assetModelPropertySummaries ?? []; assetModelPropertySummaries.forEach((modelSummary) => { if (modelSummary.id) { assetModelPropertiesMap[modelSummary.id] = modelSummary; @@ -67,7 +75,10 @@ export class listModeledDataStreamsRequestWithCompositeModels { }); } - const modeledDataStreams = this.#formatDataStreams({ assetProperties, assetModelPropertiesMap }); + const modeledDataStreams = this.#formatDataStreams({ + assetProperties, + assetModelPropertiesMap, + }); return modeledDataStreams; } catch (error) { this.#handleError(error); @@ -78,7 +89,10 @@ export class listModeledDataStreamsRequestWithCompositeModels { paginatorConfig: IoTSiteWisePaginationConfiguration, commandParams: ListAssetPropertiesCommandInput ) { - const paginator = paginateListAssetProperties(paginatorConfig, commandParams); + const paginator = paginateListAssetProperties( + paginatorConfig, + commandParams + ); return paginator; } @@ -86,7 +100,10 @@ export class listModeledDataStreamsRequestWithCompositeModels { paginatorConfig: IoTSiteWisePaginationConfiguration, commandParams: ListAssetModelPropertiesCommandInput ) { - const paginator = paginateListAssetModelProperties(paginatorConfig, commandParams); + const paginator = paginateListAssetModelProperties( + paginatorConfig, + commandParams + ); return paginator; } @@ -97,33 +114,35 @@ export class listModeledDataStreamsRequestWithCompositeModels { assetProperties: AssetPropertySummary[]; assetModelPropertiesMap: { [x: string]: AssetModelPropertySummary }; }): ModeledDataStream[] { - const allProperties: (AssetPropertySummary & AssetModelPropertySummary)[] = assetProperties.map((assetSummary) => { - const modelSummary = assetModelPropertiesMap[assetSummary.id ?? '']; - return { - ...modelSummary, - ...assetSummary, // this goes second so the type property is overwritten correctly - }; - }); + const allProperties: (AssetPropertySummary & AssetModelPropertySummary)[] = + assetProperties.map((assetSummary) => { + const modelSummary = assetModelPropertiesMap[assetSummary.id ?? '']; + return { + ...modelSummary, + ...assetSummary, // this goes second so the type property is overwritten correctly + }; + }); const nonNullableProperties = createNonNullableList(allProperties); - const allPropertiesWithAssetDetail = nonNullableProperties.map( - ({ - path, - id: propertyId = DEFAULT_STRING, - name = DEFAULT_STRING, - unit = DEFAULT_STRING, - dataType = undefined as NonNullable, - dataTypeSpec = DEFAULT_STRING, - }) => ({ - assetId: path?.at(0)?.id ?? '-', - assetName: path?.at(0)?.name ?? '-', - propertyId, - name, - unit, - dataType, - dataTypeSpec, - }) - ); + const allPropertiesWithAssetDetail = + nonNullableProperties.map( + ({ + path, + id: propertyId = DEFAULT_STRING, + name = DEFAULT_STRING, + unit = DEFAULT_STRING, + dataType = undefined as NonNullable, + dataTypeSpec = DEFAULT_STRING, + }) => ({ + assetId: path?.at(0)?.id ?? '-', + assetName: path?.at(0)?.name ?? '-', + propertyId, + name, + unit, + dataType, + dataTypeSpec, + }) + ); return allPropertiesWithAssetDetail; } @@ -131,7 +150,10 @@ export class listModeledDataStreamsRequestWithCompositeModels { #handleError(error: unknown): never { console.error(`Failed to get asset description. Error: ${error}`); console.info('Request input:'); - console.table(this.#createAssetModelPropertyPaginator.arguments, this.#createAssetPropertyPaginator.arguments); + console.table( + this.#createAssetModelPropertyPaginator.arguments, + this.#createAssetPropertyPaginator.arguments + ); throw error; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/useModeledDataStreams/modeledDataStreamCacheKeyFactory.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/useModeledDataStreams/modeledDataStreamCacheKeyFactory.ts index 56d1260be..5fc96eb0a 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/useModeledDataStreams/modeledDataStreamCacheKeyFactory.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/useModeledDataStreams/modeledDataStreamCacheKeyFactory.ts @@ -2,7 +2,9 @@ import { SelectedAsset } from '../../types'; export class ModeledDataStreamCacheKeyFactory { public create(selectedAsset: SelectedAsset) { - const cacheKey = [{ resource: 'modeled data stream', selectedAsset }] as const; + const cacheKey = [ + { resource: 'modeled data stream', selectedAsset }, + ] as const; return cacheKey; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/useModeledDataStreams/useModeledDataStreams.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/useModeledDataStreams/useModeledDataStreams.ts index 25ac88654..026d697a4 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/useModeledDataStreams/useModeledDataStreams.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamExplorer/useModeledDataStreams/useModeledDataStreams.ts @@ -12,7 +12,10 @@ export interface UseModeledDataStreamsProps { client: IoTSiteWiseClient; } -export function useModeledDataStreams({ assetProps, client }: UseModeledDataStreamsProps) { +export function useModeledDataStreams({ + assetProps, + client, +}: UseModeledDataStreamsProps) { const cacheKeyFactory = new ModeledDataStreamCacheKeyFactory(); const queries = useQueries({ @@ -34,9 +37,18 @@ function createCompositeQueryFn(client: IoTSiteWiseClient) { return async function ({ queryKey: [{ selectedAsset }], signal, - }: QueryFunctionContext>) { - invariant(selectedAsset, 'Expected assetProp to be defined as required by the enabled flag.'); - const request = new listModeledDataStreamsRequestWithCompositeModels({ selectedAsset, client, signal }); + }: QueryFunctionContext< + ReturnType + >) { + invariant( + selectedAsset, + 'Expected assetProp to be defined as required by the enabled flag.' + ); + const request = new listModeledDataStreamsRequestWithCompositeModels({ + selectedAsset, + client, + signal, + }); const response = await request.send(); return response; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamQueryEditor.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamQueryEditor.tsx index f901d002d..567d129da 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamQueryEditor.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/modeledDataStreamQueryEditor/modeledDataStreamQueryEditor.tsx @@ -6,7 +6,11 @@ import { ModeledDataStreamExplorer } from './modeledDataStreamExplorer'; import { AssetSummary, IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; import { ModeledDataStream } from './modeledDataStreamExplorer/types'; import { IoTTwinMakerClient } from '@aws-sdk/client-iottwinmaker'; -import { BROWSE_SEGMENT_ID, BrowseSearchToggle, useBrowseSearchToggle } from './browseSearchToggle'; +import { + BROWSE_SEGMENT_ID, + BrowseSearchToggle, + useBrowseSearchToggle, +} from './browseSearchToggle'; import { useSearch } from '../dataStreamSearch/useSearch'; import { SearchFields } from '../dataStreamSearch/types'; import { DataStreamSearch } from '../dataStreamSearch'; @@ -22,7 +26,9 @@ export function ModeledDataStreamQueryEditor({ iotSiteWiseClient, iotTwinMakerClient, }: ModeledDataStreamQueryEditorProps) { - const [selectedAsset, setSelectedAsset] = useState(undefined); + const [selectedAsset, setSelectedAsset] = useState( + undefined + ); function handleOnSelectAsset(asset?: AssetSummary) { setSelectedAsset(asset); } @@ -35,11 +41,18 @@ export function ModeledDataStreamQueryEditor({ }); const workspaceId = - searchFieldValues.workspace != null && 'value' in searchFieldValues.workspace + searchFieldValues.workspace != null && + 'value' in searchFieldValues.workspace ? searchFieldValues.workspace.value : undefined; - const { modeledDataStreams, hasNextPage, isFetching, fetchNextPage, isError } = useSearch({ + const { + modeledDataStreams, + hasNextPage, + isFetching, + fetchNextPage, + isError, + } = useSearch({ workspaceId: workspaceId ?? '', client: iotTwinMakerClient, searchQuery: searchFieldValues.searchQuery, @@ -47,11 +60,17 @@ export function ModeledDataStreamQueryEditor({ return ( - + {selectedSegment === BROWSE_SEGMENT_ID ? ( <> - +
    {selectedAsset != null && ( @@ -67,7 +89,10 @@ export function ModeledDataStreamQueryEditor({ ) : ( <> - + {searchFieldValues.searchQuery.length > 0 && ( { }, ] as ModeledDataStream[]; - const extendedQuery = queryExtender.extendAssetQueries(modeledDataStreams); + const extendedQuery = + queryExtender.extendAssetQueries(modeledDataStreams); expect(extendedQuery).toEqual({ assets: [ @@ -60,13 +61,17 @@ describe(QueryExtender.name, () => { }, ] as ModeledDataStream[]; - const extendedQuery = queryExtender.extendAssetQueries(modeledDataStreams); + const extendedQuery = + queryExtender.extendAssetQueries(modeledDataStreams); expect(extendedQuery).toEqual({ assets: [ { assetId: 'asset-1', - properties: [{ propertyId: 'property-1' }, { propertyId: 'property-2' }], + properties: [ + { propertyId: 'property-1' }, + { propertyId: 'property-2' }, + ], }, ], }); @@ -108,9 +113,12 @@ describe(QueryExtender.name, () => { properties: [{ propertyAlias: 'property-1' }], }; const queryExtender = new QueryExtender(currentQuery); - const unmodeledDataStreams = [{ propertyAlias: 'property-2' }] as UnmodeledDataStream[]; + const unmodeledDataStreams = [ + { propertyAlias: 'property-2' }, + ] as UnmodeledDataStream[]; - const extendedQuery = queryExtender.extendPropertyAliasQueries(unmodeledDataStreams); + const extendedQuery = + queryExtender.extendPropertyAliasQueries(unmodeledDataStreams); expect(extendedQuery).toEqual({ assets: [ @@ -119,7 +127,10 @@ describe(QueryExtender.name, () => { properties: [{ propertyId: 'property-1' }], }, ], - properties: [{ propertyAlias: 'property-1' }, { propertyAlias: 'property-2' }], + properties: [ + { propertyAlias: 'property-1' }, + { propertyAlias: 'property-2' }, + ], }); }); @@ -133,9 +144,13 @@ describe(QueryExtender.name, () => { { propertyAlias: 'property-2' }, ] as UnmodeledDataStream[]; - const extendedQuery = queryExtender.extendPropertyAliasQueries(unmodeledDataStreams); + const extendedQuery = + queryExtender.extendPropertyAliasQueries(unmodeledDataStreams); - expect(extendedQuery.properties).toEqual([{ propertyAlias: 'property-1' }, { propertyAlias: 'property-2' }]); + expect(extendedQuery.properties).toEqual([ + { propertyAlias: 'property-1' }, + { propertyAlias: 'property-2' }, + ]); }); }); diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/queryExtender/queryExtender.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/queryExtender/queryExtender.ts index 2c8de0ef3..cd79044a2 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/queryExtender/queryExtender.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/queryExtender/queryExtender.ts @@ -14,7 +14,10 @@ export class QueryExtender { public extendAssetQueries(modeledDataStreams: ModeledDataStream[]): Query { const currentAssetQueries = this.#currentQuery.assets ?? []; const newAssetQueries = this.#createAssetQueries(modeledDataStreams); - const dedupedAssetQueries = this.#dedupeAssetQueries([...currentAssetQueries, ...newAssetQueries]); + const dedupedAssetQueries = this.#dedupeAssetQueries([ + ...currentAssetQueries, + ...newAssetQueries, + ]); const extendedQuery = { ...this.#currentQuery, assets: dedupedAssetQueries, @@ -23,28 +26,45 @@ export class QueryExtender { return extendedQuery; } - #createAssetQueries(modeledDataStreams: ModeledDataStream[]): NonNullable { + #createAssetQueries( + modeledDataStreams: ModeledDataStream[] + ): NonNullable { const assetQueriesWithProperties = modeledDataStreams .filter(this.#isModeledDataStream) - .map[number]>(({ assetId, propertyId: propertyId }) => ({ - assetId, - properties: [{ propertyId }], - })); + .map[number]>( + ({ assetId, propertyId: propertyId }) => ({ + assetId, + properties: [{ propertyId }], + }) + ); return assetQueriesWithProperties; } - #isModeledDataStream(dataStream: ModeledDataStream | UnmodeledDataStream): dataStream is ModeledDataStream { - return Object.hasOwn(dataStream, 'propertyId') && Object.hasOwn(dataStream, 'assetId'); + #isModeledDataStream( + dataStream: ModeledDataStream | UnmodeledDataStream + ): dataStream is ModeledDataStream { + return ( + Object.hasOwn(dataStream, 'propertyId') && + Object.hasOwn(dataStream, 'assetId') + ); } #dedupeAssetQueries(assetQueries: NonNullable) { - const dedupedAssetQueries = assetQueries.reduce>((acc, currentQuery) => { - const existingQueryIndex = acc.findIndex((assetQuery) => assetQuery.assetId === currentQuery.assetId); + const dedupedAssetQueries = assetQueries.reduce< + NonNullable + >((acc, currentQuery) => { + const existingQueryIndex = acc.findIndex( + (assetQuery) => assetQuery.assetId === currentQuery.assetId + ); if (existingQueryIndex !== -1) { - const existingProperties = new Set(acc[existingQueryIndex].properties.map((p) => p.propertyId)); - const newProperties = currentQuery.properties.filter((p) => !existingProperties.has(p.propertyId)); + const existingProperties = new Set( + acc[existingQueryIndex].properties.map((p) => p.propertyId) + ); + const newProperties = currentQuery.properties.filter( + (p) => !existingProperties.has(p.propertyId) + ); acc[existingQueryIndex] = { ...acc[existingQueryIndex], @@ -60,9 +80,12 @@ export class QueryExtender { return dedupedAssetQueries; } - public extendPropertyAliasQueries(unmodeledDataStreams: UnmodeledDataStream[]): Query { + public extendPropertyAliasQueries( + unmodeledDataStreams: UnmodeledDataStream[] + ): Query { const currentPropertyAliasQueries = this.#currentQuery.properties ?? []; - const newPropertyAliasQueries = this.#createPropertyAliasQueries(unmodeledDataStreams); + const newPropertyAliasQueries = + this.#createPropertyAliasQueries(unmodeledDataStreams); const dedupedPropertyAliasQueries = this.#dedupePropertyAliasQueries([ ...currentPropertyAliasQueries, ...newPropertyAliasQueries, @@ -76,16 +99,22 @@ export class QueryExtender { } #createPropertyAliasQueries(unmodeledDataStreams: UnmodeledDataStream[]) { - const propertyAliasQueries = unmodeledDataStreams.map((unmodeledDataStream) => ({ - propertyAlias: unmodeledDataStream.propertyAlias ?? '', - })); + const propertyAliasQueries = unmodeledDataStreams.map( + (unmodeledDataStream) => ({ + propertyAlias: unmodeledDataStream.propertyAlias ?? '', + }) + ); return propertyAliasQueries; } #dedupePropertyAliasQueries(queries: NonNullable) { - const propertyAliasQueries = queries.reduce>((acc, currentQuery) => { - const existingQuery = acc.find((query) => query.propertyAlias === currentQuery.propertyAlias); + const propertyAliasQueries = queries.reduce< + NonNullable + >((acc, currentQuery) => { + const existingQuery = acc.find( + (query) => query.propertyAlias === currentQuery.propertyAlias + ); if (existingQuery) { return acc; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/unmodeledDataStreamExplorer.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/unmodeledDataStreamExplorer.tsx index 7582de041..868627371 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/unmodeledDataStreamExplorer.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/unmodeledDataStreamExplorer.tsx @@ -14,7 +14,10 @@ export interface UnmodeledDataStreamExplorerProps { client: IoTSiteWiseClient; } -export function UnmodeledDataStreamExplorer({ client, onClickAdd }: UnmodeledDataStreamExplorerProps) { +export function UnmodeledDataStreamExplorer({ + client, + onClickAdd, +}: UnmodeledDataStreamExplorerProps) { const [aliasPrefix, setAliasPrefix] = useAliasPrefix(); const { unmodeledDataStreams, diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/unmodeledDataStreamSearchForm/unmodeledDataStreamSearchForm.tsx b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/unmodeledDataStreamSearchForm/unmodeledDataStreamSearchForm.tsx index d9d3ecfed..6819c82b6 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/unmodeledDataStreamSearchForm/unmodeledDataStreamSearchForm.tsx +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/unmodeledDataStreamSearchForm/unmodeledDataStreamSearchForm.tsx @@ -9,8 +9,12 @@ interface UnmodeledDataStreamSearchFormProps { onSearch: (aliasPrefix: string | undefined) => void; } -export function UnmodeledDataStreamSearchForm({ onSearch }: UnmodeledDataStreamSearchFormProps) { - const { control, handleSubmit } = useForm({ defaultValues: { aliasPrefix: '' } }); +export function UnmodeledDataStreamSearchForm({ + onSearch, +}: UnmodeledDataStreamSearchFormProps) { + const { control, handleSubmit } = useForm({ + defaultValues: { aliasPrefix: '' }, + }); return (
    state.significantDigits); - const selectedWidgets = useSelector((state: DashboardState) => state.selectedWidgets); + const significantDigits = useSelector( + (state: DashboardState) => state.significantDigits + ); + const selectedWidgets = useSelector( + (state: DashboardState) => state.selectedWidgets + ); const [preferences, setPreferences] = useExplorerPreferences({ defaultVisibleContent: ['propertyAlias', 'latestValue'], resourceName: 'unmodeled data stream', @@ -45,7 +49,8 @@ export function UnmodeledDataStreamTable({ const { getLatestValue } = useLatestValues({ isEnabled: - preferences.visibleContent.includes('latestValue') || preferences.visibleContent.includes('latestValueTime'), + preferences.visibleContent.includes('latestValue') || + preferences.visibleContent.includes('latestValueTime'), dataStreams: unmodeledDataStreams, client, }); @@ -57,42 +62,45 @@ export function UnmodeledDataStreamTable({ latestValueTime: getLatestValue(item)?.timestamp, })) ?? []; - const { items, collectionProps, paginationProps, propertyFilterProps, actions } = useCollection( - unmodeledDataStreamsWithLatestValues, - { - propertyFiltering: { - filteringProperties: [ - { - key: 'propertyAlias', - propertyLabel: 'Alias', - groupValuesLabel: 'Property aliases', - operators: ['=', '!=', ':', '!:'], - }, - { - key: 'dataType', - propertyLabel: 'Data type', - groupValuesLabel: 'Data types', - operators: ['=', '!=', ':', '!:'], - }, - { - key: 'dataTypeSpec', - propertyLabel: 'Data type spec', - groupValuesLabel: 'Data type specs', - operators: ['=', '!=', ':', '!:'], - }, - { - key: 'latestValue', - propertyLabel: 'Latest value', - groupValuesLabel: 'Latest values', - operators: ['=', '!=', '>', '>=', '<', '<='], - }, - ], - }, - pagination: { pageSize: preferences.pageSize }, - selection: { keepSelection: true, trackBy: 'propertyAlias' }, - sorting: {}, - } - ); + const { + items, + collectionProps, + paginationProps, + propertyFilterProps, + actions, + } = useCollection(unmodeledDataStreamsWithLatestValues, { + propertyFiltering: { + filteringProperties: [ + { + key: 'propertyAlias', + propertyLabel: 'Alias', + groupValuesLabel: 'Property aliases', + operators: ['=', '!=', ':', '!:'], + }, + { + key: 'dataType', + propertyLabel: 'Data type', + groupValuesLabel: 'Data types', + operators: ['=', '!=', ':', '!:'], + }, + { + key: 'dataTypeSpec', + propertyLabel: 'Data type spec', + groupValuesLabel: 'Data type specs', + operators: ['=', '!=', ':', '!:'], + }, + { + key: 'latestValue', + propertyLabel: 'Latest value', + groupValuesLabel: 'Latest values', + operators: ['=', '!=', '>', '>=', '<', '<='], + }, + ], + }, + pagination: { pageSize: preferences.pageSize }, + selection: { keepSelection: true, trackBy: 'propertyAlias' }, + sorting: {}, + }); return (

    actions.setSelectedItems([])} - addDisabled={collectionProps.selectedItems?.length === 0 || selectedWidgets.length !== 1} + addDisabled={ + collectionProps.selectedItems?.length === 0 || + selectedWidgets.length !== 1 + } onAdd={() => { - onClickAdd(collectionProps.selectedItems as unknown as UnmodeledDataStream[]); + onClickAdd( + collectionProps.selectedItems as unknown as UnmodeledDataStream[] + ); metricsRecorder?.record({ metricName: 'UnmodeledDataStreamAdd', metricValue: 1, @@ -148,7 +161,9 @@ export function UnmodeledDataStreamTable({ header: 'Latest value time', cell: ({ latestValueTime }) => { if (latestValueTime && isNumeric(latestValueTime)) { - return getFormattedDateTimeFromEpoch(round(latestValueTime, significantDigits)); + return getFormattedDateTimeFromEpoch( + round(latestValueTime, significantDigits) + ); } return getFormattedDateTimeFromEpoch(latestValueTime); }, @@ -198,9 +213,11 @@ export function UnmodeledDataStreamTable({ filteringFinishedText='End of results' filteringEmpty='No suggestions found' i18nStrings={{ - filteringAriaLabel: 'Filter unmodeled data streams by text, property, or value', + filteringAriaLabel: + 'Filter unmodeled data streams by text, property, or value', dismissAriaLabel: 'Dismiss', - filteringPlaceholder: 'Filter unmodeled data streams by text, property, or value', + filteringPlaceholder: + 'Filter unmodeled data streams by text, property, or value', groupValuesText: 'Values', groupPropertiesText: 'Properties', operatorsText: 'Operators', @@ -224,7 +241,8 @@ export function UnmodeledDataStreamTable({ tokenLimitShowMore: 'Show more', tokenLimitShowFewer: 'Show fewer', clearFiltersText: 'Clear filters', - removeTokenButtonAriaLabel: (token) => `Remove token ${token.propertyKey} ${token.operator} ${token.value}`, + removeTokenButtonAriaLabel: (token) => + `Remove token ${token.propertyKey} ${token.operator} ${token.value}`, enteredTextLabel: (text) => `Use: "${text}"`, }} /> @@ -243,7 +261,10 @@ export function UnmodeledDataStreamTable({ }} pageSizePreference={{ title: 'Select page size', - options: SUPPORTED_PAGE_SIZES.map((size) => ({ value: size, label: size.toString() })), + options: SUPPORTED_PAGE_SIZES.map((size) => ({ + value: size, + label: size.toString(), + })), }} wrapLinesPreference={{ label: 'Wrap lines', @@ -273,7 +294,9 @@ export function UnmodeledDataStreamTable({ ariaLabels={{ resizerRoleDescription: 'Resize button', allItemsSelectionLabel: ({ selectedItems }) => - selectedItems.length !== items.length ? 'Select unmodeled data stream' : 'Deselect unmodeled data stream', + selectedItems.length !== items.length + ? 'Select unmodeled data stream' + : 'Deselect unmodeled data stream', }} /> ); diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/useUnmodeledDataStreams/listUnmodeledDataStreamsRequest.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/useUnmodeledDataStreams/listUnmodeledDataStreamsRequest.ts index e04d01907..caa280e6c 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/useUnmodeledDataStreams/listUnmodeledDataStreamsRequest.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/useUnmodeledDataStreams/listUnmodeledDataStreamsRequest.ts @@ -30,9 +30,12 @@ export class ListUnmodeledDataStreamsRequest { public async send() { try { - const { TimeSeriesSummaries = [], nextToken } = await this.#client.send(this.#command, { - abortSignal: this.#signal, - }); + const { TimeSeriesSummaries = [], nextToken } = await this.#client.send( + this.#command, + { + abortSignal: this.#signal, + } + ); const unmodeledDataStreams = this.#formatDataStreams(TimeSeriesSummaries); return { unmodeledDataStreams, nextToken }; @@ -41,8 +44,15 @@ export class ListUnmodeledDataStreamsRequest { } } - #createCommand({ aliasPrefix, nextToken }: Pick) { - return new ListTimeSeriesCommand({ timeSeriesType: 'DISASSOCIATED', aliasPrefix, nextToken }); + #createCommand({ + aliasPrefix, + nextToken, + }: Pick) { + return new ListTimeSeriesCommand({ + timeSeriesType: 'DISASSOCIATED', + aliasPrefix, + nextToken, + }); } #formatDataStreams(rawDataStreams: TimeSeriesSummary[]) { diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/useUnmodeledDataStreams/unmodeledDataStreamCacheKeyFactory.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/useUnmodeledDataStreams/unmodeledDataStreamCacheKeyFactory.ts index 308b821ee..576728bfc 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/useUnmodeledDataStreams/unmodeledDataStreamCacheKeyFactory.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/useUnmodeledDataStreams/unmodeledDataStreamCacheKeyFactory.ts @@ -6,7 +6,9 @@ export class UnmodeledDataStreamCacheKeyFactory { } public create() { - const cacheKey = [{ resource: 'unmodeled data stream', aliasPrefix: this.#aliasPrefix }] as const; + const cacheKey = [ + { resource: 'unmodeled data stream', aliasPrefix: this.#aliasPrefix }, + ] as const; return cacheKey; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/useUnmodeledDataStreams/useUnmodeledDataStreams.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/useUnmodeledDataStreams/useUnmodeledDataStreams.ts index 384735e87..152e00a87 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/useUnmodeledDataStreams/useUnmodeledDataStreams.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/unmodeledDataStreamExplorer/useUnmodeledDataStreams/useUnmodeledDataStreams.ts @@ -9,7 +9,10 @@ export interface UseUnmodeledDataStreamsOptions { client: IoTSiteWiseClient; } -export function useUnmodeledDataStreams({ aliasPrefix, client }: UseUnmodeledDataStreamsOptions) { +export function useUnmodeledDataStreams({ + aliasPrefix, + client, +}: UseUnmodeledDataStreamsOptions) { const cacheKeyFactory = new UnmodeledDataStreamCacheKeyFactory(aliasPrefix); const { @@ -27,7 +30,14 @@ export function useUnmodeledDataStreams({ aliasPrefix, client }: UseUnmodeledDat const unmodeledDataStreams = combinePages(unmodeledDataStreamPages); - return { unmodeledDataStreams, hasNextPage, isFetching, fetchNextPage, status, error }; + return { + unmodeledDataStreams, + hasNextPage, + isFetching, + fetchNextPage, + status, + error, + }; } function createQueryFn(client: IoTSiteWiseClient) { @@ -35,16 +45,27 @@ function createQueryFn(client: IoTSiteWiseClient) { queryKey: [{ aliasPrefix }], pageParam: nextToken, signal, - }: QueryFunctionContext>) { - const request = new ListUnmodeledDataStreamsRequest({ aliasPrefix, nextToken, client, signal }); + }: QueryFunctionContext< + ReturnType + >) { + const request = new ListUnmodeledDataStreamsRequest({ + aliasPrefix, + nextToken, + client, + signal, + }); const response = await request.send(); return response; }; } -function combinePages(pages: Awaited>[]) { - const combinedPages = pages.flatMap(({ unmodeledDataStreams }) => unmodeledDataStreams ?? []); +function combinePages( + pages: Awaited>[] +) { + const combinedPages = pages.flatMap( + ({ unmodeledDataStreams }) => unmodeledDataStreams ?? [] + ); return combinedPages; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useExplorerPreferences.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useExplorerPreferences.ts index ec8606054..dd22f7928 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useExplorerPreferences.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useExplorerPreferences.ts @@ -14,7 +14,10 @@ export interface UseExplorerPreferencesOptions { } /** Use to store component preferences in local storage. */ -export function useExplorerPreferences({ defaultVisibleContent, resourceName }: UseExplorerPreferencesOptions) { +export function useExplorerPreferences({ + defaultVisibleContent, + resourceName, +}: UseExplorerPreferencesOptions) { const initializer = { ...DEFAULT_PREFERENCES, visibleContent: defaultVisibleContent, @@ -22,7 +25,10 @@ export function useExplorerPreferences({ defaultVisibleContent, resourceName }: // the storage name is unique to the resource name const storageKey = `${resourceName}-preferences`; - const [preferences = initializer, setPreferences] = useLocalStorage(storageKey, initializer); + const [preferences = initializer, setPreferences] = useLocalStorage( + storageKey, + initializer + ); return [preferences, setPreferences] as const; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/batchFactory.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/batchFactory.ts index 4cb778ddd..9a10df9be 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/batchFactory.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/batchFactory.ts @@ -3,7 +3,9 @@ import { EntryIdFactory } from './entryIdFactory'; import type { ModeledDataStream } from '../modeledDataStreamQueryEditor/modeledDataStreamExplorer/types'; import type { UnmodeledDataStream } from '../unmodeledDataStreamExplorer/types'; -type BatchEntry = (ModeledDataStream | UnmodeledDataStream) & { entryId: string }; +type BatchEntry = (ModeledDataStream | UnmodeledDataStream) & { + entryId: string; +}; export class BatchFactory { readonly #dataStreams: ModeledDataStream[] | UnmodeledDataStream[]; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/batchGetLatestValues.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/batchGetLatestValues.ts index 6ced43543..40ce5154e 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/batchGetLatestValues.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/batchGetLatestValues.ts @@ -25,7 +25,9 @@ export class BatchGetLatestValuesRequest { public async send() { try { - const response = await this.#client.send(this.#command, { abortSignal: this.#signal }); + const response = await this.#client.send(this.#command, { + abortSignal: this.#signal, + }); return response; } catch (error) { diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/entryIdFactory.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/entryIdFactory.ts index 10d13c6de..d74ee079e 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/entryIdFactory.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/entryIdFactory.ts @@ -17,9 +17,9 @@ export class EntryIdFactory { // post-condition check invariant( entryId.length <= this.#MAXIMUM_ENTRY_ID_LENGTH, - `Entry ID must be less than or equal to ${this.#MAXIMUM_ENTRY_ID_LENGTH} characters. Got ${ - entryId.length - } characters.` + `Entry ID must be less than or equal to ${ + this.#MAXIMUM_ENTRY_ID_LENGTH + } characters. Got ${entryId.length} characters.` ); return entryId; @@ -27,16 +27,28 @@ export class EntryIdFactory { #createEntryId(): string { // Remove dashes from any UUIDs - const { assetId, propertyId, propertyAlias } = this.#extractIdentifiersFromDataStream(); - const entryId = this.#formatIdentifiers({ assetId, propertyId, propertyAlias }); + const { assetId, propertyId, propertyAlias } = + this.#extractIdentifiersFromDataStream(); + const entryId = this.#formatIdentifiers({ + assetId, + propertyId, + propertyAlias, + }); return entryId; } #extractIdentifiersFromDataStream() { - const assetId = 'assetId' in this.#dataStream ? this.#dataStream.assetId : undefined; - const propertyId = 'propertyId' in this.#dataStream ? this.#dataStream.propertyId : undefined; - const propertyAlias = 'propertyAlias' in this.#dataStream ? this.#dataStream.propertyAlias : undefined; + const assetId = + 'assetId' in this.#dataStream ? this.#dataStream.assetId : undefined; + const propertyId = + 'propertyId' in this.#dataStream + ? this.#dataStream.propertyId + : undefined; + const propertyAlias = + 'propertyAlias' in this.#dataStream + ? this.#dataStream.propertyAlias + : undefined; return { assetId, propertyId, propertyAlias }; } @@ -51,8 +63,13 @@ export class EntryIdFactory { propertyAlias?: string; }) { const trimmedAlias = this.#trimPropertyAlias(propertyAlias); - const aliasWithoutSlashes = this.#removeSlashesFromPropertyAlias(trimmedAlias); - const joinedIdentifiers = Object.values({ assetId, propertyId, propertyAlias: aliasWithoutSlashes }).join(''); + const aliasWithoutSlashes = + this.#removeSlashesFromPropertyAlias(trimmedAlias); + const joinedIdentifiers = Object.values({ + assetId, + propertyId, + propertyAlias: aliasWithoutSlashes, + }).join(''); const identifiersWithoutDashes = this.#removeDashes(joinedIdentifiers); return identifiersWithoutDashes; @@ -60,7 +77,10 @@ export class EntryIdFactory { // property aliases can be longer than the maximum entry ID length, so we trim them #trimPropertyAlias(propertyAlias?: string): string | undefined { - const trimmedPropertyAlias = propertyAlias?.substring(0, this.#MAXIMUM_ENTRY_ID_LENGTH); + const trimmedPropertyAlias = propertyAlias?.substring( + 0, + this.#MAXIMUM_ENTRY_ID_LENGTH + ); return trimmedPropertyAlias; } diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/latestValueMapFactory.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/latestValueMapFactory.ts index 385a2a787..360a6c8e6 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/latestValueMapFactory.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/latestValueMapFactory.ts @@ -3,15 +3,24 @@ import { type BatchGetAssetPropertyValueCommandOutput } from '@aws-sdk/client-io import type { LatestValueMap } from './types'; export class LatestValueMapFactory { - readonly #successEntries: NonNullable; - readonly #skippedEntries: NonNullable; - readonly #errorEntries: NonNullable; + readonly #successEntries: NonNullable< + BatchGetAssetPropertyValueCommandOutput['successEntries'] + >; + readonly #skippedEntries: NonNullable< + BatchGetAssetPropertyValueCommandOutput['skippedEntries'] + >; + readonly #errorEntries: NonNullable< + BatchGetAssetPropertyValueCommandOutput['errorEntries'] + >; constructor({ successEntries = [], skippedEntries = [], errorEntries = [], - }: Pick) { + }: Pick< + BatchGetAssetPropertyValueCommandOutput, + 'successEntries' | 'skippedEntries' | 'errorEntries' + >) { this.#successEntries = successEntries; this.#skippedEntries = skippedEntries; this.#errorEntries = errorEntries; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/types.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/types.ts index 6f08e7278..86a114659 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/types.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/types.ts @@ -3,10 +3,18 @@ import { type BatchGetAssetPropertyValueCommandOutput, } from '@aws-sdk/client-iotsitewise'; -export type Entry = NonNullable[number]; -export type SuccessEntry = NonNullable[number]; -export type SkippedEntry = NonNullable[number]; -export type ErrorEntry = NonNullable[number]; +export type Entry = NonNullable< + BatchGetAssetPropertyValueCommandInput['entries'] +>[number]; +export type SuccessEntry = NonNullable< + BatchGetAssetPropertyValueCommandOutput['successEntries'] +>[number]; +export type SkippedEntry = NonNullable< + BatchGetAssetPropertyValueCommandOutput['skippedEntries'] +>[number]; +export type ErrorEntry = NonNullable< + BatchGetAssetPropertyValueCommandOutput['errorEntries'] +>[number]; type LatestValue = { value: string | number | boolean | undefined; diff --git a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/useLatestValues.ts b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/useLatestValues.ts index d4746bc3d..3c41972bc 100644 --- a/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/useLatestValues.ts +++ b/packages/dashboard/src/components/queryEditor/iotSiteWiseQueryEditor/useLatestValues/useLatestValues.ts @@ -20,7 +20,11 @@ export interface UseLatestValueProps { } /** Regularly poll for and use the latest value for a given list of asset properties. */ -export function useLatestValues({ dataStreams, isEnabled, client }: UseLatestValueProps) { +export function useLatestValues({ + dataStreams, + isEnabled, + client, +}: UseLatestValueProps) { // Prepare asset properties for batch requests const cacheKeyFactory = new LatestValueCacheKeyFactory(); const batchFactory = new BatchFactory(dataStreams); @@ -38,10 +42,15 @@ export function useLatestValues({ dataStreams, isEnabled, client }: UseLatestVal const latestValueMaps = queries .map(({ data: latestValueMap }) => latestValueMap) - .filter((latestValueMap): latestValueMap is LatestValueMap => Boolean(latestValueMap)); - const latestValueMap = latestValueMaps.reduce((acc, currentLatestValueMap) => { - return { ...acc, ...currentLatestValueMap }; - }, {}); + .filter((latestValueMap): latestValueMap is LatestValueMap => + Boolean(latestValueMap) + ); + const latestValueMap = latestValueMaps.reduce( + (acc, currentLatestValueMap) => { + return { ...acc, ...currentLatestValueMap }; + }, + {} + ); function getLatestValue(dataStream: ModeledDataStream | UnmodeledDataStream) { const entryIdFactory = new EntryIdFactory(dataStream); @@ -59,7 +68,11 @@ function createQueryFn(client: IoTSiteWiseClient) { queryKey: [{ entries = [] }], signal, }: QueryFunctionContext>) { - const request = new BatchGetLatestValuesRequest({ client, entries, signal }); + const request = new BatchGetLatestValuesRequest({ + client, + entries, + signal, + }); const response = await request.send(); const latestValueMapFactory = new LatestValueMapFactory(response); const latestValueMap = latestValueMapFactory.create(); diff --git a/packages/dashboard/src/components/queryEditor/queryEditorErrorBoundary/index.ts b/packages/dashboard/src/components/queryEditor/queryEditorErrorBoundary/index.ts index 2a9bb300a..524dd6dc1 100644 --- a/packages/dashboard/src/components/queryEditor/queryEditorErrorBoundary/index.ts +++ b/packages/dashboard/src/components/queryEditor/queryEditorErrorBoundary/index.ts @@ -1 +1,4 @@ -export { QueryEditorErrorBoundary, type QueryEditorErrorBoundaryProps } from './queryEditorErrorBoundary'; +export { + QueryEditorErrorBoundary, + type QueryEditorErrorBoundaryProps, +} from './queryEditorErrorBoundary'; diff --git a/packages/dashboard/src/components/queryEditor/queryEditorErrorBoundary/queryEditorErrorBoundary.spec.tsx b/packages/dashboard/src/components/queryEditor/queryEditorErrorBoundary/queryEditorErrorBoundary.spec.tsx index d05b313e5..aa6aaa8f0 100644 --- a/packages/dashboard/src/components/queryEditor/queryEditorErrorBoundary/queryEditorErrorBoundary.spec.tsx +++ b/packages/dashboard/src/components/queryEditor/queryEditorErrorBoundary/queryEditorErrorBoundary.spec.tsx @@ -14,12 +14,16 @@ describe(QueryEditorErrorBoundary, () => { ); - expect(screen.getByText('error fallback is not being rendered')).toBeVisible(); + expect( + screen.getByText('error fallback is not being rendered') + ).toBeVisible(); expect(screen.queryByText('An error occured.')).not.toBeInTheDocument(); await user.click(screen.getByText('EXPLODE')); expect(screen.getByText('An error occured.')).toBeVisible(); - expect(screen.queryByText('error fallback is not being rendered')).not.toBeInTheDocument(); + expect( + screen.queryByText('error fallback is not being rendered') + ).not.toBeInTheDocument(); }); }); diff --git a/packages/dashboard/src/components/queryEditor/queryEditorErrorBoundary/queryEditorErrorBoundary.tsx b/packages/dashboard/src/components/queryEditor/queryEditorErrorBoundary/queryEditorErrorBoundary.tsx index fa4817230..d79134bf4 100644 --- a/packages/dashboard/src/components/queryEditor/queryEditorErrorBoundary/queryEditorErrorBoundary.tsx +++ b/packages/dashboard/src/components/queryEditor/queryEditorErrorBoundary/queryEditorErrorBoundary.tsx @@ -5,7 +5,9 @@ import Link from '@cloudscape-design/components/link'; export type QueryEditorErrorBoundaryProps = PropsWithChildren; -export function QueryEditorErrorBoundary({ children }: QueryEditorErrorBoundaryProps) { +export function QueryEditorErrorBoundary({ + children, +}: QueryEditorErrorBoundaryProps) { return ( ( diff --git a/packages/dashboard/src/components/queryEditor/useQuery.ts b/packages/dashboard/src/components/queryEditor/useQuery.ts index 2b6743cb4..b6c7e40d0 100644 --- a/packages/dashboard/src/components/queryEditor/useQuery.ts +++ b/packages/dashboard/src/components/queryEditor/useQuery.ts @@ -10,17 +10,33 @@ import { applyAggregationToQuery } from '~/customization/widgets/utils/assetQuer import { applyResolutionToQuery } from '~/customization/widgets/utils/assetQuery/applyResolutionToQuery'; import { getCurrentAggregationResolution } from '~/customization/widgets/utils/widgetAggregationUtils'; -type WidgetWithQuery = DashboardWidget<{ queryConfig: { query: IoTSiteWiseDataStreamQuery } }>; - -export const styledQueryWidgetOnDrop = (updatedQuery: IoTSiteWiseDataStreamQuery, widget: QueryWidget) => { - const mergedQuery = { assets: [], properties: [], assetModels: [], ...updatedQuery }; +type WidgetWithQuery = DashboardWidget<{ + queryConfig: { query: IoTSiteWiseDataStreamQuery }; +}>; + +export const styledQueryWidgetOnDrop = ( + updatedQuery: IoTSiteWiseDataStreamQuery, + widget: QueryWidget +) => { + const mergedQuery = { + assets: [], + properties: [], + assetModels: [], + ...updatedQuery, + }; const queryWithRefIds = assignDefaultRefId(mergedQuery); const { aggregation, resolution } = getCurrentAggregationResolution(widget); - const queryWithAggregation = applyAggregationToQuery(queryWithRefIds, aggregation); - const queryWithResolution = applyResolutionToQuery(queryWithAggregation, resolution); + const queryWithAggregation = applyAggregationToQuery( + queryWithRefIds, + aggregation + ); + const queryWithResolution = applyResolutionToQuery( + queryWithAggregation, + resolution + ); const queryWithDefaultStyles = applyDefaultStylesToQuery(queryWithResolution); return queryWithDefaultStyles; @@ -36,18 +52,30 @@ export const styledQueryWidgetOnDrop = (updatedQuery: IoTSiteWiseDataStreamQuery */ export function useQuery(): [ IoTSiteWiseDataStreamQuery | undefined, - (cb: (currentQuery?: IoTSiteWiseDataStreamQuery) => IoTSiteWiseDataStreamQuery | undefined) => void + ( + cb: ( + currentQuery?: IoTSiteWiseDataStreamQuery + ) => IoTSiteWiseDataStreamQuery | undefined + ) => void ] { const dispatch = useDispatch(); - const selectedWidgets = useSelectedWidgets<{ queryConfig: { query: IoTSiteWiseDataStreamQuery } }>(); - const selectedWidget = isOneWidgetSelected(selectedWidgets) ? getFirstWidget(selectedWidgets) : undefined; + const selectedWidgets = useSelectedWidgets<{ + queryConfig: { query: IoTSiteWiseDataStreamQuery }; + }>(); + const selectedWidget = isOneWidgetSelected(selectedWidgets) + ? getFirstWidget(selectedWidgets) + : undefined; const query = isWidget(selectedWidget) && 'queryConfig' in selectedWidget.properties ? getQueryFromWidget(selectedWidget) : undefined; - function setQuery(cb: (currentQuery?: IoTSiteWiseDataStreamQuery) => IoTSiteWiseDataStreamQuery | undefined): void { + function setQuery( + cb: ( + currentQuery?: IoTSiteWiseDataStreamQuery + ) => IoTSiteWiseDataStreamQuery | undefined + ): void { // Only update the query if a widget is selected if (isWidget(selectedWidget)) { let updatedQuery = cb(query); @@ -58,10 +86,15 @@ export function useQuery(): [ updatedQuery = styledQueryWidgetOnDrop(updatedQuery, selectedWidget); } - let updatedWidget = createUpdatedWidget(selectedWidget, updatedQuery as IoTSiteWiseDataStreamQuery); + let updatedWidget = createUpdatedWidget( + selectedWidget, + updatedQuery as IoTSiteWiseDataStreamQuery + ); if (selectedWidget.type !== 'xy-plot') { - updatedWidget = assignDefaultStyles(updatedWidget as QueryWidget) as typeof updatedWidget; + updatedWidget = assignDefaultStyles( + updatedWidget as QueryWidget + ) as typeof updatedWidget; } const action = createUpdateAction(updatedWidget); @@ -74,23 +107,34 @@ export function useQuery(): [ return [query, setQuery]; } -function isOneWidgetSelected(widgets: WidgetWithQuery[]): widgets is [WidgetWithQuery] { +function isOneWidgetSelected( + widgets: WidgetWithQuery[] +): widgets is [WidgetWithQuery] { return widgets.length === 1; } -function isWidget(widget: WidgetWithQuery | undefined): widget is WidgetWithQuery { +function isWidget( + widget: WidgetWithQuery | undefined +): widget is WidgetWithQuery { return Boolean(widget); } -function getFirstWidget(widgets: WidgetWithQuery[]): WidgetWithQuery | undefined { +function getFirstWidget( + widgets: WidgetWithQuery[] +): WidgetWithQuery | undefined { return widgets.at(0); } -function getQueryFromWidget(widget: WidgetWithQuery): IoTSiteWiseDataStreamQuery { +function getQueryFromWidget( + widget: WidgetWithQuery +): IoTSiteWiseDataStreamQuery { return widget.properties.queryConfig.query; } -function createUpdatedWidget(widget: WidgetWithQuery, newQuery: IoTSiteWiseDataStreamQuery): WidgetWithQuery { +function createUpdatedWidget( + widget: WidgetWithQuery, + newQuery: IoTSiteWiseDataStreamQuery +): WidgetWithQuery { const updatedWidget = { ...widget, properties: { diff --git a/packages/dashboard/src/components/resizablePanes/index.tsx b/packages/dashboard/src/components/resizablePanes/index.tsx index 9e5f26d54..3d36e51dd 100644 --- a/packages/dashboard/src/components/resizablePanes/index.tsx +++ b/packages/dashboard/src/components/resizablePanes/index.tsx @@ -24,7 +24,8 @@ const getSessionStorageNumber = (key: string, fallback: number) => { return fallback; }; -const getStoredLeftWidthPercent = () => getSessionStorageNumber(LEFT_WIDTH_PERCENT_STORAGE_KEY, LEFT_WIDTH_PERCENT); +const getStoredLeftWidthPercent = () => + getSessionStorageNumber(LEFT_WIDTH_PERCENT_STORAGE_KEY, LEFT_WIDTH_PERCENT); type ResizablePanesProps = { leftPane: ReactNode; @@ -32,14 +33,20 @@ type ResizablePanesProps = { rightPane: ReactNode; }; -export const ResizablePanes: FC = ({ leftPane, centerPane, rightPane }) => { +export const ResizablePanes: FC = ({ + leftPane, + centerPane, + rightPane, +}) => { const panes = useRef(null); // Used to prevent any scroll events leaking to the grid component on resize const [pointerEvents, setPointerEvents] = useState<'auto' | 'none'>('auto'); /** Currently active drag hangle during a drag, or null if not dragging */ - const [currentDragHandle, setCurrentDragHandle] = useState<'left' | null>(null); + const [currentDragHandle, setCurrentDragHandle] = useState<'left' | null>( + null + ); /** Last seen mouse x position during a drag, in px from screen left side */ const [lastSeenAtX, setLastSeenAtX] = useState(null); @@ -66,11 +73,17 @@ export const ResizablePanes: FC = ({ leftPane, centerPane, if (storedLeftWidthPercent) { const storedLeftWidth = elementWidth * storedLeftWidthPercent; - setLeftPaneWidth(storedLeftWidth > DEFAULT_SIDE_PANE_WIDTH ? storedLeftWidth : DEFAULT_SIDE_PANE_WIDTH); + setLeftPaneWidth( + storedLeftWidth > DEFAULT_SIDE_PANE_WIDTH + ? storedLeftWidth + : DEFAULT_SIDE_PANE_WIDTH + ); } else { const computedLeftPaneWidth = elementWidth * LEFT_WIDTH_PERCENT; const computedLeftPaneWidthWithMinimums = - computedLeftPaneWidth > DEFAULT_SIDE_PANE_WIDTH ? computedLeftPaneWidth : DEFAULT_SIDE_PANE_WIDTH; + computedLeftPaneWidth > DEFAULT_SIDE_PANE_WIDTH + ? computedLeftPaneWidth + : DEFAULT_SIDE_PANE_WIDTH; setLeftPaneWidth(computedLeftPaneWidthWithMinimums); } @@ -89,7 +102,10 @@ export const ResizablePanes: FC = ({ leftPane, centerPane, const onHandleDragStart = (event: MouseEvent) => { const target = event.target; if (target instanceof Element) { - if (target.classList && target.classList.contains('iot-resizable-panes-handle')) { + if ( + target.classList && + target.classList.contains('iot-resizable-panes-handle') + ) { setLastSeenAtX(event.clientX); setMovedX(0); setPointerEvents('none'); @@ -114,7 +130,10 @@ export const ResizablePanes: FC = ({ leftPane, centerPane, const nextLeftPaneWidth = leftPaneWidth + movedX; // Stop drag when dragged pane runs into other pane - if (nextLeftPaneWidth + rightPaneWidth >= elementWidth - MINIMUM_CENTER_PANE_WIDTH) { + if ( + nextLeftPaneWidth + rightPaneWidth >= + elementWidth - MINIMUM_CENTER_PANE_WIDTH + ) { cancelDrag(); return; } @@ -128,7 +147,10 @@ export const ResizablePanes: FC = ({ leftPane, centerPane, // Persist percentage with sessionStorage setLeftPaneWidth(minMaxLeftPaneWidth); const nextLeftPaneWidthPercent = nextLeftPaneWidth / elementWidth; - sessionStorage.setItem(LEFT_WIDTH_PERCENT_STORAGE_KEY, nextLeftPaneWidthPercent.toString()); + sessionStorage.setItem( + LEFT_WIDTH_PERCENT_STORAGE_KEY, + nextLeftPaneWidthPercent.toString() + ); } }; @@ -156,7 +178,10 @@ export const ResizablePanes: FC = ({ leftPane, centerPane, let nextRightProportion = rightProportion; if (nextLeftProportion + nextRightProportion > MAXIMUM_PANES_PROPORTION) { - while (nextLeftProportion + nextRightProportion > MAXIMUM_PANES_PROPORTION) { + while ( + nextLeftProportion + nextRightProportion > + MAXIMUM_PANES_PROPORTION + ) { nextLeftProportion = nextLeftProportion * 0.99; nextRightProportion = nextRightProportion * 0.99; } @@ -168,11 +193,16 @@ export const ResizablePanes: FC = ({ leftPane, centerPane, // If proportions are too high, or next pane width is larger than minimum // size, use minimum size as next pane width instead. const nextLeftPaneWidth = - maybeNextLeftPaneWidth > DEFAULT_SIDE_PANE_WIDTH ? maybeNextLeftPaneWidth : DEFAULT_SIDE_PANE_WIDTH; + maybeNextLeftPaneWidth > DEFAULT_SIDE_PANE_WIDTH + ? maybeNextLeftPaneWidth + : DEFAULT_SIDE_PANE_WIDTH; // Persist percentages with sessionStorage const nextLeftPaneWidthPercent = nextLeftPaneWidth / elementWidth; - sessionStorage.setItem(LEFT_WIDTH_PERCENT_STORAGE_KEY, nextLeftPaneWidthPercent.toString()); + sessionStorage.setItem( + LEFT_WIDTH_PERCENT_STORAGE_KEY, + nextLeftPaneWidthPercent.toString() + ); // Set pane widths setLeftPaneWidth(nextLeftPaneWidth); @@ -215,7 +245,9 @@ export const ResizablePanes: FC = ({ leftPane, centerPane, onMouseMove={(e) => onHandleDragMove(e)} onMouseUp={() => onHandleDragEnd()} style={{ - gridTemplateColumns: `max-content ${isLeftPaneCollapsed ? '0px' : `${spaceStaticXs}`} auto 0px max-content`, + gridTemplateColumns: `max-content ${ + isLeftPaneCollapsed ? '0px' : `${spaceStaticXs}` + } auto 0px max-content`, }} > = ({ leftPane, centerPane, />
    -
    +
    {centerPane}
    diff --git a/packages/dashboard/src/components/util/dateTimeUtil.spec.tsx b/packages/dashboard/src/components/util/dateTimeUtil.spec.tsx index 086e2c780..9a00c55fc 100644 --- a/packages/dashboard/src/components/util/dateTimeUtil.spec.tsx +++ b/packages/dashboard/src/components/util/dateTimeUtil.spec.tsx @@ -1,7 +1,12 @@ -import { getFormattedDateTime, getFormattedDateTimeFromEpoch } from './dateTimeUtil'; +import { + getFormattedDateTime, + getFormattedDateTimeFromEpoch, +} from './dateTimeUtil'; it('should format the Date to full Date and Time value', () => { - const rawDate = new Date(1665583620000 + new Date().getTimezoneOffset() * 60000); + const rawDate = new Date( + 1665583620000 + new Date().getTimezoneOffset() * 60000 + ); expect(getFormattedDateTime(rawDate)).toEqual(`10/12/22 14:07:00`); }); diff --git a/packages/dashboard/src/components/util/dateTimeUtil.tsx b/packages/dashboard/src/components/util/dateTimeUtil.tsx index ebe3d967c..ddec17071 100644 --- a/packages/dashboard/src/components/util/dateTimeUtil.tsx +++ b/packages/dashboard/src/components/util/dateTimeUtil.tsx @@ -1,6 +1,12 @@ // Format Date object to get date and time to display MM/DD/YY HH:MM:SS format const formatDateTime = (rawDate: Date) => { - const date = rawDate.getMonth() + 1 + '/' + rawDate.getDate() + '/' + rawDate.getFullYear().toString().slice(-2); + const date = + rawDate.getMonth() + + 1 + + '/' + + rawDate.getDate() + + '/' + + rawDate.getFullYear().toString().slice(-2); const time = rawDate.toTimeString().split(' ')[0]; const dateTime = date + ' ' + time; return dateTime; @@ -10,7 +16,9 @@ export const getFormattedDateTime = (rawDate: Date) => { return formatDateTime(rawDate); }; -export const getFormattedDateTimeFromEpoch = (epochSeconds: number | undefined) => { +export const getFormattedDateTimeFromEpoch = ( + epochSeconds: number | undefined +) => { if (!epochSeconds) return ''; const rawDate = new Date(epochSeconds * 1000); return formatDateTime(rawDate); diff --git a/packages/dashboard/src/components/util/labeledInput.tsx b/packages/dashboard/src/components/util/labeledInput.tsx index 83f9877d1..84315d3bb 100644 --- a/packages/dashboard/src/components/util/labeledInput.tsx +++ b/packages/dashboard/src/components/util/labeledInput.tsx @@ -4,7 +4,10 @@ import { FormField, Input, InputProps } from '@cloudscape-design/components'; export type LabeledInputProps = InputProps & { label: string }; -const LabeledInput: React.FC = ({ label, ...inputProps }) => ( +const LabeledInput: React.FC = ({ + label, + ...inputProps +}) => ( diff --git a/packages/dashboard/src/components/widgets/dynamicWidget.tsx b/packages/dashboard/src/components/widgets/dynamicWidget.tsx index f952b6501..e689e7a8b 100644 --- a/packages/dashboard/src/components/widgets/dynamicWidget.tsx +++ b/packages/dashboard/src/components/widgets/dynamicWidget.tsx @@ -37,7 +37,10 @@ export const getDragLayerProps = ({ widgetsMessages, }); -const DynamicWidgetComponent: React.FC = ({ widget, widgetsMessages }) => { +const DynamicWidgetComponent: React.FC = ({ + widget, + widgetsMessages, +}) => { const { invalidTagHeader, invalidTagSubheader } = widgetsMessages; const componentTag = widget.type; diff --git a/packages/dashboard/src/components/widgets/list.tsx b/packages/dashboard/src/components/widgets/list.tsx index 5a1066eb0..71fc4b165 100644 --- a/packages/dashboard/src/components/widgets/list.tsx +++ b/packages/dashboard/src/components/widgets/list.tsx @@ -43,7 +43,13 @@ const Widgets: React.FC = ({ margin: `${cellSize / 2}px`, }} > - {!readOnly && } + {!readOnly && ( + + )} {widgets.map((widget) => ( = ({ selectedWidgets, cellSize, dragEnabled }) => { +const SelectionBox: React.FC = ({ + selectedWidgets, + cellSize, + dragEnabled, +}) => { const { selectionBoxLayer, selectionGestureLayer } = useLayers(); const rect = getSelectionBox(selectedWidgets); @@ -37,7 +41,9 @@ const SelectionBox: React.FC = ({ selectedWidgets, cellSize, }} >
    = ({ anchor }) => { const isCorner = CORNERS.includes(anchor); const isSide = SIDES.includes(anchor); - const cornerClass = isCorner ? `selection-box-corner selection-box-corner-${anchor}` : ''; - const sideClass = isSide ? `selection-box-side selection-box-side-${anchor}` : ''; + const cornerClass = isCorner + ? `selection-box-corner selection-box-corner-${anchor}` + : ''; + const sideClass = isSide + ? `selection-box-side selection-box-side-${anchor}` + : ''; const anchorClass = `${cornerClass} ${sideClass}`; - return
    ; + return ( +
    + ); }; export default SelectionBoxAnchor; diff --git a/packages/dashboard/src/components/widgets/tile/tile.spec.tsx b/packages/dashboard/src/components/widgets/tile/tile.spec.tsx index 3a5270fdb..cf1e49b5a 100644 --- a/packages/dashboard/src/components/widgets/tile/tile.spec.tsx +++ b/packages/dashboard/src/components/widgets/tile/tile.spec.tsx @@ -96,6 +96,8 @@ describe('WidgetTile', () => { ); - expect(container.querySelector('[aria-label="delete widget"]')).not.toBeInTheDocument(); + expect( + container.querySelector('[aria-label="delete widget"]') + ).not.toBeInTheDocument(); }); }); diff --git a/packages/dashboard/src/components/widgets/tile/tile.tsx b/packages/dashboard/src/components/widgets/tile/tile.tsx index 1d9d2ccde..097be892e 100644 --- a/packages/dashboard/src/components/widgets/tile/tile.tsx +++ b/packages/dashboard/src/components/widgets/tile/tile.tsx @@ -11,7 +11,10 @@ import { spaceScaledXs, spaceScaledM, } from '@cloudscape-design/design-tokens'; -import { CancelableEventHandler, ClickDetail } from '@cloudscape-design/components/internal/events'; +import { + CancelableEventHandler, + ClickDetail, +} from '@cloudscape-design/components/internal/events'; import { getPlugin } from '@iot-app-kit/core'; import { DashboardWidget } from '~/types'; @@ -34,7 +37,12 @@ const DeletableTileAction = ({ handleDelete }: DeletableTileActionProps) => { return ( // eslint-disable-next-line jsx-a11y/no-static-element-interactions
    -
    ); }; @@ -50,7 +58,12 @@ export type WidgetTileProps = PropsWithChildren<{ * Component to add functionality to the widget container * Allows a user to title a widget, add click remove */ -const WidgetTile: React.FC = ({ children, widget, title, removeable }) => { +const WidgetTile: React.FC = ({ + children, + widget, + title, + removeable, +}) => { const isReadOnly = useSelector((state: DashboardState) => state.readOnly); const dispatch = useDispatch(); const [visible, setVisible] = useState(false); @@ -112,7 +125,9 @@ const WidgetTile: React.FC = ({ children, widget, title, remove
    - {isRemoveable && } + {isRemoveable && ( + + )} = ({ children, widget, title, remove description={ - Are you sure you want to delete the selected widget? You'll lose all the progress you made to the - widget + Are you sure you want to delete the selected widget? You'll + lose all the progress you made to the widget You cannot undo this action. diff --git a/packages/dashboard/src/components/widgets/widget.tsx b/packages/dashboard/src/components/widgets/widget.tsx index f52c877e8..658e0efc0 100644 --- a/packages/dashboard/src/components/widgets/widget.tsx +++ b/packages/dashboard/src/components/widgets/widget.tsx @@ -1,5 +1,8 @@ import React from 'react'; -import { gestureable, idable } from '../internalDashboard/gestures/determineTargetGestures'; +import { + gestureable, + idable, +} from '../internalDashboard/gestures/determineTargetGestures'; import DynamicWidgetComponent from './dynamicWidget'; import './widget.css'; @@ -22,7 +25,12 @@ export type WidgetProps = { * mark it with the handles required to capture gestures * */ -const WidgetComponent: React.FC = ({ cellSize, widget, messageOverrides, readOnly }) => { +const WidgetComponent: React.FC = ({ + cellSize, + widget, + messageOverrides, + readOnly, +}) => { const { x, y, z, width, height } = widget; return ( @@ -38,7 +46,10 @@ const WidgetComponent: React.FC = ({ cellSize, widget, messageOverr height: `${cellSize * height}px`, }} > - +
    ); }; diff --git a/packages/dashboard/src/customization/api.ts b/packages/dashboard/src/customization/api.ts index 0f46ae8d1..56113bef0 100644 --- a/packages/dashboard/src/customization/api.ts +++ b/packages/dashboard/src/customization/api.ts @@ -1,5 +1,8 @@ import React from 'react'; -import { ComponentLibraryComponentMap, ComponentLibraryComponentOrdering } from './componentLibraryComponentMap'; +import { + ComponentLibraryComponentMap, + ComponentLibraryComponentOrdering, +} from './componentLibraryComponentMap'; import { WidgetComponentMap } from './widgetComponentMap'; import { WidgetPropertiesGeneratorMap } from './widgetPropertiesGeneratorMap'; import type { DashboardWidget } from '~/types'; @@ -19,7 +22,10 @@ type WidgetRegistrationOptions = { properties?: () => T['properties']; initialSize?: Pick; }; -type RegisterWidget = (type: string, options: WidgetRegistrationOptions) => void; +type RegisterWidget = ( + type: string, + options: WidgetRegistrationOptions +) => void; /** * function to register a new widget type in the dashboard @@ -71,7 +77,9 @@ const resetMaps = () => { }; export const useDashboardPlugins = () => { - const hasSymbolLibraryFeatureFlag = useHasFeatureFlag('ENABLE_SYMBOL_LIBRARY'); + const hasSymbolLibraryFeatureFlag = useHasFeatureFlag( + 'ENABLE_SYMBOL_LIBRARY' + ); resetMaps(); plugins.forEach((plugin) => plugin.install({ registerWidget })); if (hasSymbolLibraryFeatureFlag) { diff --git a/packages/dashboard/src/customization/propertiesSection.ts b/packages/dashboard/src/customization/propertiesSection.ts index 1219ba398..ec59f5b51 100644 --- a/packages/dashboard/src/customization/propertiesSection.ts +++ b/packages/dashboard/src/customization/propertiesSection.ts @@ -2,7 +2,11 @@ import React from 'react'; import { useDispatch } from 'react-redux'; import isEqual from 'lodash/isEqual'; -import { onMoveWidgetsAction, onResizeWidgetsAction, onUpdateWidgetsAction } from '~/store/actions'; +import { + onMoveWidgetsAction, + onResizeWidgetsAction, + onUpdateWidgetsAction, +} from '~/store/actions'; import { DashboardWidget, Rect } from '~/types'; import { getSelectionBox } from '~/util/getSelectionBox'; import { useSelectedWidgets } from '~/hooks/useSelectedWidgets'; @@ -14,7 +18,9 @@ import { Just, Maybe, Nothing } from '~/util/maybe'; * will make it possible to narrow a list of dashboard widgets * to a common dashboard widget type */ -export type FilterPredicate = (w: DashboardWidget) => w is W; +export type FilterPredicate = ( + w: DashboardWidget +) => w is W; /** * Render function type to be used by the PropertiesSection component * The generic will be used in conjunction with a predicate funtion @@ -41,7 +47,10 @@ type Setter = (target: T, value: V) => T; * and a function which can be called with a new value * which will set that value in the target. */ -type Lens = (selector: Getter, updater: Setter) => [Maybe, (newValue: T) => void]; +type Lens = ( + selector: Getter, + updater: Setter +) => [Maybe, (newValue: T) => void]; // Lense specifically for widget properties export type PropertyLens = Lens; @@ -74,7 +83,9 @@ const compositeValue = (values: T[]): Maybe => * the most generic predicate function for a dashboard widget list * Is always true. To be used as the default predicate function in useSelection */ -export const isDashboardWidget = (widget: DashboardWidget): widget is DashboardWidget => !!widget; +export const isDashboardWidget = ( + widget: DashboardWidget +): widget is DashboardWidget => !!widget; /** * @@ -99,16 +110,25 @@ export const useSelection = ( * selection filter does not apply to entire selection * this means we cannot correctly narrow the selection type */ - if (selectedWidgets.length === 0 || !isEqual(selectedWidgets, filteredSelection)) return undefined; + if ( + selectedWidgets.length === 0 || + !isEqual(selectedWidgets, filteredSelection) + ) + return undefined; const selection = filteredSelection; /** * TECH DEBT: getSelectionBox should never be null given the above check */ - const { x, y, height, width } = trimRectPosition(getSelectionBox(selection) ?? { ...NO_SIZE, ...NO_POSITION }); - - const useSize = (): [Pick, (vector: Pick) => void] => [ + const { x, y, height, width } = trimRectPosition( + getSelectionBox(selection) ?? { ...NO_SIZE, ...NO_POSITION } + ); + + const useSize = (): [ + Pick, + (vector: Pick) => void + ] => [ { height, width }, (vector) => dispatch( @@ -119,7 +139,10 @@ export const useSelection = ( }) ), ]; - const usePosition = (): [Pick, (vector: Pick) => void] => [ + const usePosition = (): [ + Pick, + (vector: Pick) => void + ] => [ { x, y }, (vector) => dispatch( @@ -141,7 +164,9 @@ export const useSelection = ( * otherwise we can not pick a single value to represent * the selection */ - const propertyValues = selection.map((widget) => selector(widget.properties)); + const propertyValues = selection.map((widget) => + selector(widget.properties) + ); const propertyValue = compositeValue(propertyValues); return [ diff --git a/packages/dashboard/src/customization/propertiesSectionComponent.tsx b/packages/dashboard/src/customization/propertiesSectionComponent.tsx index 39970ed68..6c046b540 100644 --- a/packages/dashboard/src/customization/propertiesSectionComponent.tsx +++ b/packages/dashboard/src/customization/propertiesSectionComponent.tsx @@ -1,5 +1,9 @@ import { DashboardWidget } from '~/types'; -import { FilterPredicate, RenderSection, useSelection } from './propertiesSection'; +import { + FilterPredicate, + RenderSection, + useSelection, +} from './propertiesSection'; export type PropertiesSectionProps = { isVisible: FilterPredicate; @@ -8,7 +12,10 @@ export type PropertiesSectionProps = { export type DashboardSelection = NonNullable>; -export const PropertiesSection = ({ isVisible, render }: PropertiesSectionProps) => { +export const PropertiesSection = ({ + isVisible, + render, +}: PropertiesSectionProps) => { const compositeSelection = useSelection({ filter: isVisible }); if (!compositeSelection) return null; diff --git a/packages/dashboard/src/customization/propertiesSections/aggregationSettings/helpers.tsx b/packages/dashboard/src/customization/propertiesSections/aggregationSettings/helpers.tsx index 3017af561..98709b7af 100644 --- a/packages/dashboard/src/customization/propertiesSections/aggregationSettings/helpers.tsx +++ b/packages/dashboard/src/customization/propertiesSections/aggregationSettings/helpers.tsx @@ -1,6 +1,9 @@ import type { SelectProps } from '@cloudscape-design/components'; import { AggregateType } from '@aws-sdk/client-iotsitewise'; -import { LINE_AGGREGATION_OPTIONS, LINE_RESOLUTION_OPTIONS } from '../constants'; +import { + LINE_AGGREGATION_OPTIONS, + LINE_RESOLUTION_OPTIONS, +} from '../constants'; export const NONE_AGGREGATION = { label: 'No aggregation', value: undefined }; @@ -10,17 +13,26 @@ export const aggregateToString = (aggregate?: string): string => { return aggregate ? aggregate.replace(/_/g, ' ').toLowerCase() : 'auto'; }; -export const getAggregationOptions = (supportsRawData: boolean, dataTypes: Set, resolution?: string) => { +export const getAggregationOptions = ( + supportsRawData: boolean, + dataTypes: Set, + resolution?: string +) => { const dataTypeAggregations = dataTypes.has('STRING') || dataTypes.has('BOOLEAN') ? [{ label: 'Count', value: AggregateType.COUNT }] : LINE_AGGREGATION_OPTIONS; if (!supportsRawData) return dataTypeAggregations; - return !resolution || resolution === '0' ? [...dataTypeAggregations, NONE_AGGREGATION] : dataTypeAggregations; + return !resolution || resolution === '0' + ? [...dataTypeAggregations, NONE_AGGREGATION] + : dataTypeAggregations; }; export const getResolutionOptions = (supportsRawData: boolean) => { - if (!supportsRawData) return LINE_RESOLUTION_OPTIONS.filter((option: SelectProps.Option) => option.value !== '0'); + if (!supportsRawData) + return LINE_RESOLUTION_OPTIONS.filter( + (option: SelectProps.Option) => option.value !== '0' + ); return LINE_RESOLUTION_OPTIONS; }; diff --git a/packages/dashboard/src/customization/propertiesSections/aggregationSettings/index.tsx b/packages/dashboard/src/customization/propertiesSections/aggregationSettings/index.tsx index cc53d1213..941844ade 100644 --- a/packages/dashboard/src/customization/propertiesSections/aggregationSettings/index.tsx +++ b/packages/dashboard/src/customization/propertiesSections/aggregationSettings/index.tsx @@ -6,21 +6,33 @@ import { PropertiesSection } from '~/customization/propertiesSectionComponent'; import { LineScatterChartWidget } from '~/customization/widgets/types'; import { applyAggregationToQuery } from '~/customization/widgets/utils/assetQuery/applyAggregationToQuery'; import { applyResolutionToQuery } from '~/customization/widgets/utils/assetQuery/applyResolutionToQuery'; -import { type FilterPredicate, PropertyLens } from '~/customization/propertiesSection'; +import { + type FilterPredicate, + PropertyLens, +} from '~/customization/propertiesSection'; import { type DashboardWidget } from '~/types'; import { isJust, maybeWithDefault } from '~/util/maybe'; import { BAR_AGGREGATION_OPTIONS, BAR_RESOLUTION_OPTIONS } from '../constants'; -const isOnlyRawData: readonly string[] = ['status-timeline', 'table', 'kpi', 'status']; +const isOnlyRawData: readonly string[] = [ + 'status-timeline', + 'table', + 'kpi', + 'status', +]; const isOnlyAggregated: readonly string[] = ['bar-chart']; // TODO: Fix lying type -export const isOnlyRawDataWidget = (widget: DashboardWidget): widget is LineScatterChartWidget => +export const isOnlyRawDataWidget = ( + widget: DashboardWidget +): widget is LineScatterChartWidget => isOnlyRawData.some((t) => t === widget.type); // TODO: Fix lying type -export const isOnlyAggregatedDataWidget = (widget: DashboardWidget): widget is LineScatterChartWidget => +export const isOnlyAggregatedDataWidget = ( + widget: DashboardWidget +): widget is LineScatterChartWidget => isOnlyAggregated.some((t) => t === widget.type); const RenderAggregationsPropertiesSection = ({ @@ -38,7 +50,10 @@ const RenderAggregationsPropertiesSection = ({ queryConfig: { ...properties.queryConfig, query: properties.queryConfig.query - ? applyAggregationToQuery(properties.queryConfig.query, updatedAggregationType) + ? applyAggregationToQuery( + properties.queryConfig.query, + updatedAggregationType + ) : undefined, }, aggregationType: updatedAggregationType, @@ -49,7 +64,9 @@ const RenderAggregationsPropertiesSection = ({ ({ resolution }) => resolution, (properties, updatedResolution) => { // We get the current aggregation and don't change it if it's already set. - let updatedAggregationType: AggregateType | undefined = isJust(aggregationMaybe) + let updatedAggregationType: AggregateType | undefined = isJust( + aggregationMaybe + ) ? aggregationMaybe.value : 'AVERAGE'; @@ -60,7 +77,10 @@ const RenderAggregationsPropertiesSection = ({ const updatedQuery = properties.queryConfig.query ? applyResolutionToQuery( - applyResolutionToQuery(properties.queryConfig.query, updatedAggregationType), + applyResolutionToQuery( + properties.queryConfig.query, + updatedAggregationType + ), updatedResolution ) : undefined; @@ -84,7 +104,9 @@ const RenderAggregationsPropertiesSection = ({ updateAggregation(updatedAggregationType as AggregateType)} + updateAggregation={(updatedAggregationType) => + updateAggregation(updatedAggregationType as AggregateType) + } updateResolution={updateResolution} resolutionOptions={BAR_RESOLUTION_OPTIONS} aggregationOptions={BAR_AGGREGATION_OPTIONS} @@ -107,14 +129,22 @@ const AggregationsPropertiesSection = ({ return ( } + render={({ useProperty }) => ( + + )} /> ); }; export const AggregationsSettingsConfiguration: React.FC = () => ( <> - - + + ); diff --git a/packages/dashboard/src/customization/propertiesSections/axisSettings/index.tsx b/packages/dashboard/src/customization/propertiesSections/axisSettings/index.tsx index 045253741..aa3794d0f 100644 --- a/packages/dashboard/src/customization/propertiesSections/axisSettings/index.tsx +++ b/packages/dashboard/src/customization/propertiesSections/axisSettings/index.tsx @@ -1,23 +1,38 @@ import React from 'react'; -import { PropertiesSection, PropertiesSectionProps } from '~/customization/propertiesSectionComponent'; +import { + PropertiesSection, + PropertiesSectionProps, +} from '~/customization/propertiesSectionComponent'; import { AxisSettings } from '~/customization/settings'; import { DashboardWidget } from '~/types'; import { maybeWithDefault } from '~/util/maybe'; import AxisSection from './section'; import { PropertyLens } from '~/customization/propertiesSection'; -const axisWidgetTypes: readonly string[] = ['line-chart', 'scatter-chart', 'bar-chart', 'status-timeline']; +const axisWidgetTypes: readonly string[] = [ + 'line-chart', + 'scatter-chart', + 'bar-chart', + 'status-timeline', +]; // The widget does not support the Y axis -const axisWidgetTypesThatSupportYAxis: readonly string[] = ['line-chart', 'scatter-chart', 'bar-chart']; +const axisWidgetTypesThatSupportYAxis: readonly string[] = [ + 'line-chart', + 'scatter-chart', + 'bar-chart', +]; type AxisWidget = DashboardWidget<{ axis?: AxisSettings }>; -const isAxisWidget = (w: DashboardWidget): w is AxisWidget => axisWidgetTypes.some((t) => t === w.type); +const isAxisWidget = (w: DashboardWidget): w is AxisWidget => + axisWidgetTypes.some((t) => t === w.type); const supportsYAxis = (w: DashboardWidget): w is AxisWidget => axisWidgetTypesThatSupportYAxis.some((t) => t === w.type); -const isAxisWidgetWithYAxis = (w: DashboardWidget): w is AxisWidget => isAxisWidget(w) && supportsYAxis(w); -const isAxisWidgetWithoutYAxis = (w: DashboardWidget): w is AxisWidget => isAxisWidget(w) && !supportsYAxis(w); +const isAxisWidgetWithYAxis = (w: DashboardWidget): w is AxisWidget => + isAxisWidget(w) && supportsYAxis(w); +const isAxisWidgetWithoutYAxis = (w: DashboardWidget): w is AxisWidget => + isAxisWidget(w) && !supportsYAxis(w); const RenderAxisSettingSection = ({ useProperty, @@ -31,7 +46,13 @@ const RenderAxisSettingSection = ({ (properties, updatedAxis) => ({ ...properties, axis: updatedAxis }) ); - return ; + return ( + + ); }; const AxisSettingSection = ({ @@ -43,13 +64,21 @@ const AxisSettingSection = ({ }) => ( } + render={({ useProperty }) => ( + + )} /> ); export const AxisSettingsConfiguration: React.FC = () => ( <> - + ); diff --git a/packages/dashboard/src/customization/propertiesSections/axisSettings/section.spec.tsx b/packages/dashboard/src/customization/propertiesSections/axisSettings/section.spec.tsx index 63843b983..39559f426 100644 --- a/packages/dashboard/src/customization/propertiesSections/axisSettings/section.spec.tsx +++ b/packages/dashboard/src/customization/propertiesSections/axisSettings/section.spec.tsx @@ -27,14 +27,21 @@ it('renders', () => { it('renders toggles for both X and Y axis', async () => { const elem = render().baseElement; - expect(elem.querySelector('[data-test-id="axis-setting-x-toggle"]')).toBeTruthy(); - expect(elem.querySelector('[data-test-id="axis-setting-y-toggle"]')).toBeTruthy(); + expect( + elem.querySelector('[data-test-id="axis-setting-x-toggle"]') + ).toBeTruthy(); + expect( + elem.querySelector('[data-test-id="axis-setting-y-toggle"]') + ).toBeTruthy(); }); it('renders label input for Y axis', () => { const elem = render().baseElement; - expect(elem.querySelector('[data-test-id="axis-setting-y-label-content"]')?.textContent).toMatch( - DefaultDashboardMessages.sidePanel.axisMessages.yLabelContent - ); - expect(elem.querySelector('[data-test-id="axis-setting-y-label-input"]')).toBeTruthy(); + expect( + elem.querySelector('[data-test-id="axis-setting-y-label-content"]') + ?.textContent + ).toMatch(DefaultDashboardMessages.sidePanel.axisMessages.yLabelContent); + expect( + elem.querySelector('[data-test-id="axis-setting-y-label-input"]') + ).toBeTruthy(); }); diff --git a/packages/dashboard/src/customization/propertiesSections/axisSettings/section.tsx b/packages/dashboard/src/customization/propertiesSections/axisSettings/section.tsx index c7877c427..d39ce17cc 100644 --- a/packages/dashboard/src/customization/propertiesSections/axisSettings/section.tsx +++ b/packages/dashboard/src/customization/propertiesSections/axisSettings/section.tsx @@ -1,5 +1,10 @@ import * as React from 'react'; -import { ExpandableSection, Input, SpaceBetween, Toggle } from '@cloudscape-design/components'; +import { + ExpandableSection, + Input, + SpaceBetween, + Toggle, +} from '@cloudscape-design/components'; import ExpandableSectionHeader from '../shared/expandableSectionHeader'; import type { FC } from 'react'; import type { InputProps, ToggleProps } from '@cloudscape-design/components'; @@ -29,16 +34,26 @@ type AxisSectionProps = { updateAxis: (newValue: AxisSettings | undefined) => void; }; -const AxisSection: FC = ({ usesYAxis, axis = defaultAxisSetting, updateAxis }) => { - const toggleShowX: NonCancelableEventHandler = ({ detail: { checked } }) => { +const AxisSection: FC = ({ + usesYAxis, + axis = defaultAxisSetting, + updateAxis, +}) => { + const toggleShowX: NonCancelableEventHandler = ({ + detail: { checked }, + }) => { updateAxis({ ...axis, showX: checked }); }; - const toggleShowY: NonCancelableEventHandler = ({ detail: { checked } }) => { + const toggleShowY: NonCancelableEventHandler = ({ + detail: { checked }, + }) => { updateAxis({ ...axis, showY: checked }); }; - const updateLabel: NonCancelableEventHandler = ({ detail: { value } }) => { + const updateLabel: NonCancelableEventHandler = ({ + detail: { value }, + }) => { updateAxis({ ...axis, yAxisLabel: value, @@ -47,25 +62,43 @@ const AxisSection: FC = ({ usesYAxis, axis = defaultAxisSettin return ( {defaultMessages.header}} + headerText={ + + {defaultMessages.header} + + } defaultExpanded > - + {defaultMessages.toggleXLabel} {usesYAxis && ( - + {defaultMessages.toggleYLabel} )} {usesYAxis && ( -
    -
    = (widget) => { !readOnly && ( setPreferences(detail)} /> ) diff --git a/packages/dashboard/src/customization/widgets/table/emptyTableComponent.spec.tsx b/packages/dashboard/src/customization/widgets/table/emptyTableComponent.spec.tsx index 9bb327af6..445cb3fee 100644 --- a/packages/dashboard/src/customization/widgets/table/emptyTableComponent.spec.tsx +++ b/packages/dashboard/src/customization/widgets/table/emptyTableComponent.spec.tsx @@ -12,6 +12,8 @@ describe('empty state should display', () => { it('should display "No data to display" in document', async () => { render(); - expect(screen.getByTestId('default-msg')).toHaveTextContent('No data to display'); + expect(screen.getByTestId('default-msg')).toHaveTextContent( + 'No data to display' + ); }); }); diff --git a/packages/dashboard/src/customization/widgets/table/emptyTableComponent.tsx b/packages/dashboard/src/customization/widgets/table/emptyTableComponent.tsx index 585f50899..bf721efb8 100644 --- a/packages/dashboard/src/customization/widgets/table/emptyTableComponent.tsx +++ b/packages/dashboard/src/customization/widgets/table/emptyTableComponent.tsx @@ -4,7 +4,12 @@ import type { FunctionComponent } from 'react'; const EmptyTableComponent: FunctionComponent = () => { return ( - + No data to display diff --git a/packages/dashboard/src/customization/widgets/table/icon.tsx b/packages/dashboard/src/customization/widgets/table/icon.tsx index 79dc331bd..80633071e 100644 --- a/packages/dashboard/src/customization/widgets/table/icon.tsx +++ b/packages/dashboard/src/customization/widgets/table/icon.tsx @@ -4,7 +4,9 @@ import { default as tableSvgDark } from './table-dark.svg'; import WidgetIcon from '../components/widgetIcon'; const TableIcon = () => { - return ; + return ( + + ); }; export default TableIcon; diff --git a/packages/dashboard/src/customization/widgets/table/plugin.tsx b/packages/dashboard/src/customization/widgets/table/plugin.tsx index b0c152f51..389db1e91 100644 --- a/packages/dashboard/src/customization/widgets/table/plugin.tsx +++ b/packages/dashboard/src/customization/widgets/table/plugin.tsx @@ -3,7 +3,10 @@ import TableWidgetComponent from './component'; import TableIcon from './icon'; import type { DashboardPlugin } from '~/customization/api'; import type { TableWidget } from '../types'; -import { TABLE_WIDGET_INITIAL_HEIGHT, TABLE_WIDGET_INITIAL_WIDTH } from '../constants'; +import { + TABLE_WIDGET_INITIAL_HEIGHT, + TABLE_WIDGET_INITIAL_WIDTH, +} from '../constants'; export const tablePlugin: DashboardPlugin = { install: ({ registerWidget }) => { diff --git a/packages/dashboard/src/customization/widgets/table/table-config.tsx b/packages/dashboard/src/customization/widgets/table/table-config.tsx index 2079d5c91..b87a38396 100644 --- a/packages/dashboard/src/customization/widgets/table/table-config.tsx +++ b/packages/dashboard/src/customization/widgets/table/table-config.tsx @@ -19,7 +19,9 @@ export const collectionPreferencesProps = { confirmLabel: 'Confirm', title: 'Preferences', }; -export const PROPERTY_FILTERING: { filteringProperties: PropertyFilterProperty[] } = { +export const PROPERTY_FILTERING: { + filteringProperties: PropertyFilterProperty[]; +} = { filteringProperties: [ { key: 'property', diff --git a/packages/dashboard/src/customization/widgets/table/useTableItems.ts b/packages/dashboard/src/customization/widgets/table/useTableItems.ts index caa0b616f..90c337653 100644 --- a/packages/dashboard/src/customization/widgets/table/useTableItems.ts +++ b/packages/dashboard/src/customization/widgets/table/useTableItems.ts @@ -23,8 +23,12 @@ export const useTableItems = (query: SiteWiseQueryConfig['query']) => { const assetItems = assets.flatMap(({ assetId, properties }) => properties.map(({ propertyId }) => { - const assetDescription = queries.find(({ data }) => data?.assetId === assetId)?.data; - const { unit, name } = assetDescription?.assetProperties?.find(({ id }) => id === propertyId) ?? { unit: '' }; + const assetDescription = queries.find( + ({ data }) => data?.assetId === assetId + )?.data; + const { unit, name } = assetDescription?.assetProperties?.find( + ({ id }) => id === propertyId + ) ?? { unit: '' }; return { property: `${name} (${assetDescription?.assetName ?? ''})`, unit, diff --git a/packages/dashboard/src/customization/widgets/text/component.test.tsx b/packages/dashboard/src/customization/widgets/text/component.test.tsx index f1de21b73..0cebb062f 100644 --- a/packages/dashboard/src/customization/widgets/text/component.test.tsx +++ b/packages/dashboard/src/customization/widgets/text/component.test.tsx @@ -21,14 +21,18 @@ jest.mock('~/customization/hooks/useIsSelected', () => ({ useIsSelected: jest.fn(), })); -jest.mock('./link', () => (props: unknown) =>
    {JSON.stringify(props)}
    ); +jest.mock('./link', () => (props: unknown) => ( +
    {JSON.stringify(props)}
    +)); jest.mock('./styledText/textArea', () => (props: unknown) => (
    {JSON.stringify(props)}
    )); jest.mock('./styledText/editableText', () => (props: unknown) => (
    {JSON.stringify(props)}
    )); -jest.mock('./styledText', () => (props: unknown) =>
    {JSON.stringify(props)}
    ); +jest.mock('./styledText', () => (props: unknown) => ( +
    {JSON.stringify(props)}
    +)); describe('Text Widget', () => { beforeEach(() => { @@ -89,6 +93,8 @@ describe('Text Widget', () => { ); unmount(); - expect(onChangeDashboardGridEnabledAction).toBeCalledWith({ enabled: true }); + expect(onChangeDashboardGridEnabledAction).toBeCalledWith({ + enabled: true, + }); }); }); diff --git a/packages/dashboard/src/customization/widgets/text/icon.tsx b/packages/dashboard/src/customization/widgets/text/icon.tsx index 4c4469bff..0838ded69 100644 --- a/packages/dashboard/src/customization/widgets/text/icon.tsx +++ b/packages/dashboard/src/customization/widgets/text/icon.tsx @@ -4,7 +4,9 @@ import { default as textSvgDark } from './text-dark.svg'; import WidgetIcon from '../components/widgetIcon'; const TextIcon = () => { - return ; + return ( + + ); }; export default TextIcon; diff --git a/packages/dashboard/src/customization/widgets/text/link/index.tsx b/packages/dashboard/src/customization/widgets/text/link/index.tsx index d97132a24..ac32edd2f 100644 --- a/packages/dashboard/src/customization/widgets/text/link/index.tsx +++ b/packages/dashboard/src/customization/widgets/text/link/index.tsx @@ -8,11 +8,14 @@ type TextLinkProps = TextWidget; const TextLink: React.FC = (widget) => { const { value, href } = widget.properties; - const { fontSize, fontColor, isBold, isItalic, isUnderlined } = widget.properties.fontSettings || defaultFontSettings; + const { fontSize, fontColor, isBold, isItalic, isUnderlined } = + widget.properties.fontSettings || defaultFontSettings; - const className = `text-widget text-widget-link ${isItalic ? 'text-widget-italic' : ''} ${ - isBold ? 'text-widget-bold' : '' - } ${isUnderlined ? 'text-widget-underline' : ''}`; + const className = `text-widget text-widget-link ${ + isItalic ? 'text-widget-italic' : '' + } ${isBold ? 'text-widget-bold' : ''} ${ + isUnderlined ? 'text-widget-underline' : '' + }`; const style: CSSProperties = { fontSize, diff --git a/packages/dashboard/src/customization/widgets/text/plugin.tsx b/packages/dashboard/src/customization/widgets/text/plugin.tsx index 2c1486811..c6d1a4949 100644 --- a/packages/dashboard/src/customization/widgets/text/plugin.tsx +++ b/packages/dashboard/src/customization/widgets/text/plugin.tsx @@ -3,7 +3,10 @@ import TextWidgetComponent from './component'; import TextIcon from './icon'; import type { DashboardPlugin } from '~/customization/api'; import type { TextWidget } from '../types'; -import { TEXT_WIDGET_INITIAL_HEIGHT, TEXT_WIDGET_INITIAL_WIDTH } from '../constants'; +import { + TEXT_WIDGET_INITIAL_HEIGHT, + TEXT_WIDGET_INITIAL_WIDTH, +} from '../constants'; export const textPlugin: DashboardPlugin = { install: ({ registerWidget }) => { diff --git a/packages/dashboard/src/customization/widgets/text/styledText/editableText.tsx b/packages/dashboard/src/customization/widgets/text/styledText/editableText.tsx index f55d8067c..98998fe53 100644 --- a/packages/dashboard/src/customization/widgets/text/styledText/editableText.tsx +++ b/packages/dashboard/src/customization/widgets/text/styledText/editableText.tsx @@ -10,7 +10,10 @@ type EditableStyledTextProps = TextWidget & { handleSetEdit: (isEditing: boolean) => void; }; -const EditableStyledText: React.FC = ({ handleSetEdit, ...widget }) => { +const EditableStyledText: React.FC = ({ + handleSetEdit, + ...widget +}) => { const isSelected = useIsSelected(widget); const { x, y } = widget; @@ -31,7 +34,13 @@ const EditableStyledText: React.FC = ({ handleSetEdit, } }; - return ; + return ( + + ); }; export default EditableStyledText; diff --git a/packages/dashboard/src/customization/widgets/text/styledText/index.tsx b/packages/dashboard/src/customization/widgets/text/styledText/index.tsx index 159afe698..7a2bc99ba 100644 --- a/packages/dashboard/src/customization/widgets/text/styledText/index.tsx +++ b/packages/dashboard/src/customization/widgets/text/styledText/index.tsx @@ -10,16 +10,23 @@ type StyledTextProps = TextWidget & { onPointerUp?: PointerEventHandler; }; -const StyledText: React.FC = ({ onPointerDown, onPointerUp, ...widget }) => { +const StyledText: React.FC = ({ + onPointerDown, + onPointerUp, + ...widget +}) => { const { value } = widget.properties; - const { fontSize, fontColor, isBold, isItalic, isUnderlined } = widget.properties.fontSettings || defaultFontSettings; + const { fontSize, fontColor, isBold, isItalic, isUnderlined } = + widget.properties.fontSettings || defaultFontSettings; const addPlaceholder = value.length === 0; - const className = `text-widget text-widget-display ${isItalic ? 'text-widget-italic' : ''} ${ - isBold ? 'text-widget-bold' : '' - } ${isUnderlined ? 'text-widget-underline' : ''} ${addPlaceholder ? 'text-widget-placeholder' : ''}`; + const className = `text-widget text-widget-display ${ + isItalic ? 'text-widget-italic' : '' + } ${isBold ? 'text-widget-bold' : ''} ${ + isUnderlined ? 'text-widget-underline' : '' + } ${addPlaceholder ? 'text-widget-placeholder' : ''}`; const style: CSSProperties = { fontSize, diff --git a/packages/dashboard/src/customization/widgets/text/styledText/textArea.tsx b/packages/dashboard/src/customization/widgets/text/styledText/textArea.tsx index f30ed2677..f003740bb 100644 --- a/packages/dashboard/src/customization/widgets/text/styledText/textArea.tsx +++ b/packages/dashboard/src/customization/widgets/text/styledText/textArea.tsx @@ -14,18 +14,25 @@ type StyledTextAreaProps = TextWidget & { isUrl?: boolean; }; -const StyledTextArea: React.FC = ({ handleSetEdit, isUrl, ...widget }) => { +const StyledTextArea: React.FC = ({ + handleSetEdit, + isUrl, + ...widget +}) => { const { update } = useWidgetActions(); const { value } = widget.properties; - const { fontSize, fontColor, isBold, isItalic, isUnderlined } = widget.properties.fontSettings || defaultFontSettings; + const { fontSize, fontColor, isBold, isItalic, isUnderlined } = + widget.properties.fontSettings || defaultFontSettings; const addPlaceholder = value.length === 0; - const className = `text-widget text-widget-editing ${isItalic ? 'text-widget-italic' : ''} ${ - isBold ? 'text-widget-bold' : '' - } ${isUnderlined ? 'text-widget-underline' : ''} ${addPlaceholder ? 'text-widget-placeholder' : ''}`; + const className = `text-widget text-widget-editing ${ + isItalic ? 'text-widget-italic' : '' + } ${isBold ? 'text-widget-bold' : ''} ${ + isUnderlined ? 'text-widget-underline' : '' + } ${addPlaceholder ? 'text-widget-placeholder' : ''}`; const style: CSSProperties = { fontSize, @@ -53,7 +60,8 @@ const StyledTextArea: React.FC = ({ handleSetEdit, isUrl, . // eslint-disable-next-line react-hooks/exhaustive-deps }, [value.length]); - const filter = (e: KeyboardEvent | ClipboardEvent) => e.target === ref.current; + const filter = (e: KeyboardEvent | ClipboardEvent) => + e.target === ref.current; useKeyPress('mod+shift+l', { callback: () => { const updatedWidget: TextWidget = { diff --git a/packages/dashboard/src/customization/widgets/types.ts b/packages/dashboard/src/customization/widgets/types.ts index a2853988d..b31f7b90e 100644 --- a/packages/dashboard/src/customization/widgets/types.ts +++ b/packages/dashboard/src/customization/widgets/types.ts @@ -1,4 +1,8 @@ -import type { StyleSettingsMap, Threshold, ThresholdSettings } from '@iot-app-kit/core'; +import type { + StyleSettingsMap, + Threshold, + ThresholdSettings, +} from '@iot-app-kit/core'; import type { AssetPropertyQuery, SiteWiseAssetQuery, @@ -6,8 +10,16 @@ import type { SiteWiseAssetModelQuery, } from '@iot-app-kit/source-iotsitewise'; import type { DashboardWidget } from '~/types'; -import type { AxisSettings, ComplexFontSettings, SimpleFontSettings, ThresholdWithId } from '../settings'; -import type { TableColumnDefinition, TableItem } from '@iot-app-kit/react-components'; +import type { + AxisSettings, + ComplexFontSettings, + SimpleFontSettings, + ThresholdWithId, +} from '../settings'; +import type { + TableColumnDefinition, + TableItem, +} from '@iot-app-kit/react-components'; import { AggregateType } from '@aws-sdk/client-iotsitewise'; export type QueryConfig = { @@ -17,7 +29,10 @@ export type QueryConfig = { export type SiteWiseQueryConfig = QueryConfig< 'iotsitewise', - (Partial & Partial & Partial) | undefined + | (Partial & + Partial & + Partial) + | undefined >; export type QueryProperties = { @@ -75,7 +90,13 @@ export type LineAndScatterStyles = { }; export type LineStyles = { - connectionStyle?: 'none' | 'linear' | 'curve' | 'step-start' | 'step-middle' | 'step-end'; + connectionStyle?: + | 'none' + | 'linear' + | 'curve' + | 'step-start' + | 'step-middle' + | 'step-end'; style?: 'solid' | 'dotted' | 'dashed'; thickness?: number; color?: string; @@ -97,7 +118,9 @@ export type SymbolStyles = { size?: number; }; -export type AssetPropertyStyles = LineAndScatterStyles & { yAxis?: YAxisOptions }; +export type AssetPropertyStyles = LineAndScatterStyles & { + yAxis?: YAxisOptions; +}; export type StyledAssetPropertyQuery = AssetPropertyQuery & AssetPropertyStyles; export type StyledAssetQuery = { @@ -105,7 +128,8 @@ export type StyledAssetQuery = { assetId: SiteWiseAssetQuery['assets'][number]['assetId']; properties: StyledAssetPropertyQuery[]; }[]; - properties?: (SiteWisePropertyAliasQuery['properties'][number] & AssetPropertyStyles)[]; + properties?: (SiteWisePropertyAliasQuery['properties'][number] & + AssetPropertyStyles)[]; assetModels?: { assetModelId: SiteWiseAssetModelQuery['assetModels'][number]['assetModelId']; assetIds?: SiteWiseAssetModelQuery['assetModels'][number]['assetIds']; @@ -144,7 +168,10 @@ export type ChartLegend = { visibleContent?: { [key in ChartLegendContent]?: boolean }; }; -export type StyledSiteWiseQueryConfig = QueryConfig<'iotsitewise', StyledAssetQuery | undefined>; +export type StyledSiteWiseQueryConfig = QueryConfig< + 'iotsitewise', + StyledAssetQuery | undefined +>; export type LineScatterChartProperties = LineAndScatterStyles & { title?: string; @@ -218,13 +245,17 @@ type ChartPropertiesKeysIntersection = KPIPropertiesKeys & BarChartPropertiesKeys & TablePropertiesKeys & StatusTimelinePropertiesKeys; -export type CommonChartProperties = Pick; +export type CommonChartProperties = Pick< + ChartPropertiesUnion, + ChartPropertiesKeysIntersection +>; export type QueryWidget = DashboardWidget; export type KPIWidget = DashboardWidget; export type StatusWidget = DashboardWidget; -export type LineScatterChartWidget = DashboardWidget; +export type LineScatterChartWidget = + DashboardWidget; export type BarChartWidget = DashboardWidget; export type TableWidget = DashboardWidget; export type TextWidget = DashboardWidget; diff --git a/packages/dashboard/src/customization/widgets/utils/assetModelQueryToAssetQuery.ts b/packages/dashboard/src/customization/widgets/utils/assetModelQueryToAssetQuery.ts index 4a4b83068..52a429c60 100644 --- a/packages/dashboard/src/customization/widgets/utils/assetModelQueryToAssetQuery.ts +++ b/packages/dashboard/src/customization/widgets/utils/assetModelQueryToAssetQuery.ts @@ -1,4 +1,7 @@ -import { AssetModelQuery, SiteWiseAssetQuery } from '@iot-app-kit/source-iotsitewise'; +import { + AssetModelQuery, + SiteWiseAssetQuery, +} from '@iot-app-kit/source-iotsitewise'; import unionBy from 'lodash/unionBy'; import uniq from 'lodash/uniq'; @@ -6,15 +9,22 @@ import uniq from 'lodash/uniq'; import { SiteWiseQueryConfig } from '../types'; type AssetModelQueryWithAssetId = Required; -const assetModelWithAssetId = (assetModelQuery: AssetModelQuery): assetModelQuery is AssetModelQueryWithAssetId => +const assetModelWithAssetId = ( + assetModelQuery: AssetModelQuery +): assetModelQuery is AssetModelQueryWithAssetId => assetModelQuery.assetIds != null && assetModelQuery.assetIds.length > 0; -const assetModelQueryToAssetQuery = (assetModelQuery: AssetModelQueryWithAssetId) => +const assetModelQueryToAssetQuery = ( + assetModelQuery: AssetModelQueryWithAssetId +) => assetModelQuery.assetIds.map((assetId) => ({ assetId, properties: assetModelQuery.properties, })); -const combineAssets = (assetsA: SiteWiseAssetQuery['assets'], assetsB: SiteWiseAssetQuery['assets']) => { +const combineAssets = ( + assetsA: SiteWiseAssetQuery['assets'], + assetsB: SiteWiseAssetQuery['assets'] +) => { const assetIds = uniq([...assetsA, ...assetsB].map(({ assetId }) => assetId)); return assetIds.map((assetId) => { const foundA = assetsA.find((asset) => asset.assetId === assetId); @@ -32,11 +42,17 @@ const combineAssets = (assetsA: SiteWiseAssetQuery['assets'], assetsB: SiteWiseA }); }; -export const assetModelQueryToSiteWiseAssetQuery = (query: SiteWiseQueryConfig['query']) => { +export const assetModelQueryToSiteWiseAssetQuery = ( + query: SiteWiseQueryConfig['query'] +) => { const assetModels = query?.assetModels ?? []; - const assetModelQueriesWithAssetId = assetModels.filter(assetModelWithAssetId); + const assetModelQueriesWithAssetId = assetModels.filter( + assetModelWithAssetId + ); - const mappedAssetModelQuery = assetModelQueriesWithAssetId.flatMap(assetModelQueryToAssetQuery); + const mappedAssetModelQuery = assetModelQueriesWithAssetId.flatMap( + assetModelQueryToAssetQuery + ); const assetQuery = query?.assets ?? []; diff --git a/packages/dashboard/src/customization/widgets/utils/assetQuery/applyAggregationToQuery.ts b/packages/dashboard/src/customization/widgets/utils/assetQuery/applyAggregationToQuery.ts index dd68a49ec..2c42f50d3 100644 --- a/packages/dashboard/src/customization/widgets/utils/assetQuery/applyAggregationToQuery.ts +++ b/packages/dashboard/src/customization/widgets/utils/assetQuery/applyAggregationToQuery.ts @@ -2,7 +2,11 @@ import { type AggregateType } from '@aws-sdk/client-iotsitewise'; import { IoTSiteWiseDataStreamQuery } from '~/types'; export const applyAggregationToQuery = ( - { assets = [], properties = [], assetModels = [] }: IoTSiteWiseDataStreamQuery, + { + assets = [], + properties = [], + assetModels = [], + }: IoTSiteWiseDataStreamQuery, aggregationType: AggregateType | undefined ) => ({ assets: assets.map(({ properties, ...others }) => ({ diff --git a/packages/dashboard/src/customization/widgets/utils/assetQuery/applyDefaultStylesToQuery.tsx b/packages/dashboard/src/customization/widgets/utils/assetQuery/applyDefaultStylesToQuery.tsx index f8d99a9e9..10284fcff 100644 --- a/packages/dashboard/src/customization/widgets/utils/assetQuery/applyDefaultStylesToQuery.tsx +++ b/packages/dashboard/src/customization/widgets/utils/assetQuery/applyDefaultStylesToQuery.tsx @@ -1,18 +1,30 @@ import { StyledAssetQuery } from '../../types'; import { colorerFromStyledQuery } from './defaultColors'; -export const applyDefaultStylesToQuery = ({ assets = [], properties = [], assetModels = [] }: StyledAssetQuery) => { - const assignDefaultColor = colorerFromStyledQuery({ assets, assetModels, properties }); +export const applyDefaultStylesToQuery = ({ + assets = [], + properties = [], + assetModels = [], +}: StyledAssetQuery) => { + const assignDefaultColor = colorerFromStyledQuery({ + assets, + assetModels, + properties, + }); return { assets: assets.map(({ properties, ...others }) => ({ ...others, - properties: properties.map((propertyQuery) => assignDefaultColor(propertyQuery)), + properties: properties.map((propertyQuery) => + assignDefaultColor(propertyQuery) + ), })), properties: properties.map((property) => assignDefaultColor(property)), assetModels: assetModels.map(({ properties, ...others }) => ({ ...others, - properties: properties.map((propertyQuery) => assignDefaultColor(propertyQuery)), + properties: properties.map((propertyQuery) => + assignDefaultColor(propertyQuery) + ), })), }; }; diff --git a/packages/dashboard/src/customization/widgets/utils/assetQuery/applyResolutionToQuery.ts b/packages/dashboard/src/customization/widgets/utils/assetQuery/applyResolutionToQuery.ts index ffb327553..97aacb8e5 100644 --- a/packages/dashboard/src/customization/widgets/utils/assetQuery/applyResolutionToQuery.ts +++ b/packages/dashboard/src/customization/widgets/utils/assetQuery/applyResolutionToQuery.ts @@ -1,7 +1,11 @@ import { IoTSiteWiseDataStreamQuery } from '~/types'; export const applyResolutionToQuery = ( - { assets = [], properties = [], assetModels = [] }: IoTSiteWiseDataStreamQuery, + { + assets = [], + properties = [], + assetModels = [], + }: IoTSiteWiseDataStreamQuery, resolution: string | undefined ) => ({ assets: assets.map(({ properties, ...others }) => ({ diff --git a/packages/dashboard/src/customization/widgets/utils/assetQuery/assignDefaultRefId.ts b/packages/dashboard/src/customization/widgets/utils/assetQuery/assignDefaultRefId.ts index 7d5cecdc5..08b0833c6 100644 --- a/packages/dashboard/src/customization/widgets/utils/assetQuery/assignDefaultRefId.ts +++ b/packages/dashboard/src/customization/widgets/utils/assetQuery/assignDefaultRefId.ts @@ -3,7 +3,11 @@ import { v4 as uuid } from 'uuid'; import type { IoTSiteWiseDataStreamQuery } from '~/types'; export const assignDefaultRefId = ( - { assets = [], properties = [], assetModels = [] }: IoTSiteWiseDataStreamQuery, + { + assets = [], + properties = [], + assetModels = [], + }: IoTSiteWiseDataStreamQuery, getId: () => string = uuid ) => ({ assets: assets.map(({ properties, ...others }) => ({ diff --git a/packages/dashboard/src/customization/widgets/utils/assetQuery/defaultColors.ts b/packages/dashboard/src/customization/widgets/utils/assetQuery/defaultColors.ts index 6b7fe8bbb..17ab88088 100644 --- a/packages/dashboard/src/customization/widgets/utils/assetQuery/defaultColors.ts +++ b/packages/dashboard/src/customization/widgets/utils/assetQuery/defaultColors.ts @@ -6,8 +6,11 @@ import { Colorizer } from '@iot-app-kit/core-util'; import { createNonNullableList } from '~/helpers/lists/createNonNullableList'; import { StyledAssetQuery } from '../../types'; -const colorsFromProperties = ({ properties }: { properties: { color?: string }[] }) => - createNonNullableList(properties.map(({ color }) => color)); +const colorsFromProperties = ({ + properties, +}: { + properties: { color?: string }[]; +}) => createNonNullableList(properties.map(({ color }) => color)); const applyDefault = (colorizer: ReturnType) => @@ -25,7 +28,11 @@ export const colorerFromStyledQuery = (query: StyledAssetQuery) => { const assetModelsColors = assetModels.flatMap(colorsFromProperties); const propertiesColors = colorsFromProperties({ properties }); - const existingColors = uniq([...assetsColors, ...assetModelsColors, ...propertiesColors]); + const existingColors = uniq([ + ...assetsColors, + ...assetModelsColors, + ...propertiesColors, + ]); const colorer = Colorizer(); colorer.remove(existingColors); @@ -34,7 +41,9 @@ export const colorerFromStyledQuery = (query: StyledAssetQuery) => { }; export const colorerFromStyleSettings = (styleSettings: StyleSettingsMap) => { - const existingColors = createNonNullableList(Object.values(styleSettings).map(({ color }) => color)); + const existingColors = createNonNullableList( + Object.values(styleSettings).map(({ color }) => color) + ); const colorer = Colorizer(); colorer.remove(existingColors); diff --git a/packages/dashboard/src/customization/widgets/utils/assignDefaultStyleSettings.ts b/packages/dashboard/src/customization/widgets/utils/assignDefaultStyleSettings.ts index 7d5499acc..76df6c077 100644 --- a/packages/dashboard/src/customization/widgets/utils/assignDefaultStyleSettings.ts +++ b/packages/dashboard/src/customization/widgets/utils/assignDefaultStyleSettings.ts @@ -11,7 +11,11 @@ type Query = NonNullable; // Assigns default RefID to each property and defauly aggregations+resolutions to each property const assignDefaults = ( - { assets = [], properties = [], assetModels = [] }: IoTSiteWiseDataStreamQuery, + { + assets = [], + properties = [], + assetModels = [], + }: IoTSiteWiseDataStreamQuery, resAndAggr: { aggregation?: AggregateType; resolution?: string }, getId: () => string = uuid ) => ({ @@ -41,12 +45,20 @@ const assignDefaults = ( })), }); -const assignDefaultColors = (styleSettings: StyleSettingsMap, siteWiseQuery: Query): StyleSettingsMap => { +const assignDefaultColors = ( + styleSettings: StyleSettingsMap, + siteWiseQuery: Query +): StyleSettingsMap => { const assetRefIds = - siteWiseQuery.assets?.flatMap((asset) => asset.properties.map(({ refId }) => refId)).filter(isDefined) ?? []; - const propertyRefIds = siteWiseQuery.properties?.map(({ refId }) => refId).filter(isDefined) ?? []; + siteWiseQuery.assets + ?.flatMap((asset) => asset.properties.map(({ refId }) => refId)) + .filter(isDefined) ?? []; + const propertyRefIds = + siteWiseQuery.properties?.map(({ refId }) => refId).filter(isDefined) ?? []; const assetModelRefIds = - siteWiseQuery.assetModels?.flatMap((asset) => asset.properties.map(({ refId }) => refId)).filter(isDefined) ?? []; + siteWiseQuery.assetModels + ?.flatMap((asset) => asset.properties.map(({ refId }) => refId)) + .filter(isDefined) ?? []; const refIds = [...assetRefIds, ...propertyRefIds, ...assetModelRefIds]; const applicableStyleSettings = Object.keys(styleSettings) @@ -72,8 +84,12 @@ export const assignDefaultStyles = (widget: QueryWidget): QueryWidget => { let styleSettings = widget.properties.styleSettings || {}; - const defaultResolutionAndAggregation = getCurrentAggregationResolution(widget); - const assetQueriesWithRefIds = assignDefaults(siteWiseAssetQuery, defaultResolutionAndAggregation); + const defaultResolutionAndAggregation = + getCurrentAggregationResolution(widget); + const assetQueriesWithRefIds = assignDefaults( + siteWiseAssetQuery, + defaultResolutionAndAggregation + ); styleSettings = assignDefaultColors(styleSettings, assetQueriesWithRefIds); const updated = { diff --git a/packages/dashboard/src/customization/widgets/utils/widgetAggregationUtils.ts b/packages/dashboard/src/customization/widgets/utils/widgetAggregationUtils.ts index 07f8d46c8..67879fbf2 100644 --- a/packages/dashboard/src/customization/widgets/utils/widgetAggregationUtils.ts +++ b/packages/dashboard/src/customization/widgets/utils/widgetAggregationUtils.ts @@ -1,7 +1,10 @@ import { AggregateType } from '@aws-sdk/client-iotsitewise'; import { LineScatterChartWidget, QueryWidget } from '../types'; -export const WidgetDefaultAggregation: Record = { +export const WidgetDefaultAggregation: Record< + string, + AggregateType | undefined +> = { 'bar-chart': AggregateType.AVERAGE, 'line-chart': AggregateType.AVERAGE, 'xy-plot': AggregateType.AVERAGE, @@ -23,8 +26,13 @@ export const WidgetDefaultResolution: Record = { table: '0', }; -export const getCurrentAggregationResolution = (widget: QueryWidget | LineScatterChartWidget) => { - if ('aggregationType' in widget.properties && 'resolution' in widget.properties) { +export const getCurrentAggregationResolution = ( + widget: QueryWidget | LineScatterChartWidget +) => { + if ( + 'aggregationType' in widget.properties && + 'resolution' in widget.properties + ) { return { aggregation: widget.properties.aggregationType, resolution: widget.properties.resolution, @@ -33,8 +41,10 @@ export const getCurrentAggregationResolution = (widget: QueryWidget | LineScatte const widgetType = widget.type; const firstAssetProperty = getFirstProperty(widget.properties.queryConfig); - const currentAggregation = firstAssetProperty?.aggregationType ?? WidgetDefaultAggregation[widgetType]; - const currentResolution = firstAssetProperty?.resolution ?? WidgetDefaultResolution[widgetType]; + const currentAggregation = + firstAssetProperty?.aggregationType ?? WidgetDefaultAggregation[widgetType]; + const currentResolution = + firstAssetProperty?.resolution ?? WidgetDefaultResolution[widgetType]; return { aggregation: currentAggregation, @@ -42,8 +52,13 @@ export const getCurrentAggregationResolution = (widget: QueryWidget | LineScatte }; }; -export const getAggregation = (widget: QueryWidget | LineScatterChartWidget) => { - if ('aggregationType' in widget.properties && 'resolution' in widget.properties) { +export const getAggregation = ( + widget: QueryWidget | LineScatterChartWidget +) => { + if ( + 'aggregationType' in widget.properties && + 'resolution' in widget.properties + ) { return widget.properties.aggregationType; } @@ -52,7 +67,9 @@ export const getAggregation = (widget: QueryWidget | LineScatterChartWidget) => return firstProperty?.aggregationType; }; -function getFirstProperty(queryConfig: QueryWidget['properties']['queryConfig']) { +function getFirstProperty( + queryConfig: QueryWidget['properties']['queryConfig'] +) { if ('query' in queryConfig && queryConfig.query != null) { if ( 'properties' in queryConfig.query && @@ -62,7 +79,11 @@ function getFirstProperty(queryConfig: QueryWidget['properties']['queryConfig']) return queryConfig.query.properties[0]; } - if ('assets' in queryConfig.query && queryConfig.query.assets != null && queryConfig.query.assets.length > 0) { + if ( + 'assets' in queryConfig.query && + queryConfig.query.assets != null && + queryConfig.query.assets.length > 0 + ) { return queryConfig.query.assets[0].properties[0]; } diff --git a/packages/dashboard/src/helpers/lists/createNonNullableList.ts b/packages/dashboard/src/helpers/lists/createNonNullableList.ts index 31d35aa2e..352015a1a 100644 --- a/packages/dashboard/src/helpers/lists/createNonNullableList.ts +++ b/packages/dashboard/src/helpers/lists/createNonNullableList.ts @@ -1,3 +1,7 @@ -export function createNonNullableList(list: T[]): NonNullable[] { - return list.filter>((item): item is NonNullable => item != null); +export function createNonNullableList( + list: T[] +): NonNullable[] { + return list.filter>( + (item): item is NonNullable => item != null + ); } diff --git a/packages/dashboard/src/hooks/listAssetPropertiesWithAssetDescription.ts b/packages/dashboard/src/hooks/listAssetPropertiesWithAssetDescription.ts index 956401387..f4a037d38 100644 --- a/packages/dashboard/src/hooks/listAssetPropertiesWithAssetDescription.ts +++ b/packages/dashboard/src/hooks/listAssetPropertiesWithAssetDescription.ts @@ -13,12 +13,22 @@ import { Paginator } from '@aws-sdk/types'; import { createNonNullableList } from '~/helpers/lists/createNonNullableList'; export class listAssetPropertiesWithComposite { - readonly #listAssetPropertyPaginator: Paginator; + readonly #listAssetPropertyPaginator: Paginator< + ListAssetPropertiesCommandOutput | undefined + >; #signal: AbortSignal | undefined; #client: IoTSiteWiseClient; readonly #describeAssetCommand: DescribeAssetCommand; - constructor({ assetId, client, signal }: { assetId: string; client: IoTSiteWiseClient; signal?: AbortSignal }) { + constructor({ + assetId, + client, + signal, + }: { + assetId: string; + client: IoTSiteWiseClient; + signal?: AbortSignal; + }) { this.#listAssetPropertyPaginator = this.#createAssetPropertyPaginator( { client }, { assetId: assetId, filter: 'ALL' } @@ -41,17 +51,25 @@ export class listAssetPropertiesWithComposite { } //get assetModelId - const { assetModelId } = await this.#client.send(this.#describeAssetCommand, { - abortSignal: this.#signal, - }); + const { assetModelId } = await this.#client.send( + this.#describeAssetCommand, + { + abortSignal: this.#signal, + } + ); // get all AssetModelProperties for dataType - const listAssetModelPropertiesPaginator = this.#createAssetModelPropertyPaginator( - { client: this.#client }, - { assetModelId, filter: 'ALL' } - ); - const assetModelPropertiesMap: { [x: string]: AssetModelPropertySummary } = {}; - for await (const { assetModelPropertySummaries = [] } of listAssetModelPropertiesPaginator) { + const listAssetModelPropertiesPaginator = + this.#createAssetModelPropertyPaginator( + { client: this.#client }, + { assetModelId, filter: 'ALL' } + ); + const assetModelPropertiesMap: { + [x: string]: AssetModelPropertySummary; + } = {}; + for await (const { + assetModelPropertySummaries = [], + } of listAssetModelPropertiesPaginator) { if (this.#signal?.aborted) { break; } @@ -85,7 +103,10 @@ export class listAssetPropertiesWithComposite { paginatorConfig: IoTSiteWisePaginationConfiguration, commandParams: ListAssetPropertiesCommandInput ) { - const paginator = paginateListAssetProperties(paginatorConfig, commandParams); + const paginator = paginateListAssetProperties( + paginatorConfig, + commandParams + ); return paginator; } @@ -93,7 +114,10 @@ export class listAssetPropertiesWithComposite { paginatorConfig: IoTSiteWisePaginationConfiguration, commandParams: ListAssetModelPropertiesCommandInput ) { - const paginator = paginateListAssetModelProperties(paginatorConfig, commandParams); + const paginator = paginateListAssetModelProperties( + paginatorConfig, + commandParams + ); return paginator; } diff --git a/packages/dashboard/src/hooks/useAssetDescriptionQueries.ts b/packages/dashboard/src/hooks/useAssetDescriptionQueries.ts index 3f79a1267..55059de2d 100644 --- a/packages/dashboard/src/hooks/useAssetDescriptionQueries.ts +++ b/packages/dashboard/src/hooks/useAssetDescriptionQueries.ts @@ -7,7 +7,10 @@ import { AssetPropertySummary, AssetModelPropertySummary, } from '@aws-sdk/client-iotsitewise'; -import type { SiteWiseAssetQuery, SiteWisePropertyAliasQuery } from '@iot-app-kit/source-iotsitewise'; +import type { + SiteWiseAssetQuery, + SiteWisePropertyAliasQuery, +} from '@iot-app-kit/source-iotsitewise'; import { useQuery } from '@tanstack/react-query'; import { useClients } from '~/components/dashboard/clientContext'; import { listAssetPropertiesWithComposite } from './listAssetPropertiesWithAssetDescription'; @@ -39,15 +42,27 @@ const describeAsset = (client: IoTSiteWiseClient, assetId: string) => const describeSiteWiseAssetQuery = async ( client: IoTSiteWiseClient, siteWiseQuery: Partial -) => Promise.all(siteWiseQuery.assets?.map(({ assetId }: { assetId: string }) => describeAsset(client, assetId)) ?? []); +) => + Promise.all( + siteWiseQuery.assets?.map(({ assetId }: { assetId: string }) => + describeAsset(client, assetId) + ) ?? [] + ); const ASSET_DESCRIPTION_QUERY_KEY = ['assetDescriptions']; export const isAlarm = (item: AssetCompositeModel) => item.type === 'AWS/ALARM'; -export const isAlarmState = (property: AssetProperty) => property.name === 'AWS/ALARM_STATE'; +export const isAlarmState = (property: AssetProperty) => + property.name === 'AWS/ALARM_STATE'; -const mapPropertySummary = ({ id, name, unit, dataType, alias }: AssetProperty): PropertySummary => ({ +const mapPropertySummary = ({ + id, + name, + unit, + dataType, + alias, +}: AssetProperty): PropertySummary => ({ propertyId: id, name, unit, @@ -55,25 +70,35 @@ const mapPropertySummary = ({ id, name, unit, dataType, alias }: AssetProperty): alias, }); -const mapCompositeModelToAlarmSummary = (model: AssetCompositeModel): AlarmSummary => ({ +const mapCompositeModelToAlarmSummary = ( + model: AssetCompositeModel +): AlarmSummary => ({ name: model.name, id: model?.id, properties: model.properties ?.filter(isAlarmState) - .map(({ id, unit, dataType, alias }) => mapPropertySummary({ id, unit, dataType, alias, name: model.name })) ?? - [], + .map(({ id, unit, dataType, alias }) => + mapPropertySummary({ id, unit, dataType, alias, name: model.name }) + ) ?? [], }); -export const mapAssetDescriptionToAssetSummary = (description: DescribeAssetResponse): AssetSummary => ({ +export const mapAssetDescriptionToAssetSummary = ( + description: DescribeAssetResponse +): AssetSummary => ({ assetId: description.assetId, assetName: description.assetName, properties: description.assetProperties?.map(mapPropertySummary) ?? [], - alarms: description.assetCompositeModels?.filter(isAlarm).map(mapCompositeModelToAlarmSummary) ?? [], + alarms: + description.assetCompositeModels + ?.filter(isAlarm) + .map(mapCompositeModelToAlarmSummary) ?? [], }); export const useAssetDescriptionMapQuery = ( - siteWiseQuery: Partial | undefined + siteWiseQuery: + | Partial + | undefined ) => { const { iotSiteWiseClient } = useClients(); @@ -111,7 +136,9 @@ const listPropertiesSiteWiseAssetQuery = async ( siteWiseQuery: Partial ) => Promise.all( - siteWiseQuery.assets?.map(({ assetId }: { assetId: string }) => listAssetProperties(client, assetId)) ?? [] + siteWiseQuery.assets?.map(({ assetId }: { assetId: string }) => + listAssetProperties(client, assetId) + ) ?? [] ); const newMapPropertySummary = ({ @@ -142,7 +169,9 @@ const mapListAssetPropertiesToAssetSummary = ( }; export const useListAssetPropertiesMapQuery = ( - siteWiseQuery: Partial | undefined + siteWiseQuery: + | Partial + | undefined ) => { const { iotSiteWiseClient } = useClients(); const fetchSiteWiseAssetQueryDescription = async () => { @@ -162,7 +191,11 @@ export const useListAssetPropertiesMapQuery = ( const assetId = n.at(0)?.path?.at(0)?.id; if (assetId) { const assetName = n.at(0)?.path?.at(0)?.name; - acc[assetId] = mapListAssetPropertiesToAssetSummary(n, assetId, assetName); + acc[assetId] = mapListAssetPropertiesToAssetSummary( + n, + assetId, + assetName + ); } return acc; }, {}) ?? {}, diff --git a/packages/dashboard/src/hooks/useAssetModel/describeAssetModelRequest.ts b/packages/dashboard/src/hooks/useAssetModel/describeAssetModelRequest.ts index f1d5ab8ab..0c4f4c6cb 100644 --- a/packages/dashboard/src/hooks/useAssetModel/describeAssetModelRequest.ts +++ b/packages/dashboard/src/hooks/useAssetModel/describeAssetModelRequest.ts @@ -1,4 +1,7 @@ -import { DescribeAssetModelCommand, type IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; +import { + DescribeAssetModelCommand, + type IoTSiteWiseClient, +} from '@aws-sdk/client-iotsitewise'; export class DescribeAssetModelRequest { readonly #signal: AbortSignal | undefined; @@ -21,7 +24,9 @@ export class DescribeAssetModelRequest { public async send() { try { - const response = await this.#client.send(this.#command, { abortSignal: this.#signal }); + const response = await this.#client.send(this.#command, { + abortSignal: this.#signal, + }); return response; } catch (error) { diff --git a/packages/dashboard/src/hooks/useAssetModel/listAssetModelPropertiesRequest.ts b/packages/dashboard/src/hooks/useAssetModel/listAssetModelPropertiesRequest.ts index f9a6e6760..a4246987d 100644 --- a/packages/dashboard/src/hooks/useAssetModel/listAssetModelPropertiesRequest.ts +++ b/packages/dashboard/src/hooks/useAssetModel/listAssetModelPropertiesRequest.ts @@ -11,7 +11,9 @@ import { Paginator } from '@aws-sdk/types'; import { createNonNullableList } from '~/helpers/lists/createNonNullableList'; export class listAssetModelPropertiesRequest { - readonly #listAssetModelPropertyPaginator: Paginator; + readonly #listAssetModelPropertyPaginator: Paginator< + ListAssetModelPropertiesCommandOutput | undefined + >; #signal: AbortSignal | undefined; constructor({ @@ -23,10 +25,11 @@ export class listAssetModelPropertiesRequest { client: IoTSiteWiseClient; signal?: AbortSignal; }) { - this.#listAssetModelPropertyPaginator = this.#createAssetModelPropertyPaginator( - { client }, - { assetModelId: assetModelId, filter: 'ALL' } - ); + this.#listAssetModelPropertyPaginator = + this.#createAssetModelPropertyPaginator( + { client }, + { assetModelId: assetModelId, filter: 'ALL' } + ); this.#signal = signal; } @@ -38,12 +41,15 @@ export class listAssetModelPropertiesRequest { if (this.#signal?.aborted) { break; } - const assetModelPropertySummaries = result?.assetModelPropertySummaries ?? []; + const assetModelPropertySummaries = + result?.assetModelPropertySummaries ?? []; assetModelPropertiesList.push(...assetModelPropertySummaries); } // const modeledDataStreams = this.#formatDataStreams({ assetProperties, assetModelPropertiesMap }); - const nonNullableProperties = createNonNullableList(assetModelPropertiesList); + const nonNullableProperties = createNonNullableList( + assetModelPropertiesList + ); return nonNullableProperties; } catch (error) { this.#handleError(error); @@ -54,7 +60,10 @@ export class listAssetModelPropertiesRequest { paginatorConfig: IoTSiteWisePaginationConfiguration, commandParams: ListAssetPropertiesCommandInput ) { - const paginator = paginateListAssetProperties(paginatorConfig, commandParams); + const paginator = paginateListAssetProperties( + paginatorConfig, + commandParams + ); return paginator; } @@ -62,14 +71,20 @@ export class listAssetModelPropertiesRequest { paginatorConfig: IoTSiteWisePaginationConfiguration, commandParams: ListAssetModelPropertiesCommandInput ) { - const paginator = paginateListAssetModelProperties(paginatorConfig, commandParams); + const paginator = paginateListAssetModelProperties( + paginatorConfig, + commandParams + ); return paginator; } #handleError(error: unknown): never { console.error(`Failed to get asset description. Error: ${error}`); console.info('Request input:'); - console.table(this.#createAssetModelPropertyPaginator.arguments, this.#createAssetPropertyPaginator.arguments); + console.table( + this.#createAssetModelPropertyPaginator.arguments, + this.#createAssetPropertyPaginator.arguments + ); throw error; } diff --git a/packages/dashboard/src/hooks/useAssetModel/useAssetModel.ts b/packages/dashboard/src/hooks/useAssetModel/useAssetModel.ts index 9e6214ddb..bc53e6280 100644 --- a/packages/dashboard/src/hooks/useAssetModel/useAssetModel.ts +++ b/packages/dashboard/src/hooks/useAssetModel/useAssetModel.ts @@ -20,10 +20,15 @@ export type UseAssetModelOptions = { } & (SingleAssetRequest | MultiAssetRequest); /** Use the list of child assets for an asset with a given asset ID. */ -export function useAssetModel({ assetModelId, assetModelIds, client }: UseAssetModelOptions) { +export function useAssetModel({ + assetModelId, + assetModelIds, + client, +}: UseAssetModelOptions) { const cacheKeyFactory = new AssetModelCacheKeyFactory(); - const requestIds = assetModelId !== undefined ? [assetModelId] : assetModelIds ?? []; + const requestIds = + assetModelId !== undefined ? [assetModelId] : assetModelIds ?? []; const queries = useQueries({ @@ -35,9 +40,13 @@ export function useAssetModel({ assetModelId, assetModelIds, client }: UseAssetM })), }) ?? []; - const assetModelResponses = createNonNullableList(queries.map(({ data }) => data)); - const assetModel = assetModelId !== undefined ? assetModelResponses.at(0) : undefined; - const assetModels = assetModelIds !== undefined ? assetModelResponses : undefined; + const assetModelResponses = createNonNullableList( + queries.map(({ data }) => data) + ); + const assetModel = + assetModelId !== undefined ? assetModelResponses.at(0) : undefined; + const assetModels = + assetModelIds !== undefined ? assetModelResponses : undefined; const isError = queries.some(({ isError }) => isError); const isFetching = queries.some(({ isFetching }) => isFetching); @@ -70,9 +79,16 @@ function createModelPropertyQueryFn(client: IoTSiteWiseClient) { queryKey: [{ assetModelId }], signal, }: QueryFunctionContext>) { - invariant(isEnabled(assetModelId), 'Expected asset model ID to be defined as required by the enabled flag.'); - - const request = new listAssetModelPropertiesRequest({ assetModelId, client, signal }); + invariant( + isEnabled(assetModelId), + 'Expected asset model ID to be defined as required by the enabled flag.' + ); + + const request = new listAssetModelPropertiesRequest({ + assetModelId, + client, + signal, + }); const response = await request.send(); return response; diff --git a/packages/dashboard/src/hooks/useChartSize.ts b/packages/dashboard/src/hooks/useChartSize.ts index 7aaf6f4f0..09546a515 100644 --- a/packages/dashboard/src/hooks/useChartSize.ts +++ b/packages/dashboard/src/hooks/useChartSize.ts @@ -5,5 +5,8 @@ import { DashboardWidget } from '~/types'; export const useChartSize = (widget: DashboardWidget) => { const grid = useSelector((state: DashboardState) => state.grid); - return { width: grid.cellSize * widget.width, height: grid.cellSize * widget.height }; + return { + width: grid.cellSize * widget.width, + height: grid.cellSize * widget.height, + }; }; diff --git a/packages/dashboard/src/hooks/useClickOutside.ts b/packages/dashboard/src/hooks/useClickOutside.ts index 4734a4009..ce2cc2a7f 100644 --- a/packages/dashboard/src/hooks/useClickOutside.ts +++ b/packages/dashboard/src/hooks/useClickOutside.ts @@ -15,7 +15,12 @@ export const useClickOutside = (cb: ClickCallback) => { }; const handlePointerDown = (event: PointerEvent) => { - if (ref.current && event.target && event.target instanceof Node && !ref.current.contains(event.target)) { + if ( + ref.current && + event.target && + event.target instanceof Node && + !ref.current.contains(event.target) + ) { setPointerDown(true); } }; diff --git a/packages/dashboard/src/hooks/useKeyPress.ts b/packages/dashboard/src/hooks/useKeyPress.ts index 5b3175456..993895a7b 100644 --- a/packages/dashboard/src/hooks/useKeyPress.ts +++ b/packages/dashboard/src/hooks/useKeyPress.ts @@ -13,7 +13,10 @@ type KeyPressOptions = { * @param {(pressed: boolean, e: KeyboardEvent) => void} [callback] Triggered if the key is pressed. * @return {boolean} whether or not the key is pressed */ -export const useKeyPress = (key: string, options?: KeyPressOptions | KeyPressCallback) => { +export const useKeyPress = ( + key: string, + options?: KeyPressOptions | KeyPressCallback +) => { let callback: KeyPressOptions['callback'] = undefined; let filter: KeyPressOptions['filter'] = () => true; diff --git a/packages/dashboard/src/hooks/useSelectedWidgets.ts b/packages/dashboard/src/hooks/useSelectedWidgets.ts index 051031714..8aea3bf44 100644 --- a/packages/dashboard/src/hooks/useSelectedWidgets.ts +++ b/packages/dashboard/src/hooks/useSelectedWidgets.ts @@ -5,7 +5,13 @@ import isEqual from 'lodash/isEqual'; import { DashboardWidget } from '~/types'; import type { DashboardState } from '~/store/state'; -const compareSelectedWidgets = (a: DashboardWidget[], b: DashboardWidget[]) => isEqual(a, b); +const compareSelectedWidgets = (a: DashboardWidget[], b: DashboardWidget[]) => + isEqual(a, b); -export const useSelectedWidgets = = Record>() => - useSelector((state: DashboardState) => state.selectedWidgets, compareSelectedWidgets); +export const useSelectedWidgets = < + Properties extends Record = Record +>() => + useSelector( + (state: DashboardState) => state.selectedWidgets, + compareSelectedWidgets + ); diff --git a/packages/dashboard/src/hooks/useWidgetDataTypeSet.ts b/packages/dashboard/src/hooks/useWidgetDataTypeSet.ts index db410b57d..bf4bc6d2e 100644 --- a/packages/dashboard/src/hooks/useWidgetDataTypeSet.ts +++ b/packages/dashboard/src/hooks/useWidgetDataTypeSet.ts @@ -3,7 +3,9 @@ import { isDefined } from '~/util/isDefined'; import type { IoTSiteWiseDataStreamQuery } from '~/types'; // hook grabs all the data types of the assets in a SiteWiseAssetQuery -export const useWidgetDataTypeSet = (siteWiseQuery: IoTSiteWiseDataStreamQuery | undefined): Set => { +export const useWidgetDataTypeSet = ( + siteWiseQuery: IoTSiteWiseDataStreamQuery | undefined +): Set => { const describedAssetsMapQuery = useListAssetPropertiesMapQuery(siteWiseQuery); const describedAssetsMap = describedAssetsMapQuery.data ?? {}; @@ -17,7 +19,9 @@ export const useWidgetDataTypeSet = (siteWiseQuery: IoTSiteWiseDataStreamQuery | const dataTypes = siteWiseQuery?.assets - ?.map(({ assetId, properties }) => properties.map(({ propertyId }) => getPropertyType(assetId, propertyId))) + ?.map(({ assetId, properties }) => + properties.map(({ propertyId }) => getPropertyType(assetId, propertyId)) + ) .flat(2) // need to flatten array of assets where each asset has array of properties .filter(isDefined) ?? []; diff --git a/packages/dashboard/src/messages.ts b/packages/dashboard/src/messages.ts index 39e31cb79..b523fda90 100644 --- a/packages/dashboard/src/messages.ts +++ b/packages/dashboard/src/messages.ts @@ -205,7 +205,8 @@ export const DefaultDashboardMessages: DashboardMessages = { customRelativeRangeOptionLabel: 'Custom range', customRelativeRangeOptionDescription: 'Set a custom range in the past', customRelativeRangeUnitLabel: 'Unit of time', - dateTimeConstraintText: 'For date, use YYYY/MM/DD. For time, use 24 hr format.', + dateTimeConstraintText: + 'For date, use YYYY/MM/DD. For time, use 24 hr format.', relativeModeTitle: 'Relative range', absoluteModeTitle: 'Absolute range', relativeRangeSelectionHeading: 'Choose a range', @@ -221,11 +222,14 @@ export const DefaultDashboardMessages: DashboardMessages = { return `Last ${e.amount} ${e.unit}s`; }, formatUnit: (e, n) => (1 === n ? e : `${e}s`), - dateRangeIncompleteError: 'The selected date range is incomplete. Select a start and end date for the date range.', - dateRangeInvalidError: 'The selected date range is invalid. The start date must be before the end date.', + dateRangeIncompleteError: + 'The selected date range is incomplete. Select a start and end date for the date range.', + dateRangeInvalidError: + 'The selected date range is invalid. The start date must be before the end date.', }, resourceExplorer: { - explorerEmptyLabel: 'No resources found. Please provide an asset tree query from source-iotsitewise.', + explorerEmptyLabel: + 'No resources found. Please provide an asset tree query from source-iotsitewise.', panelEmptyLabel: 'Asset has no properties or child assets.', rootAssetsHeader: 'Root Assets', childAssetsHeader: 'Child Assets', diff --git a/packages/dashboard/src/msw/iot-sitewise/handlers/batchGetAssetPropertyValue/batchGetAssetPropertyValueHandler.ts b/packages/dashboard/src/msw/iot-sitewise/handlers/batchGetAssetPropertyValue/batchGetAssetPropertyValueHandler.ts index 6dfb22266..c3195b4ee 100644 --- a/packages/dashboard/src/msw/iot-sitewise/handlers/batchGetAssetPropertyValue/batchGetAssetPropertyValueHandler.ts +++ b/packages/dashboard/src/msw/iot-sitewise/handlers/batchGetAssetPropertyValue/batchGetAssetPropertyValueHandler.ts @@ -21,7 +21,10 @@ import { type PartialAggregatedValue = Partial>; export class AggregatedValueFactory { - public create(aggregateType: AggregateType, partialAggregatedValue: PartialAggregatedValue = {}): AggregatedValue { + public create( + aggregateType: AggregateType, + partialAggregatedValue: PartialAggregatedValue = {} + ): AggregatedValue { const aggregateTypeMap = { AVERAGE: 'average', COUNT: 'count', @@ -35,7 +38,10 @@ export class AggregatedValueFactory { ...this.#createDefaults(), ...partialAggregatedValue, value: { - [aggregateTypeMap[aggregateType]]: faker.number.float({ min: 0, max: 100 }), + [aggregateTypeMap[aggregateType]]: faker.number.float({ + min: 0, + max: 100, + }), }, }; @@ -68,7 +74,9 @@ export class AssetPropertyValueFactory { return assetPropertyValue; } - public createDoubleAssetPropertyValue(partialAssetPropertyValue: PartialAssetPropertyValue = {}): AssetPropertyValue { + public createDoubleAssetPropertyValue( + partialAssetPropertyValue: PartialAssetPropertyValue = {} + ): AssetPropertyValue { const assetPropertyValue = { ...this.#createDefaults(), ...partialAssetPropertyValue, @@ -94,7 +102,9 @@ export class AssetPropertyValueFactory { return assetPropertyValue; } - public createStringAssetPropertyValue(partialAssetPropertyValue: PartialAssetPropertyValue = {}): AssetPropertyValue { + public createStringAssetPropertyValue( + partialAssetPropertyValue: PartialAssetPropertyValue = {} + ): AssetPropertyValue { const assetPropertyValue = { ...this.#createDefaults(), ...partialAssetPropertyValue, @@ -116,25 +126,27 @@ export class AssetPropertyValueFactory { } export function batchGetAssetPropertyValueHandler() { - return rest.post, BatchGetAssetPropertyValueResponse>( - BATCH_GET_ASSET_PROPERTY_VALUE_URL, - async (req, res, ctx) => { - const { entries = [] } = await req.json(); + return rest.post< + BatchGetAssetPropertyValueRequest, + Record, + BatchGetAssetPropertyValueResponse + >(BATCH_GET_ASSET_PROPERTY_VALUE_URL, async (req, res, ctx) => { + const { entries = [] } = + await req.json(); - const factory = new AssetPropertyValueFactory(); - const response = { - successEntries: entries.map(({ entryId }) => ({ - entryId, - assetPropertyValue: factory.createIntegerAssetPropertyValue(), - })), - skippedEntries: [], - errorEntries: [], - nextToken: undefined, - } satisfies BatchGetAssetPropertyValueResponse; + const factory = new AssetPropertyValueFactory(); + const response = { + successEntries: entries.map(({ entryId }) => ({ + entryId, + assetPropertyValue: factory.createIntegerAssetPropertyValue(), + })), + skippedEntries: [], + errorEntries: [], + nextToken: undefined, + } satisfies BatchGetAssetPropertyValueResponse; - return res(ctx.delay(), ctx.status(200), ctx.json(response)); - } - ); + return res(ctx.delay(), ctx.status(200), ctx.json(response)); + }); } function parseTimeInSecondsDate(date: number | Date) { @@ -148,71 +160,97 @@ function parseTimeInSecondsDate(date: number | Date) { } export function batchGetAssetPropertyValueHistoryHandler() { - return rest.post(BATCH_GET_ASSET_PROPERTY_VALUE_HISTORY_URL, async (req, res, ctx) => { - const { entries = [], maxResults = 20_000 } = await req.json(); - const maxResultsPerEntry = Math.floor(maxResults / entries.length); + return rest.post( + BATCH_GET_ASSET_PROPERTY_VALUE_HISTORY_URL, + async (req, res, ctx) => { + const { entries = [], maxResults = 20_000 } = + await req.json(); + const maxResultsPerEntry = Math.floor(maxResults / entries.length); - const factory = new AssetPropertyValueFactory(); - const response: BatchGetAssetPropertyValueHistoryResponse = { - successEntries: entries.map(({ entryId, startDate = 0, endDate = Date.now() / 1000 }) => { - // Parse startDate into common Date object - startDate = parseTimeInSecondsDate(startDate); - // Parse endDate into common Date object - endDate = parseTimeInSecondsDate(endDate); - - return { - entryId, - assetPropertyValueHistory: faker.date - .betweens({ - from: startDate, - to: endDate, - count: maxResultsPerEntry, - }) - .map((date) => { - const assetPropertyValue = factory.createIntegerAssetPropertyValue({ - timestamp: { - timeInSeconds: Math.floor(date.getTime() / 1000), - offsetInNanos: Math.floor(date.getTime() % 1000), - }, - }); - - return assetPropertyValue; - }), - }; - }), - skippedEntries: [], - errorEntries: [], - }; + const factory = new AssetPropertyValueFactory(); + const response: BatchGetAssetPropertyValueHistoryResponse = { + successEntries: entries.map( + ({ entryId, startDate = 0, endDate = Date.now() / 1000 }) => { + // Parse startDate into common Date object + startDate = parseTimeInSecondsDate(startDate); + // Parse endDate into common Date object + endDate = parseTimeInSecondsDate(endDate); - return res(ctx.delay(), ctx.status(200), ctx.json(response)); - }); + return { + entryId, + assetPropertyValueHistory: faker.date + .betweens({ + from: startDate, + to: endDate, + count: maxResultsPerEntry, + }) + .map((date) => { + const assetPropertyValue = + factory.createIntegerAssetPropertyValue({ + timestamp: { + timeInSeconds: Math.floor(date.getTime() / 1000), + offsetInNanos: Math.floor(date.getTime() % 1000), + }, + }); + + return assetPropertyValue; + }), + }; + } + ), + skippedEntries: [], + errorEntries: [], + }; + + return res(ctx.delay(), ctx.status(200), ctx.json(response)); + } + ); } export function batchGetAssetPropertyAggregatesHandler() { - return rest.post(BATCH_GET_ASSET_PROPERTY_AGGREGATES_URL, async (req, res, ctx) => { - const { entries = [], maxResults = 4000 } = await req.json(); - const maxResultsPerEntry = Math.floor(maxResults / entries.length); - const factory = new AggregatedValueFactory(); - - const response: BatchGetAssetPropertyAggregatesResponse = { - successEntries: entries.map(({ entryId, startDate = 0, endDate = Date.now(), aggregateTypes = ['AVERAGE'] }) => { - return { - entryId, - aggregatedValues: faker.date - .betweens({ from: startDate, to: endDate, count: maxResultsPerEntry }) - .map((date) => { - const aggregatedValue = factory.create(aggregateTypes[0] as AggregateType, { - timestamp: date.getTime() as unknown as AggregatedValue['timestamp'], - }); - - return aggregatedValue; - }), - }; - }), - skippedEntries: [], - errorEntries: [], - }; + return rest.post( + BATCH_GET_ASSET_PROPERTY_AGGREGATES_URL, + async (req, res, ctx) => { + const { entries = [], maxResults = 4000 } = + await req.json(); + const maxResultsPerEntry = Math.floor(maxResults / entries.length); + const factory = new AggregatedValueFactory(); - return res(ctx.delay(), ctx.status(200), ctx.json(response)); - }); + const response: BatchGetAssetPropertyAggregatesResponse = { + successEntries: entries.map( + ({ + entryId, + startDate = 0, + endDate = Date.now(), + aggregateTypes = ['AVERAGE'], + }) => { + return { + entryId, + aggregatedValues: faker.date + .betweens({ + from: startDate, + to: endDate, + count: maxResultsPerEntry, + }) + .map((date) => { + const aggregatedValue = factory.create( + aggregateTypes[0] as AggregateType, + { + timestamp: + date.getTime() as unknown as AggregatedValue['timestamp'], + } + ); + + return aggregatedValue; + }), + }; + } + ), + skippedEntries: [], + errorEntries: [], + }; + + return res(ctx.delay(), ctx.status(200), ctx.json(response)); + } + ); } diff --git a/packages/dashboard/src/msw/iot-sitewise/handlers/describeTimeSeries/describeTimeSeries.ts b/packages/dashboard/src/msw/iot-sitewise/handlers/describeTimeSeries/describeTimeSeries.ts index 450b62a2c..3410fa446 100644 --- a/packages/dashboard/src/msw/iot-sitewise/handlers/describeTimeSeries/describeTimeSeries.ts +++ b/packages/dashboard/src/msw/iot-sitewise/handlers/describeTimeSeries/describeTimeSeries.ts @@ -1,5 +1,8 @@ import { rest } from 'msw'; -import { type DescribeTimeSeriesRequest, type DescribeTimeSeriesResponse } from '@aws-sdk/client-iotsitewise'; +import { + type DescribeTimeSeriesRequest, + type DescribeTimeSeriesResponse, +} from '@aws-sdk/client-iotsitewise'; import { SITEWISE_CONTROL_PLANE_API_BASE_URL } from '../../constants'; import { TIME_SERIES_DESCRIPTIONS } from '../../resources/timeSeries'; @@ -8,9 +11,12 @@ const DESCRIBE_TIME_SERIES_URL = `${SITEWISE_CONTROL_PLANE_API_BASE_URL}/time-se export function describeTimeSeriesHandler() { return rest.get(DESCRIBE_TIME_SERIES_URL, (req, res, ctx) => { - const alias: DescribeTimeSeriesRequest['alias'] = req.url.searchParams.get('alias') ?? undefined; - const assetId: DescribeTimeSeriesRequest['assetId'] = req.url.searchParams.get('assetId') ?? undefined; - const propertyId: DescribeTimeSeriesRequest['propertyId'] = req.url.searchParams.get('propertyId') ?? undefined; + const alias: DescribeTimeSeriesRequest['alias'] = + req.url.searchParams.get('alias') ?? undefined; + const assetId: DescribeTimeSeriesRequest['assetId'] = + req.url.searchParams.get('assetId') ?? undefined; + const propertyId: DescribeTimeSeriesRequest['propertyId'] = + req.url.searchParams.get('propertyId') ?? undefined; if (!alias && !assetId && !propertyId) { return res(ctx.status(400)); @@ -24,15 +30,19 @@ export function describeTimeSeriesHandler() { return res(ctx.status(400)); } - const response: DescribeTimeSeriesResponse | undefined = TIME_SERIES_DESCRIPTIONS.find((timeSeries) => { - if (alias) { - return timeSeries.alias === alias; - } - - if (assetId && propertyId) { - return timeSeries.assetId === assetId && timeSeries.propertyId === propertyId; - } - }); + const response: DescribeTimeSeriesResponse | undefined = + TIME_SERIES_DESCRIPTIONS.find((timeSeries) => { + if (alias) { + return timeSeries.alias === alias; + } + + if (assetId && propertyId) { + return ( + timeSeries.assetId === assetId && + timeSeries.propertyId === propertyId + ); + } + }); if (!response) { return res(ctx.status(404)); diff --git a/packages/dashboard/src/msw/iot-sitewise/handlers/listAssets/listAssetsHandler.ts b/packages/dashboard/src/msw/iot-sitewise/handlers/listAssets/listAssetsHandler.ts index 05978f110..d231e9138 100644 --- a/packages/dashboard/src/msw/iot-sitewise/handlers/listAssets/listAssetsHandler.ts +++ b/packages/dashboard/src/msw/iot-sitewise/handlers/listAssets/listAssetsHandler.ts @@ -1,4 +1,7 @@ -import { type AssetSummary, type ListAssetsResponse } from '@aws-sdk/client-iotsitewise'; +import { + type AssetSummary, + type ListAssetsResponse, +} from '@aws-sdk/client-iotsitewise'; import { rest } from 'msw'; import { LIST_ASSETS_URL } from './constants'; diff --git a/packages/dashboard/src/msw/iot-sitewise/handlers/listAssociatedAssets/listAssociatedAssetsHandler.ts b/packages/dashboard/src/msw/iot-sitewise/handlers/listAssociatedAssets/listAssociatedAssetsHandler.ts index 3060f87d5..3f0193cfa 100644 --- a/packages/dashboard/src/msw/iot-sitewise/handlers/listAssociatedAssets/listAssociatedAssetsHandler.ts +++ b/packages/dashboard/src/msw/iot-sitewise/handlers/listAssociatedAssets/listAssociatedAssetsHandler.ts @@ -1,4 +1,7 @@ -import { type ListAssociatedAssetsResponse, type ListAssociatedAssetsRequest } from '@aws-sdk/client-iotsitewise'; +import { + type ListAssociatedAssetsResponse, + type ListAssociatedAssetsRequest, +} from '@aws-sdk/client-iotsitewise'; import { rest } from 'msw'; import { LIST_ASSOCIATED_ASSETS_URL } from './constants'; @@ -6,43 +9,55 @@ import { ASSET_HIERARCHY } from '../../resources/assets'; import { summarizeAsset } from '../../resources/assets/summarizeAssetDescription'; export function listAssociatedAssetsHandler() { - return rest.get(LIST_ASSOCIATED_ASSETS_URL, (req, res, ctx) => { - const { assetId } = req.params as { assetId: ListAssociatedAssetsRequest['assetId'] }; - const hierarchyId = req.url.searchParams.get('hierarchyId') as ListAssociatedAssetsRequest['hierarchyId']; - const traversalDirection = - (req.url.searchParams.get('traversalDirection') as ListAssociatedAssetsRequest['traversalDirection']) ?? 'CHILD'; - - if (!assetId) { - return res(ctx.status(400)); + return rest.get( + LIST_ASSOCIATED_ASSETS_URL, + (req, res, ctx) => { + const { assetId } = req.params as { + assetId: ListAssociatedAssetsRequest['assetId']; + }; + const hierarchyId = req.url.searchParams.get( + 'hierarchyId' + ) as ListAssociatedAssetsRequest['hierarchyId']; + const traversalDirection = + (req.url.searchParams.get( + 'traversalDirection' + ) as ListAssociatedAssetsRequest['traversalDirection']) ?? 'CHILD'; + + if (!assetId) { + return res(ctx.status(400)); + } + + if (traversalDirection !== 'CHILD' && traversalDirection !== 'PARENT') { + return res(ctx.status(400)); + } + + if (traversalDirection === 'CHILD' && !hierarchyId) { + return res(ctx.status(400)); + } + + let assetSummaries: ListAssociatedAssetsResponse['assetSummaries'] = []; + + if (traversalDirection === 'CHILD') { + const childAssets = + ASSET_HIERARCHY.findChildren(assetId, hierarchyId ?? '') ?? []; + const childAssetSummaries = childAssets.map(summarizeAsset); + + assetSummaries = childAssetSummaries; + } else if (traversalDirection === 'PARENT') { + const parentAsset = ASSET_HIERARCHY.findParentAsset(assetId); + const parentAssetSummary = parentAsset + ? summarizeAsset(parentAsset) + : undefined; + + assetSummaries = parentAssetSummary ? [parentAssetSummary] : []; + } + + const response: ListAssociatedAssetsResponse = { + assetSummaries: assetSummaries, + nextToken: undefined, + }; + + return res(ctx.delay(), ctx.status(200), ctx.json(response)); } - - if (traversalDirection !== 'CHILD' && traversalDirection !== 'PARENT') { - return res(ctx.status(400)); - } - - if (traversalDirection === 'CHILD' && !hierarchyId) { - return res(ctx.status(400)); - } - - let assetSummaries: ListAssociatedAssetsResponse['assetSummaries'] = []; - - if (traversalDirection === 'CHILD') { - const childAssets = ASSET_HIERARCHY.findChildren(assetId, hierarchyId ?? '') ?? []; - const childAssetSummaries = childAssets.map(summarizeAsset); - - assetSummaries = childAssetSummaries; - } else if (traversalDirection === 'PARENT') { - const parentAsset = ASSET_HIERARCHY.findParentAsset(assetId); - const parentAssetSummary = parentAsset ? summarizeAsset(parentAsset) : undefined; - - assetSummaries = parentAssetSummary ? [parentAssetSummary] : []; - } - - const response: ListAssociatedAssetsResponse = { - assetSummaries: assetSummaries, - nextToken: undefined, - }; - - return res(ctx.delay(), ctx.status(200), ctx.json(response)); - }); + ); } diff --git a/packages/dashboard/src/msw/iot-sitewise/handlers/listTimeSeries/listTimeSeries.ts b/packages/dashboard/src/msw/iot-sitewise/handlers/listTimeSeries/listTimeSeries.ts index e7d8c0b62..71838fff3 100644 --- a/packages/dashboard/src/msw/iot-sitewise/handlers/listTimeSeries/listTimeSeries.ts +++ b/packages/dashboard/src/msw/iot-sitewise/handlers/listTimeSeries/listTimeSeries.ts @@ -12,10 +12,13 @@ const LIST_TIME_SERIES_URL = `${SITEWISE_CONTROL_PLANE_API_BASE_URL}/time-series export function listTimeSeriesHandler() { return rest.get(LIST_TIME_SERIES_URL, (req, res, ctx) => { - const aliasPrefix: ListTimeSeriesRequest['aliasPrefix'] = req.url.searchParams.get('aliasPrefix') ?? undefined; - const assetId: ListTimeSeriesRequest['assetId'] = req.url.searchParams.get('assetId') ?? undefined; + const aliasPrefix: ListTimeSeriesRequest['aliasPrefix'] = + req.url.searchParams.get('aliasPrefix') ?? undefined; + const assetId: ListTimeSeriesRequest['assetId'] = + req.url.searchParams.get('assetId') ?? undefined; const timeSeriesType: ListTimeSeriesRequest['timeSeriesType'] = - (req.url.searchParams.get('timeSeriesType') as ListTimeSeriesType) ?? undefined; + (req.url.searchParams.get('timeSeriesType') as ListTimeSeriesType) ?? + undefined; if (timeSeriesType === 'ASSOCIATED' && !assetId) { return res(ctx.status(400)); diff --git a/packages/dashboard/src/msw/iot-sitewise/resources/assetModels/index.ts b/packages/dashboard/src/msw/iot-sitewise/resources/assetModels/index.ts index 1ac76333e..b3627b691 100644 --- a/packages/dashboard/src/msw/iot-sitewise/resources/assetModels/index.ts +++ b/packages/dashboard/src/msw/iot-sitewise/resources/assetModels/index.ts @@ -144,7 +144,10 @@ export const STORAGE_TANK_ASSET_MODEL_HIERARCHY = { export const PRODUCTION_LINE_ASSET_MODEL = assetModelFactory.create({ assetModelName: 'Production Line', assetModelDescription: 'Production Line Asset Model', - assetModelHierarchies: [REACTOR_ASSET_MODEL_HIERARCHY, STORAGE_TANK_ASSET_MODEL_HIERARCHY], + assetModelHierarchies: [ + REACTOR_ASSET_MODEL_HIERARCHY, + STORAGE_TANK_ASSET_MODEL_HIERARCHY, + ], }); const productionLineAssetModelHierarchyId = uuid(); @@ -195,7 +198,12 @@ export const SITE_ASSET_MODEL = assetModelFactory.create({ }); export const ASSET_MODELS = { - models: [SITE_ASSET_MODEL, PRODUCTION_LINE_ASSET_MODEL, REACTOR_ASSET_MODEL, STORAGE_TANK_ASSET_MODEL], + models: [ + SITE_ASSET_MODEL, + PRODUCTION_LINE_ASSET_MODEL, + REACTOR_ASSET_MODEL, + STORAGE_TANK_ASSET_MODEL, + ], getAll: function () { return this.models; }, diff --git a/packages/dashboard/src/msw/iot-sitewise/resources/assets/index.ts b/packages/dashboard/src/msw/iot-sitewise/resources/assets/index.ts index 925ae2c93..e4a686b84 100644 --- a/packages/dashboard/src/msw/iot-sitewise/resources/assets/index.ts +++ b/packages/dashboard/src/msw/iot-sitewise/resources/assets/index.ts @@ -8,7 +8,9 @@ import { import type { Asset, AssetModel } from '../types'; const siteAssetFactory = new AssetFactory(SITE_ASSET_MODEL); -const productionLineAssetFactory = new AssetFactory(PRODUCTION_LINE_ASSET_MODEL); +const productionLineAssetFactory = new AssetFactory( + PRODUCTION_LINE_ASSET_MODEL +); const reactorAssetFactory = new AssetFactory(REACTOR_ASSET_MODEL); const storageTankAssetFactory = new AssetFactory(STORAGE_TANK_ASSET_MODEL); @@ -28,7 +30,10 @@ class AssetHierarchyClient { } public getAssetsByAssetModelId(assetModelId: string): Asset[] { - const assetList = this.#searchByAssetModelId(assetModelId, this.#assetHierarchy); + const assetList = this.#searchByAssetModelId( + assetModelId, + this.#assetHierarchy + ); return assetList; } @@ -41,12 +46,18 @@ class AssetHierarchyClient { public findChildren(assetId: string, hierarchyId: string): Asset[] { const node = this.#searchById(assetId, this.#assetHierarchy); - const hierarchy = node?.asset.assetHierarchies?.find(({ id }) => id === hierarchyId); + const hierarchy = node?.asset.assetHierarchies?.find( + ({ id }) => id === hierarchyId + ); // childAssetModelId is not on Assets - We keep it in the translation from AssetModel to Asset to enable this search. - const hierarchyAsAssetModelHierarchy = hierarchy as NonNullable[number]; + const hierarchyAsAssetModelHierarchy = hierarchy as NonNullable< + AssetModel['assetModelHierarchies'] + >[number]; const childAssetModelId = hierarchyAsAssetModelHierarchy.childAssetModelId; const children = node?.children?.map(({ asset }) => asset) ?? []; - const childrenOfHierachy = children.filter(({ assetModelId }) => assetModelId === childAssetModelId); + const childrenOfHierachy = children.filter( + ({ assetModelId }) => assetModelId === childAssetModelId + ); return childrenOfHierachy; } @@ -58,7 +69,10 @@ class AssetHierarchyClient { return parentAsset; } - #searchById(assetId: string, assetHierarchy: AssetHierarchy): AssetHierarchy[number] | undefined { + #searchById( + assetId: string, + assetHierarchy: AssetHierarchy + ): AssetHierarchy[number] | undefined { for (const node of assetHierarchy) { if (node.asset.assetId === assetId) { return node; @@ -74,7 +88,11 @@ class AssetHierarchyClient { } } - #searchByAssetModelId(assetModelId: string, assetHierarchy: AssetHierarchy, assetList?: Asset[]) { + #searchByAssetModelId( + assetModelId: string, + assetHierarchy: AssetHierarchy, + assetList?: Asset[] + ) { const assetsMatchingModelIdList = assetList ? assetList : []; for (const node of assetHierarchy) { if (node.asset.assetModelId === assetModelId) { @@ -82,7 +100,11 @@ class AssetHierarchyClient { } if (node.children) { - this.#searchByAssetModelId(assetModelId, node.children, assetsMatchingModelIdList); + this.#searchByAssetModelId( + assetModelId, + node.children, + assetsMatchingModelIdList + ); } } return assetsMatchingModelIdList; @@ -121,19 +143,27 @@ export const ASSET_HIERARCHY = new AssetHierarchyClient( ].map(({ assetName }) => ({ asset: siteAssetFactory.create({ assetName }), children: new Array(100).fill(null).map((_, i) => ({ - asset: productionLineAssetFactory.create({ assetName: `Production Line ${i + 1}` }), + asset: productionLineAssetFactory.create({ + assetName: `Production Line ${i + 1}`, + }), children: [ - ...new Array(10) - .fill(null) - .map((_, j) => ({ asset: reactorAssetFactory.create({ assetName: `Reactor ${j + 1}` }) })), - ...new Array(15) - .fill(null) - .map((_, j) => ({ asset: storageTankAssetFactory.create({ assetName: `Storage Tank ${j + 1}` }) })), + ...new Array(10).fill(null).map((_, j) => ({ + asset: reactorAssetFactory.create({ + assetName: `Reactor ${j + 1}`, + }), + })), + ...new Array(15).fill(null).map((_, j) => ({ + asset: storageTankAssetFactory.create({ + assetName: `Storage Tank ${j + 1}`, + }), + })), ], })), })) ); export const giantFlatAssetHierarchy = new AssetHierarchyClient( - new Array(10000).fill(null).map((_, i) => ({ asset: siteAssetFactory.create({ assetName: `Site ${i + 1}` }) })) + new Array(10000).fill(null).map((_, i) => ({ + asset: siteAssetFactory.create({ assetName: `Site ${i + 1}` }), + })) ); diff --git a/packages/dashboard/src/msw/iot-sitewise/resources/timeSeries/TimeSeriesDescriptionFactory.ts b/packages/dashboard/src/msw/iot-sitewise/resources/timeSeries/TimeSeriesDescriptionFactory.ts index 28e8cd6d1..c06fe0e84 100644 --- a/packages/dashboard/src/msw/iot-sitewise/resources/timeSeries/TimeSeriesDescriptionFactory.ts +++ b/packages/dashboard/src/msw/iot-sitewise/resources/timeSeries/TimeSeriesDescriptionFactory.ts @@ -6,8 +6,11 @@ type TimeSeriesDescription = DescribeTimeSeriesResponse; export class TimeSeriesDescriptionFactory { readonly #timeSeriesSummaryFactory = new TimeSeriesSummaryFactory(); - public create(partialDescription: Parameters[0]): TimeSeriesDescription { - const timeSeriesDescription = this.#timeSeriesSummaryFactory.create(partialDescription); + public create( + partialDescription: Parameters[0] + ): TimeSeriesDescription { + const timeSeriesDescription = + this.#timeSeriesSummaryFactory.create(partialDescription); return timeSeriesDescription; } diff --git a/packages/dashboard/src/msw/iot-sitewise/resources/timeSeries/TimeSeriesSummaryFactory.ts b/packages/dashboard/src/msw/iot-sitewise/resources/timeSeries/TimeSeriesSummaryFactory.ts index cb1ef12cc..502fb0d35 100644 --- a/packages/dashboard/src/msw/iot-sitewise/resources/timeSeries/TimeSeriesSummaryFactory.ts +++ b/packages/dashboard/src/msw/iot-sitewise/resources/timeSeries/TimeSeriesSummaryFactory.ts @@ -1,25 +1,37 @@ -import { PropertyDataType, type TimeSeriesSummary } from '@aws-sdk/client-iotsitewise'; +import { + PropertyDataType, + type TimeSeriesSummary, +} from '@aws-sdk/client-iotsitewise'; import { v4 as uuid } from 'uuid'; -type AssociatedTimeSeriesSummary = TimeSeriesSummary & Required>; -type DisassociatedTimeSeriesSummary = Omit & +type AssociatedTimeSeriesSummary = TimeSeriesSummary & + Required>; +type DisassociatedTimeSeriesSummary = Omit< + TimeSeriesSummary, + 'assetId' | 'propertyId' +> & Required>; type PartialAssociatedTimeSeriesSummary = Partial & Required>; -type PartialDisassociatedTimeSeriesSummary = Partial & - Required>; +type PartialDisassociatedTimeSeriesSummary = + Partial & + Required>; function isAssociatedTimeSeriesSummary( - timeSeriesSummary: PartialAssociatedTimeSeriesSummary | PartialDisassociatedTimeSeriesSummary + timeSeriesSummary: + | PartialAssociatedTimeSeriesSummary + | PartialDisassociatedTimeSeriesSummary ): timeSeriesSummary is PartialAssociatedTimeSeriesSummary { return 'assetId' in timeSeriesSummary && 'propertyId' in timeSeriesSummary; } export class TimeSeriesSummaryFactory { public create( - partialSummary: PartialAssociatedTimeSeriesSummary | PartialDisassociatedTimeSeriesSummary + partialSummary: + | PartialAssociatedTimeSeriesSummary + | PartialDisassociatedTimeSeriesSummary ): AssociatedTimeSeriesSummary | DisassociatedTimeSeriesSummary { const timeSeriesSummary = isAssociatedTimeSeriesSummary(partialSummary) ? this.#createAssociatedTimeSeriesSummary(partialSummary) @@ -56,7 +68,13 @@ export class TimeSeriesSummaryFactory { const timeSeriesCreationDate = new Date(); const timeSeriesLastUpdateDate = new Date(); const dataType = 'DOUBLE' as PropertyDataType; - const defaults = { timeSeriesArn, timeSeriesId, timeSeriesCreationDate, timeSeriesLastUpdateDate, dataType }; + const defaults = { + timeSeriesArn, + timeSeriesId, + timeSeriesCreationDate, + timeSeriesLastUpdateDate, + dataType, + }; return defaults; } diff --git a/packages/dashboard/src/msw/iot-sitewise/resources/timeSeries/index.ts b/packages/dashboard/src/msw/iot-sitewise/resources/timeSeries/index.ts index 157a4233d..8688b7723 100644 --- a/packages/dashboard/src/msw/iot-sitewise/resources/timeSeries/index.ts +++ b/packages/dashboard/src/msw/iot-sitewise/resources/timeSeries/index.ts @@ -32,21 +32,26 @@ const propertyMap = [ 'wind-speed', ]; -export const UNMODELED_DATA_STREAMS = new Array(10).fill(null).flatMap((_, i) => { - const timeSeriesSummaries = new Array(100).fill(null).map((_, j) => { - const alias = `/aws/windfarm/${i}/turbine/${j}/${propertyMap[i]}`; - const timeSeriesSummary = timeSeriesSummaryFactory.create({ alias }); +export const UNMODELED_DATA_STREAMS = new Array(10) + .fill(null) + .flatMap((_, i) => { + const timeSeriesSummaries = new Array(100).fill(null).map((_, j) => { + const alias = `/aws/windfarm/${i}/turbine/${j}/${propertyMap[i]}`; + const timeSeriesSummary = timeSeriesSummaryFactory.create({ alias }); + + return timeSeriesSummary; + }); - return timeSeriesSummary; + return timeSeriesSummaries; }); - return timeSeriesSummaries; -}); - export const TIME_SERIES_SUMMARIES = [...UNMODELED_DATA_STREAMS]; -export const TIME_SERIES_DESCRIPTIONS = TIME_SERIES_SUMMARIES.map((timeSeriesSummary) => { - const timeSeriesDescription = timeSeriesDescriptionFactory.create(timeSeriesSummary); +export const TIME_SERIES_DESCRIPTIONS = TIME_SERIES_SUMMARIES.map( + (timeSeriesSummary) => { + const timeSeriesDescription = + timeSeriesDescriptionFactory.create(timeSeriesSummary); - return timeSeriesDescription; -}); + return timeSeriesDescription; + } +); diff --git a/packages/dashboard/src/msw/iot-sitewise/resources/types.ts b/packages/dashboard/src/msw/iot-sitewise/resources/types.ts index a4339e39f..016d7347a 100644 --- a/packages/dashboard/src/msw/iot-sitewise/resources/types.ts +++ b/packages/dashboard/src/msw/iot-sitewise/resources/types.ts @@ -1,4 +1,7 @@ -import type { DescribeAssetResponse, DescribeAssetModelResponse } from '@aws-sdk/client-iotsitewise'; +import type { + DescribeAssetResponse, + DescribeAssetModelResponse, +} from '@aws-sdk/client-iotsitewise'; export interface Asset extends DescribeAssetResponse { assetId: NonNullable; diff --git a/packages/dashboard/src/msw/iot-twinmaker/handlers/executeQuery/executeQueryHandler.ts b/packages/dashboard/src/msw/iot-twinmaker/handlers/executeQuery/executeQueryHandler.ts index e5375bff7..8d40e8471 100644 --- a/packages/dashboard/src/msw/iot-twinmaker/handlers/executeQuery/executeQueryHandler.ts +++ b/packages/dashboard/src/msw/iot-twinmaker/handlers/executeQuery/executeQueryHandler.ts @@ -1,4 +1,7 @@ -import { type ExecuteQueryRequest, type ExecuteQueryResponse } from '@aws-sdk/client-iottwinmaker'; +import { + type ExecuteQueryRequest, + type ExecuteQueryResponse, +} from '@aws-sdk/client-iottwinmaker'; import { rest } from 'msw'; import { WORKSPACE_SUMMARIES } from '../../resources/workspaces'; @@ -11,13 +14,18 @@ import { WORKSPACE_SUMMARIES } from '../../resources/workspaces'; */ export function executeQueryHandler() { return rest.post('', async (req, res, ctx) => { - const { workspaceId, queryStatement } = await req.json(); + const { workspaceId, queryStatement } = + await req.json(); if (!workspaceId || !queryStatement) { return res(ctx.status(400)); } - if (!WORKSPACE_SUMMARIES.find((workspace) => workspace.workspaceId === workspaceId)) { + if ( + !WORKSPACE_SUMMARIES.find( + (workspace) => workspace.workspaceId === workspaceId + ) + ) { return res(ctx.status(404)); } diff --git a/packages/dashboard/src/msw/iot-twinmaker/resources/workspaces/WorkspaceSummaryFactory.ts b/packages/dashboard/src/msw/iot-twinmaker/resources/workspaces/WorkspaceSummaryFactory.ts index b9a77d957..5efc8ba41 100644 --- a/packages/dashboard/src/msw/iot-twinmaker/resources/workspaces/WorkspaceSummaryFactory.ts +++ b/packages/dashboard/src/msw/iot-twinmaker/resources/workspaces/WorkspaceSummaryFactory.ts @@ -1,6 +1,7 @@ import { type WorkspaceSummary } from '@aws-sdk/client-iottwinmaker'; -type PartialWorkspaceSummary = Partial & Required>; +type PartialWorkspaceSummary = Partial & + Required>; export class WorkspaceSummaryFactory { public create(partialSummary: PartialWorkspaceSummary): WorkspaceSummary { @@ -17,7 +18,13 @@ export class WorkspaceSummaryFactory { const description = ''; const creationDateTime = new Date(); const updateDateTime = new Date(); - const defaults = { arn, workspaceId, creationDateTime, updateDateTime, description }; + const defaults = { + arn, + workspaceId, + creationDateTime, + updateDateTime, + description, + }; return defaults; } diff --git a/packages/dashboard/src/store/actions/bringToFront/index.spec.ts b/packages/dashboard/src/store/actions/bringToFront/index.spec.ts index 42963f403..01de61935 100644 --- a/packages/dashboard/src/store/actions/bringToFront/index.spec.ts +++ b/packages/dashboard/src/store/actions/bringToFront/index.spec.ts @@ -18,14 +18,17 @@ const setupDashboardState = ( }); it('does nothing if there are no widgets selected', () => { - expect(bringWidgetsToFront(setupDashboardState([MOCK_KPI_WIDGET])).dashboardConfiguration.widgets).toEqual([ - MOCK_KPI_WIDGET, - ]); + expect( + bringWidgetsToFront(setupDashboardState([MOCK_KPI_WIDGET])) + .dashboardConfiguration.widgets + ).toEqual([MOCK_KPI_WIDGET]); }); it('does nothing if there all widgets are selected', () => { expect( - bringWidgetsToFront(setupDashboardState([MOCK_KPI_WIDGET], [MOCK_KPI_WIDGET])).dashboardConfiguration.widgets + bringWidgetsToFront( + setupDashboardState([MOCK_KPI_WIDGET], [MOCK_KPI_WIDGET]) + ).dashboardConfiguration.widgets ).toEqual([MOCK_KPI_WIDGET]); }); @@ -39,8 +42,9 @@ it('moves selected widget to front', () => { }); expect( - bringWidgetsToFront(setupDashboardState([MOCK_WIDGET, MOCK_WIDGET_2], [MOCK_WIDGET_2])).dashboardConfiguration - .widgets + bringWidgetsToFront( + setupDashboardState([MOCK_WIDGET, MOCK_WIDGET_2], [MOCK_WIDGET_2]) + ).dashboardConfiguration.widgets ).toEqual( expect.arrayContaining([ MOCK_WIDGET, @@ -66,7 +70,10 @@ it('moves group of widgets and retains their relative order', () => { expect( bringWidgetsToFront( - setupDashboardState([MOCK_WIDGET, MOCK_WIDGET_2, MOCK_WIDGET_3], [MOCK_WIDGET_2, MOCK_WIDGET_3]) + setupDashboardState( + [MOCK_WIDGET, MOCK_WIDGET_2, MOCK_WIDGET_3], + [MOCK_WIDGET_2, MOCK_WIDGET_3] + ) ).dashboardConfiguration.widgets ).toEqual( expect.arrayContaining([ diff --git a/packages/dashboard/src/store/actions/bringToFront/index.ts b/packages/dashboard/src/store/actions/bringToFront/index.ts index 4dfbd7dc2..a53b36bd2 100644 --- a/packages/dashboard/src/store/actions/bringToFront/index.ts +++ b/packages/dashboard/src/store/actions/bringToFront/index.ts @@ -35,7 +35,9 @@ export const bringWidgetsToFront = (state: DashboardState): DashboardState => { z: selectedWidgetsIds.includes(widget.id) ? widget.z + zOffset : widget.z, })); - const translatedSelectedWidgets = translatedWidgets.filter((widget) => selectedWidgetsIds.includes(widget.id)); + const translatedSelectedWidgets = translatedWidgets.filter((widget) => + selectedWidgetsIds.includes(widget.id) + ); return { ...state, diff --git a/packages/dashboard/src/store/actions/changeDashboardGrid/changeCellSize.spec.ts b/packages/dashboard/src/store/actions/changeDashboardGrid/changeCellSize.spec.ts index 10abae57b..7b17807c2 100644 --- a/packages/dashboard/src/store/actions/changeDashboardGrid/changeCellSize.spec.ts +++ b/packages/dashboard/src/store/actions/changeDashboardGrid/changeCellSize.spec.ts @@ -1,5 +1,8 @@ import { initialState } from '../../state'; -import { changeDashboardCellSize, onChangeDashboardCellSizeAction } from './changeCellSize'; +import { + changeDashboardCellSize, + onChangeDashboardCellSizeAction, +} from './changeCellSize'; it('can change grid cellSize', () => { expect( diff --git a/packages/dashboard/src/store/actions/changeDashboardGrid/changeCellSize.ts b/packages/dashboard/src/store/actions/changeDashboardGrid/changeCellSize.ts index 20e186f72..867080f18 100644 --- a/packages/dashboard/src/store/actions/changeDashboardGrid/changeCellSize.ts +++ b/packages/dashboard/src/store/actions/changeDashboardGrid/changeCellSize.ts @@ -19,5 +19,8 @@ export const onChangeDashboardCellSizeAction = ( }); // Prevent the dashboard from having a negative cell size which would make an invalid grid. -export const changeDashboardCellSize = (state: DashboardState, action: ChangeDashboardCellSizeAction): DashboardState => +export const changeDashboardCellSize = ( + state: DashboardState, + action: ChangeDashboardCellSizeAction +): DashboardState => changeGridProperty(state, 'cellSize', nonNegative(action.payload.cellSize)); diff --git a/packages/dashboard/src/store/actions/changeDashboardGrid/changeEnabled.spec.ts b/packages/dashboard/src/store/actions/changeDashboardGrid/changeEnabled.spec.ts index c5c7efdb5..45be26fdf 100644 --- a/packages/dashboard/src/store/actions/changeDashboardGrid/changeEnabled.spec.ts +++ b/packages/dashboard/src/store/actions/changeDashboardGrid/changeEnabled.spec.ts @@ -1,5 +1,8 @@ import { initialState } from '../../state'; -import { changeDashboardGridDragEnabled, onChangeDashboardGridEnabledAction } from './changeEnabled'; +import { + changeDashboardGridDragEnabled, + onChangeDashboardGridEnabledAction, +} from './changeEnabled'; it('can change grid enabled', () => { expect( diff --git a/packages/dashboard/src/store/actions/changeDashboardGrid/changeEnabled.ts b/packages/dashboard/src/store/actions/changeDashboardGrid/changeEnabled.ts index 533da1cc5..9245615b7 100644 --- a/packages/dashboard/src/store/actions/changeDashboardGrid/changeEnabled.ts +++ b/packages/dashboard/src/store/actions/changeDashboardGrid/changeEnabled.ts @@ -21,4 +21,8 @@ export const changeDashboardGridDragEnabled = ( state: DashboardState, action: ChangeDashboardGridEnabledAction ): DashboardState => - changeGridProperty(state, 'enabled', action.payload ? action.payload.enabled : !state.grid.enabled); + changeGridProperty( + state, + 'enabled', + action.payload ? action.payload.enabled : !state.grid.enabled + ); diff --git a/packages/dashboard/src/store/actions/changeDashboardGrid/changeHeight.spec.ts b/packages/dashboard/src/store/actions/changeDashboardGrid/changeHeight.spec.ts index f93f7fc90..b70ead7bf 100644 --- a/packages/dashboard/src/store/actions/changeDashboardGrid/changeHeight.spec.ts +++ b/packages/dashboard/src/store/actions/changeDashboardGrid/changeHeight.spec.ts @@ -1,4 +1,7 @@ -import { changeDashboardHeight, onChangeDashboardHeightAction } from './changeHeight'; +import { + changeDashboardHeight, + onChangeDashboardHeightAction, +} from './changeHeight'; import { initialState } from '../../state'; it('can change the height of the dashboard', () => { diff --git a/packages/dashboard/src/store/actions/changeDashboardGrid/changeHeight.ts b/packages/dashboard/src/store/actions/changeDashboardGrid/changeHeight.ts index ff7bd9d78..1ee6625ab 100644 --- a/packages/dashboard/src/store/actions/changeDashboardGrid/changeHeight.ts +++ b/packages/dashboard/src/store/actions/changeDashboardGrid/changeHeight.ts @@ -18,5 +18,8 @@ export const onChangeDashboardHeightAction = ( payload, }); -export const changeDashboardHeight = (state: DashboardState, action: ChangeDashboardHeightAction): DashboardState => +export const changeDashboardHeight = ( + state: DashboardState, + action: ChangeDashboardHeightAction +): DashboardState => changeGridProperty(state, 'height', nonNegative(action.payload.height)); diff --git a/packages/dashboard/src/store/actions/changeDashboardGrid/changeWidth.spec.ts b/packages/dashboard/src/store/actions/changeDashboardGrid/changeWidth.spec.ts index ef1fef92e..23ce20939 100644 --- a/packages/dashboard/src/store/actions/changeDashboardGrid/changeWidth.spec.ts +++ b/packages/dashboard/src/store/actions/changeDashboardGrid/changeWidth.spec.ts @@ -1,4 +1,7 @@ -import { changeDashboardWidth, onChangeDashboardWidthAction } from './changeWidth'; +import { + changeDashboardWidth, + onChangeDashboardWidthAction, +} from './changeWidth'; import { initialState } from '../../state'; it('can change the width of the dashboard', () => { diff --git a/packages/dashboard/src/store/actions/changeDashboardGrid/changeWidth.ts b/packages/dashboard/src/store/actions/changeDashboardGrid/changeWidth.ts index 54b766587..87f8786ea 100644 --- a/packages/dashboard/src/store/actions/changeDashboardGrid/changeWidth.ts +++ b/packages/dashboard/src/store/actions/changeDashboardGrid/changeWidth.ts @@ -18,5 +18,8 @@ export const onChangeDashboardWidthAction = ( payload, }); -export const changeDashboardWidth = (state: DashboardState, action: ChangeDashboardWidthAction): DashboardState => +export const changeDashboardWidth = ( + state: DashboardState, + action: ChangeDashboardWidthAction +): DashboardState => changeGridProperty(state, 'width', nonNegative(action.payload.width)); diff --git a/packages/dashboard/src/store/actions/changeDashboardGrid/updateGrid.spec.ts b/packages/dashboard/src/store/actions/changeDashboardGrid/updateGrid.spec.ts index d92ddb0cf..de11374ab 100644 --- a/packages/dashboard/src/store/actions/changeDashboardGrid/updateGrid.spec.ts +++ b/packages/dashboard/src/store/actions/changeDashboardGrid/updateGrid.spec.ts @@ -2,9 +2,13 @@ import { changeGridProperty } from './updateGrid'; import { initialState } from '../../state'; it('can change a grid property', () => { - expect(changeGridProperty(initialState, 'cellSize', 20).grid.cellSize).toEqual(20); + expect( + changeGridProperty(initialState, 'cellSize', 20).grid.cellSize + ).toEqual(20); expect(changeGridProperty(initialState, 'width', 10).grid.width).toEqual(10); - expect(changeGridProperty(initialState, 'height', 10).grid.height).toEqual(10); + expect(changeGridProperty(initialState, 'height', 10).grid.height).toEqual( + 10 + ); }); diff --git a/packages/dashboard/src/store/actions/changeDashboardGrid/updateGrid.ts b/packages/dashboard/src/store/actions/changeDashboardGrid/updateGrid.ts index 66ba377bc..af0f9edb3 100644 --- a/packages/dashboard/src/store/actions/changeDashboardGrid/updateGrid.ts +++ b/packages/dashboard/src/store/actions/changeDashboardGrid/updateGrid.ts @@ -14,7 +14,10 @@ export const changeGridProperty = ( [property]: value, }; const widgets = state.dashboardConfiguration.widgets.map((w) => { - return constrainWidgetPositionToGrid({ x: 0, y: 0, width: grid.width, height: grid.height }, w); + return constrainWidgetPositionToGrid( + { x: 0, y: 0, width: grid.width, height: grid.height }, + w + ); }); return { diff --git a/packages/dashboard/src/store/actions/copyWidgets/index.spec.ts b/packages/dashboard/src/store/actions/copyWidgets/index.spec.ts index 5f359bee8..0a9ae2860 100644 --- a/packages/dashboard/src/store/actions/copyWidgets/index.spec.ts +++ b/packages/dashboard/src/store/actions/copyWidgets/index.spec.ts @@ -1,11 +1,18 @@ import { copyWidgets, onCopyWidgetsAction } from '.'; import { initialState } from '../../state'; -import { MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET, MOCK_SCATTER_CHART_WIDGET } from '../../../../testing/mocks'; +import { + MOCK_KPI_WIDGET, + MOCK_LINE_CHART_WIDGET, + MOCK_SCATTER_CHART_WIDGET, +} from '../../../../testing/mocks'; import type { DashboardState } from '../../state'; import type { DashboardWidget } from '~/types'; -const setupDashboardState = (widgets: DashboardWidget[] = [], pasteCounter = 0): DashboardState => ({ +const setupDashboardState = ( + widgets: DashboardWidget[] = [], + pasteCounter = 0 +): DashboardState => ({ ...initialState, dashboardConfiguration: { ...initialState.dashboardConfiguration, diff --git a/packages/dashboard/src/store/actions/copyWidgets/index.ts b/packages/dashboard/src/store/actions/copyWidgets/index.ts index 4999e0b9d..4c19a61f3 100644 --- a/packages/dashboard/src/store/actions/copyWidgets/index.ts +++ b/packages/dashboard/src/store/actions/copyWidgets/index.ts @@ -12,13 +12,22 @@ export interface CopyWidgetsAction extends Action { payload: CopyWidgetsActionPayload; } -export const onCopyWidgetsAction = (payload: CopyWidgetsActionPayload): CopyWidgetsAction => ({ +export const onCopyWidgetsAction = ( + payload: CopyWidgetsActionPayload +): CopyWidgetsAction => ({ type: 'COPY_WIDGETS', payload, }); -export const copyWidgets = (state: DashboardState, action: CopyWidgetsAction): DashboardState => { - const copiedWidgets = intersectionBy(state.dashboardConfiguration.widgets, action.payload.widgets, 'id'); +export const copyWidgets = ( + state: DashboardState, + action: CopyWidgetsAction +): DashboardState => { + const copiedWidgets = intersectionBy( + state.dashboardConfiguration.widgets, + action.payload.widgets, + 'id' + ); return { ...state, diff --git a/packages/dashboard/src/store/actions/createWidget/index.spec.ts b/packages/dashboard/src/store/actions/createWidget/index.spec.ts index 7331256a9..9437018f0 100644 --- a/packages/dashboard/src/store/actions/createWidget/index.spec.ts +++ b/packages/dashboard/src/store/actions/createWidget/index.spec.ts @@ -1,7 +1,11 @@ import { createWidgets, onCreateWidgetsAction } from '.'; import { initialState } from '../../state'; -import { MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET, MOCK_SCATTER_CHART_WIDGET } from '../../../../testing/mocks'; +import { + MOCK_KPI_WIDGET, + MOCK_LINE_CHART_WIDGET, + MOCK_SCATTER_CHART_WIDGET, +} from '../../../../testing/mocks'; it('does nothing if no widgets are provided', () => { expect( @@ -58,7 +62,12 @@ it('adds multiple widgets to a dashboard with existing widgets', () => { widgets: [MOCK_LINE_CHART_WIDGET, MOCK_SCATTER_CHART_WIDGET], }) ).dashboardConfiguration.widgets - ).toEqual([MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET, MOCK_LINE_CHART_WIDGET, MOCK_SCATTER_CHART_WIDGET]); + ).toEqual([ + MOCK_KPI_WIDGET, + MOCK_LINE_CHART_WIDGET, + MOCK_LINE_CHART_WIDGET, + MOCK_SCATTER_CHART_WIDGET, + ]); }); it('selects the widgets that are created', () => { diff --git a/packages/dashboard/src/store/actions/createWidget/index.ts b/packages/dashboard/src/store/actions/createWidget/index.ts index 908a3fdb1..56bfaeef3 100644 --- a/packages/dashboard/src/store/actions/createWidget/index.ts +++ b/packages/dashboard/src/store/actions/createWidget/index.ts @@ -13,14 +13,24 @@ export interface CreateWidgetsAction extends Action { payload: CreateWidgetsActionPayload; } -export const onCreateWidgetsAction = (payload: CreateWidgetsActionPayload): CreateWidgetsAction => ({ +export const onCreateWidgetsAction = ( + payload: CreateWidgetsActionPayload +): CreateWidgetsAction => ({ type: 'CREATE_WIDGETS', payload, }); -export const createWidgets = (state: DashboardState, action: CreateWidgetsAction): DashboardState => { +export const createWidgets = ( + state: DashboardState, + action: CreateWidgetsAction +): DashboardState => { const widgets = action.payload.widgets - .map((w) => constrainWidgetPositionToGrid({ x: 0, y: 0, width: state.grid.width, height: state.grid.height }, w)) + .map((w) => + constrainWidgetPositionToGrid( + { x: 0, y: 0, width: state.grid.width, height: state.grid.height }, + w + ) + ) .map(trimRectPosition); return { diff --git a/packages/dashboard/src/store/actions/createWidget/presets/index.ts b/packages/dashboard/src/store/actions/createWidget/presets/index.ts index 69416e822..94c716c92 100644 --- a/packages/dashboard/src/store/actions/createWidget/presets/index.ts +++ b/packages/dashboard/src/store/actions/createWidget/presets/index.ts @@ -16,7 +16,8 @@ export const widgetCreator = const { properties, initialSize } = WidgetPropertiesGeneratorMap[type]; - const { width: widgetPixelWidth, height: widgetPixelHeight } = initialSize || { height: 150, width: 150 }; + const { width: widgetPixelWidth, height: widgetPixelHeight } = + initialSize || { height: 150, width: 150 }; const preset = { id: nanoid(), diff --git a/packages/dashboard/src/store/actions/deleteWidgets/index.spec.ts b/packages/dashboard/src/store/actions/deleteWidgets/index.spec.ts index 2152c83b1..656ec22ca 100644 --- a/packages/dashboard/src/store/actions/deleteWidgets/index.spec.ts +++ b/packages/dashboard/src/store/actions/deleteWidgets/index.spec.ts @@ -5,7 +5,9 @@ import { MOCK_KPI_WIDGET, MockWidgetFactory } from '../../../../testing/mocks'; import type { DashboardState } from '../../state'; import type { DashboardWidget } from '~/types'; -const setupDashboardState = (widgets: DashboardWidget[] = []): DashboardState => ({ +const setupDashboardState = ( + widgets: DashboardWidget[] = [] +): DashboardState => ({ ...initialState, dashboardConfiguration: { ...initialState.dashboardConfiguration, diff --git a/packages/dashboard/src/store/actions/deleteWidgets/index.ts b/packages/dashboard/src/store/actions/deleteWidgets/index.ts index 7285511e4..28b37ad53 100644 --- a/packages/dashboard/src/store/actions/deleteWidgets/index.ts +++ b/packages/dashboard/src/store/actions/deleteWidgets/index.ts @@ -11,20 +11,29 @@ export interface DeleteWidgetsAction extends Action { payload: DeleteWidgetsActionPayload; } -export const onDeleteWidgetsAction = (payload: DeleteWidgetsActionPayload): DeleteWidgetsAction => ({ +export const onDeleteWidgetsAction = ( + payload: DeleteWidgetsActionPayload +): DeleteWidgetsAction => ({ type: 'DELETE_WIDGETS', payload, }); -export const deleteWidgets = (state: DashboardState, action: DeleteWidgetsAction): DashboardState => { +export const deleteWidgets = ( + state: DashboardState, + action: DeleteWidgetsAction +): DashboardState => { const widgetIdsToDelete = action.payload.widgets.map(({ id }) => id); return { ...state, dashboardConfiguration: { ...state.dashboardConfiguration, - widgets: state.dashboardConfiguration.widgets.filter(({ id }) => !widgetIdsToDelete.includes(id)), + widgets: state.dashboardConfiguration.widgets.filter( + ({ id }) => !widgetIdsToDelete.includes(id) + ), }, - selectedWidgets: state.selectedWidgets.filter(({ id }) => !widgetIdsToDelete.includes(id)), + selectedWidgets: state.selectedWidgets.filter( + ({ id }) => !widgetIdsToDelete.includes(id) + ), }; }; diff --git a/packages/dashboard/src/store/actions/moveWidgets/index.spec.ts b/packages/dashboard/src/store/actions/moveWidgets/index.spec.ts index d0dae966e..a37af0bbf 100644 --- a/packages/dashboard/src/store/actions/moveWidgets/index.spec.ts +++ b/packages/dashboard/src/store/actions/moveWidgets/index.spec.ts @@ -5,7 +5,9 @@ import { initialState } from '../../state'; import { MOCK_KPI_WIDGET, MockWidgetFactory } from '../../../../testing/mocks'; import type { DashboardWidget } from '~/types'; -const setupDashboardState = (widgets: DashboardWidget[] = []): DashboardState => ({ +const setupDashboardState = ( + widgets: DashboardWidget[] = [] +): DashboardState => ({ ...initialState, grid: { ...initialState.grid, @@ -32,7 +34,12 @@ describe('move', () => { }); it('shifts a single selected widget by a fractional amount when position changes slightly', () => { - const widget = MockWidgetFactory.getKpiWidget({ x: 1, y: 1, width: 5, height: 5 }); + const widget = MockWidgetFactory.getKpiWidget({ + x: 1, + y: 1, + width: 5, + height: 5, + }); expect( moveWidgets( @@ -53,7 +60,12 @@ describe('move', () => { }); it('does not shift off of the grid', () => { - const widget = MockWidgetFactory.getKpiWidget({ x: 0, y: 0, width: 5, height: 5 }); + const widget = MockWidgetFactory.getKpiWidget({ + x: 0, + y: 0, + width: 5, + height: 5, + }); const dashboardState = setupDashboardState([widget]); expect( @@ -75,8 +87,18 @@ describe('move', () => { }); it('does not shift widget that is not selected', () => { - const widget1 = MockWidgetFactory.getKpiWidget({ x: 0, y: 0, width: 5, height: 5 }); - const widget2 = MockWidgetFactory.getKpiWidget({ x: 6, y: 6, width: 5, height: 5 }); + const widget1 = MockWidgetFactory.getKpiWidget({ + x: 0, + y: 0, + width: 5, + height: 5, + }); + const widget2 = MockWidgetFactory.getKpiWidget({ + x: 6, + y: 6, + width: 5, + height: 5, + }); expect( moveWidgets( @@ -101,7 +123,12 @@ describe('move', () => { }); it('does not shift widget when the vector is 0, 0', () => { - const widget = MockWidgetFactory.getKpiWidget({ x: 0, y: 0, width: 5, height: 5 }); + const widget = MockWidgetFactory.getKpiWidget({ + x: 0, + y: 0, + width: 5, + height: 5, + }); expect( moveWidgets( @@ -122,9 +149,24 @@ describe('move', () => { }); it('shifts only selected widgets when multiple widgets are on the dashboard', () => { - const widget1 = MockWidgetFactory.getKpiWidget({ x: 0, y: 0, width: 5, height: 5 }); - const widget2 = MockWidgetFactory.getKpiWidget({ x: 6, y: 6, width: 5, height: 5 }); - const widget3 = MockWidgetFactory.getKpiWidget({ x: 14, y: 14, width: 5, height: 5 }); + const widget1 = MockWidgetFactory.getKpiWidget({ + x: 0, + y: 0, + width: 5, + height: 5, + }); + const widget2 = MockWidgetFactory.getKpiWidget({ + x: 6, + y: 6, + width: 5, + height: 5, + }); + const widget3 = MockWidgetFactory.getKpiWidget({ + x: 14, + y: 14, + width: 5, + height: 5, + }); expect( moveWidgets( @@ -153,7 +195,12 @@ describe('move', () => { }); it('snaps widgets to the grid when the move action is complete', () => { - const widget1 = MockWidgetFactory.getKpiWidget({ x: 0, y: 0, width: 5, height: 5 }); + const widget1 = MockWidgetFactory.getKpiWidget({ + x: 0, + y: 0, + width: 5, + height: 5, + }); expect( moveWidgets( @@ -175,8 +222,18 @@ describe('move', () => { }); it('does not move widget group out of the grid', () => { - const widget1 = MockWidgetFactory.getKpiWidget({ x: 0, y: 0, width: 5, height: 5 }); - const widget2 = MockWidgetFactory.getKpiWidget({ x: 5, y: 5, width: 5, height: 5 }); + const widget1 = MockWidgetFactory.getKpiWidget({ + x: 0, + y: 0, + width: 5, + height: 5, + }); + const widget2 = MockWidgetFactory.getKpiWidget({ + x: 5, + y: 5, + width: 5, + height: 5, + }); const dashboardState = setupDashboardState([widget1, widget2]); expect( @@ -184,7 +241,10 @@ describe('move', () => { dashboardState, onMoveWidgetsAction({ widgets: [widget1, widget2], - vector: { x: dashboardState.grid.width, y: dashboardState.grid.height }, + vector: { + x: dashboardState.grid.width, + y: dashboardState.grid.height, + }, }) ).dashboardConfiguration.widgets ).toEqual( diff --git a/packages/dashboard/src/store/actions/moveWidgets/index.ts b/packages/dashboard/src/store/actions/moveWidgets/index.ts index 3a2767cd1..1cbe7a387 100644 --- a/packages/dashboard/src/store/actions/moveWidgets/index.ts +++ b/packages/dashboard/src/store/actions/moveWidgets/index.ts @@ -17,12 +17,17 @@ export interface MoveWidgetsAction extends Action { payload: MoveWidgetsActionPayload; } -export const onMoveWidgetsAction = (payload: MoveWidgetsActionPayload): MoveWidgetsAction => ({ +export const onMoveWidgetsAction = ( + payload: MoveWidgetsActionPayload +): MoveWidgetsAction => ({ type: 'MOVE_WIDGETS', payload, }); -export const moveWidgets = (state: DashboardState, action: MoveWidgetsAction): DashboardState => { +export const moveWidgets = ( + state: DashboardState, + action: MoveWidgetsAction +): DashboardState => { const { vector, complete, widgets } = action.payload; const selectedWidgetIds = action.payload.widgets.map((w) => w.id); const selectionBox = getSelectionBox(widgets); @@ -35,7 +40,11 @@ export const moveWidgets = (state: DashboardState, action: MoveWidgetsAction): D }); const mover = (widget: DashboardWidget) => - transformWidget(widget, selectionBox, complete ? trimRectPosition(newSelectionBox) : newSelectionBox); + transformWidget( + widget, + selectionBox, + complete ? trimRectPosition(newSelectionBox) : newSelectionBox + ); const updateWidgets = (widgets: DashboardWidget[]) => widgets.map((widget) => { diff --git a/packages/dashboard/src/store/actions/pasteWidgets/index.spec.ts b/packages/dashboard/src/store/actions/pasteWidgets/index.spec.ts index 6bab6931c..7f804eb77 100644 --- a/packages/dashboard/src/store/actions/pasteWidgets/index.spec.ts +++ b/packages/dashboard/src/store/actions/pasteWidgets/index.spec.ts @@ -1,7 +1,10 @@ import { onPasteWidgetsAction, pasteWidgets } from '.'; import { initialState } from '../../state'; -import { MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET } from '../../../../testing/mocks'; +import { + MOCK_KPI_WIDGET, + MOCK_LINE_CHART_WIDGET, +} from '../../../../testing/mocks'; import type { DashboardState } from '../../state'; import type { DashboardWidget } from '~/types'; @@ -24,13 +27,18 @@ const setupDashboardState = ( }); it('does nothing when pasting with nothing in the copy group', () => { - expect(pasteWidgets(setupDashboardState(), onPasteWidgetsAction({})).dashboardConfiguration.widgets).toEqual([]); + expect( + pasteWidgets(setupDashboardState(), onPasteWidgetsAction({})) + .dashboardConfiguration.widgets + ).toEqual([]); }); it('paste single widget', () => { expect( - pasteWidgets(setupDashboardState([MOCK_KPI_WIDGET], [MOCK_KPI_WIDGET]), onPasteWidgetsAction({})) - .dashboardConfiguration.widgets + pasteWidgets( + setupDashboardState([MOCK_KPI_WIDGET], [MOCK_KPI_WIDGET]), + onPasteWidgetsAction({}) + ).dashboardConfiguration.widgets ).toEqual( expect.arrayContaining([ expect.objectContaining({ @@ -48,9 +56,14 @@ it('paste single widget', () => { }); it('paste single widget a second time, shifts the position down', () => { - const state = pasteWidgets(setupDashboardState([MOCK_KPI_WIDGET], [MOCK_KPI_WIDGET]), onPasteWidgetsAction({})); + const state = pasteWidgets( + setupDashboardState([MOCK_KPI_WIDGET], [MOCK_KPI_WIDGET]), + onPasteWidgetsAction({}) + ); - expect(pasteWidgets(state, onPasteWidgetsAction({})).dashboardConfiguration.widgets).toEqual( + expect( + pasteWidgets(state, onPasteWidgetsAction({})).dashboardConfiguration.widgets + ).toEqual( expect.arrayContaining([ expect.objectContaining({ type: MOCK_KPI_WIDGET.type, @@ -74,7 +87,10 @@ it('paste single widget a second time, shifts the position down', () => { it('paste multiple widgets', () => { expect( pasteWidgets( - setupDashboardState([MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET], [MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET]), + setupDashboardState( + [MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET], + [MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET] + ), onPasteWidgetsAction({}) ).dashboardConfiguration.widgets ).toEqual( @@ -130,7 +146,10 @@ it('pastes a widget at a specific location', () => { it('pastes multiple widgets at a specific location', () => { expect( pasteWidgets( - setupDashboardState([MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET], [MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET]), + setupDashboardState( + [MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET], + [MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET] + ), onPasteWidgetsAction({ position: { x: 100, y: 100 }, }) @@ -163,7 +182,10 @@ it('pastes multiple widgets at a specific location', () => { it('selects the widgets that are pasted', () => { const selectedWidgets = pasteWidgets( - setupDashboardState([MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET], [MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET]), + setupDashboardState( + [MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET], + [MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET] + ), onPasteWidgetsAction({ position: { x: 100, y: 100 }, }) @@ -229,7 +251,10 @@ it('pastes a widget at right edge location', () => { it('pastes multiple widgets at right edge location', () => { expect( pasteWidgets( - setupDashboardState([MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET], [MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET]), + setupDashboardState( + [MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET], + [MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET] + ), onPasteWidgetsAction({ position: { x: 1000, y: 1000 }, }) diff --git a/packages/dashboard/src/store/actions/pasteWidgets/index.ts b/packages/dashboard/src/store/actions/pasteWidgets/index.ts index 300103ded..e541ae18d 100644 --- a/packages/dashboard/src/store/actions/pasteWidgets/index.ts +++ b/packages/dashboard/src/store/actions/pasteWidgets/index.ts @@ -12,12 +12,17 @@ export interface PasteWidgetsAction extends Action { payload: PasteWidgetsActionPayload; } -export const onPasteWidgetsAction = (payload: PasteWidgetsActionPayload): PasteWidgetsAction => ({ +export const onPasteWidgetsAction = ( + payload: PasteWidgetsActionPayload +): PasteWidgetsAction => ({ type: 'PASTE_WIDGETS', payload, }); -export const pasteWidgets = (state: DashboardState, action: PasteWidgetsAction): DashboardState => { +export const pasteWidgets = ( + state: DashboardState, + action: PasteWidgetsAction +): DashboardState => { const { position } = action.payload; const cellSize = state.grid.cellSize; @@ -43,11 +48,13 @@ export const pasteWidgets = (state: DashboardState, action: PasteWidgetsAction): }; // getting widgets group's left most cell value - const leftmostWidget: DashboardWidget = minBy(copyGroup, 'x') || copyGroup[0]; + const leftmostWidget: DashboardWidget = + minBy(copyGroup, 'x') || copyGroup[0]; const groupLeftX = leftmostWidget.x; // getting widgets group's top most cell value - const topmostWidget: DashboardWidget = minBy(copyGroup, 'y') || copyGroup[0]; + const topmostWidget: DashboardWidget = + minBy(copyGroup, 'y') || copyGroup[0]; const groupTopY = topmostWidget.y; // getting widgets group's right most cell value diff --git a/packages/dashboard/src/store/actions/resizeWidgets/index.spec.ts b/packages/dashboard/src/store/actions/resizeWidgets/index.spec.ts index d0ed56752..bd0c021e6 100644 --- a/packages/dashboard/src/store/actions/resizeWidgets/index.spec.ts +++ b/packages/dashboard/src/store/actions/resizeWidgets/index.spec.ts @@ -5,7 +5,9 @@ import type { DashboardState } from '../../state'; import { initialState } from '../../state'; import type { DashboardWidget } from '~/types'; -const setupDashboardState = (widgets: DashboardWidget[] = []): DashboardState => ({ +const setupDashboardState = ( + widgets: DashboardWidget[] = [] +): DashboardState => ({ ...initialState, grid: { ...initialState.grid, diff --git a/packages/dashboard/src/store/actions/resizeWidgets/index.ts b/packages/dashboard/src/store/actions/resizeWidgets/index.ts index eca20aad5..2e87b1fd2 100644 --- a/packages/dashboard/src/store/actions/resizeWidgets/index.ts +++ b/packages/dashboard/src/store/actions/resizeWidgets/index.ts @@ -6,7 +6,15 @@ import type { DashboardState } from '../../state'; import { transformWidget } from '~/util/transformWidget'; import { resizeSelectionBox } from '~/util/resizeSelectionBox'; -export type Anchor = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'left' | 'right' | 'top' | 'bottom'; +export type Anchor = + | 'top-right' + | 'top-left' + | 'bottom-right' + | 'bottom-left' + | 'left' + | 'right' + | 'top' + | 'bottom'; type ResizeWidgetsActionPayload = { anchor: Anchor; widgets: DashboardWidget[]; @@ -19,12 +27,17 @@ export interface ResizeWidgetsAction extends Action { payload: ResizeWidgetsActionPayload; } -export const onResizeWidgetsAction = (payload: ResizeWidgetsActionPayload): ResizeWidgetsAction => ({ +export const onResizeWidgetsAction = ( + payload: ResizeWidgetsActionPayload +): ResizeWidgetsAction => ({ type: 'RESIZE_WIDGETS', payload, }); -export const resizeWidgets = (state: DashboardState, action: ResizeWidgetsAction): DashboardState => { +export const resizeWidgets = ( + state: DashboardState, + action: ResizeWidgetsAction +): DashboardState => { const { anchor, widgets, vector, complete } = action.payload; const selectedWidgetIds = widgets.map((w) => w.id); @@ -41,7 +54,11 @@ export const resizeWidgets = (state: DashboardState, action: ResizeWidgetsAction }); const resizer = (widget: DashboardWidget) => - transformWidget(widget, selectionBox, complete ? trimRectPosition(newSelectionBox) : newSelectionBox); + transformWidget( + widget, + selectionBox, + complete ? trimRectPosition(newSelectionBox) : newSelectionBox + ); const updateWidgets = (widgets: DashboardWidget[]) => widgets.map((widget) => { diff --git a/packages/dashboard/src/store/actions/selectWidgets/index.spec.ts b/packages/dashboard/src/store/actions/selectWidgets/index.spec.ts index 2a91e8741..1e344e335 100644 --- a/packages/dashboard/src/store/actions/selectWidgets/index.spec.ts +++ b/packages/dashboard/src/store/actions/selectWidgets/index.spec.ts @@ -1,14 +1,22 @@ import { selectWidgets, onSelectWidgetsAction } from '.'; import { initialState } from '../../state'; -import { MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET, MOCK_SCATTER_CHART_WIDGET } from '../../../../testing/mocks'; +import { + MOCK_KPI_WIDGET, + MOCK_LINE_CHART_WIDGET, + MOCK_SCATTER_CHART_WIDGET, +} from '../../../../testing/mocks'; import type { DashboardState } from '../../state'; const dashboardState: DashboardState = { ...initialState, dashboardConfiguration: { ...initialState.dashboardConfiguration, - widgets: [MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET, MOCK_SCATTER_CHART_WIDGET], + widgets: [ + MOCK_KPI_WIDGET, + MOCK_LINE_CHART_WIDGET, + MOCK_SCATTER_CHART_WIDGET, + ], }, }; @@ -85,5 +93,9 @@ it('adds multiple widgets to a selection', () => { union: true, }) ).selectedWidgets - ).toEqual([MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET, MOCK_SCATTER_CHART_WIDGET]); + ).toEqual([ + MOCK_KPI_WIDGET, + MOCK_LINE_CHART_WIDGET, + MOCK_SCATTER_CHART_WIDGET, + ]); }); diff --git a/packages/dashboard/src/store/actions/selectWidgets/index.ts b/packages/dashboard/src/store/actions/selectWidgets/index.ts index 0520e9497..a5c024163 100644 --- a/packages/dashboard/src/store/actions/selectWidgets/index.ts +++ b/packages/dashboard/src/store/actions/selectWidgets/index.ts @@ -13,12 +13,17 @@ export interface SelectWidgetsAction extends Action { payload: SelectWidgetsActionPayload; } -export const onSelectWidgetsAction = (payload: SelectWidgetsActionPayload): SelectWidgetsAction => ({ +export const onSelectWidgetsAction = ( + payload: SelectWidgetsActionPayload +): SelectWidgetsAction => ({ type: 'SELECT_WIDGETS', payload, }); -export const selectWidgets = (state: DashboardState, action: SelectWidgetsAction): DashboardState => ({ +export const selectWidgets = ( + state: DashboardState, + action: SelectWidgetsAction +): DashboardState => ({ ...state, selectedWidgets: action.payload.union ? uniqBy([...state.selectedWidgets, ...action.payload.widgets], 'id') diff --git a/packages/dashboard/src/store/actions/sendToBack/index.spec.ts b/packages/dashboard/src/store/actions/sendToBack/index.spec.ts index 6873b51dd..42c1f1293 100644 --- a/packages/dashboard/src/store/actions/sendToBack/index.spec.ts +++ b/packages/dashboard/src/store/actions/sendToBack/index.spec.ts @@ -17,14 +17,16 @@ const setupDashboardState = ( }); it('does nothing if there are no widgets selected', () => { - expect(sendWidgetsToBack(setupDashboardState([MOCK_KPI_WIDGET])).dashboardConfiguration.widgets).toEqual([ - MOCK_KPI_WIDGET, - ]); + expect( + sendWidgetsToBack(setupDashboardState([MOCK_KPI_WIDGET])) + .dashboardConfiguration.widgets + ).toEqual([MOCK_KPI_WIDGET]); }); it('does nothing if all widgets are selected', () => { expect( - sendWidgetsToBack(setupDashboardState([MOCK_KPI_WIDGET], [MOCK_KPI_WIDGET])).dashboardConfiguration.widgets + sendWidgetsToBack(setupDashboardState([MOCK_KPI_WIDGET], [MOCK_KPI_WIDGET])) + .dashboardConfiguration.widgets ).toEqual([MOCK_KPI_WIDGET]); }); @@ -38,7 +40,9 @@ it('moves selected widget to back', () => { }); expect( - sendWidgetsToBack(setupDashboardState([MOCK_WIDGET, MOCK_WIDGET_2], [MOCK_WIDGET])).dashboardConfiguration.widgets + sendWidgetsToBack( + setupDashboardState([MOCK_WIDGET, MOCK_WIDGET_2], [MOCK_WIDGET]) + ).dashboardConfiguration.widgets ).toEqual( expect.arrayContaining([ expect.objectContaining({ @@ -63,8 +67,12 @@ it('moves group of widgets and retains their relative order', () => { }); expect( - sendWidgetsToBack(setupDashboardState([MOCK_WIDGET, MOCK_WIDGET_2, MOCK_WIDGET_3], [MOCK_WIDGET, MOCK_WIDGET_2])) - .dashboardConfiguration.widgets + sendWidgetsToBack( + setupDashboardState( + [MOCK_WIDGET, MOCK_WIDGET_2, MOCK_WIDGET_3], + [MOCK_WIDGET, MOCK_WIDGET_2] + ) + ).dashboardConfiguration.widgets ).toEqual( expect.arrayContaining([ expect.objectContaining({ diff --git a/packages/dashboard/src/store/actions/sendToBack/index.ts b/packages/dashboard/src/store/actions/sendToBack/index.ts index 041ece2c3..89a9604b1 100644 --- a/packages/dashboard/src/store/actions/sendToBack/index.ts +++ b/packages/dashboard/src/store/actions/sendToBack/index.ts @@ -35,7 +35,9 @@ export const sendWidgetsToBack = (state: DashboardState): DashboardState => { z: selectedWidgetsIds.includes(widget.id) ? widget.z + zOffset : widget.z, })); - const translatedSelectedWidgets = translatedWidgets.filter((widget) => selectedWidgetsIds.includes(widget.id)); + const translatedSelectedWidgets = translatedWidgets.filter((widget) => + selectedWidgetsIds.includes(widget.id) + ); return { ...state, diff --git a/packages/dashboard/src/store/actions/toggleReadOnly/index.spec.ts b/packages/dashboard/src/store/actions/toggleReadOnly/index.spec.ts index 09cea5284..eef36a381 100644 --- a/packages/dashboard/src/store/actions/toggleReadOnly/index.spec.ts +++ b/packages/dashboard/src/store/actions/toggleReadOnly/index.spec.ts @@ -1,11 +1,17 @@ import { toggleReadOnly } from '.'; import { initialState } from '../../state'; -import { MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET } from '../../../../testing/mocks'; +import { + MOCK_KPI_WIDGET, + MOCK_LINE_CHART_WIDGET, +} from '../../../../testing/mocks'; import type { DashboardState } from '../../state'; import type { DashboardWidget } from '~/types'; -const setupDashboardState = (widgets: DashboardWidget[] = [], pasteCounter = 0): DashboardState => ({ +const setupDashboardState = ( + widgets: DashboardWidget[] = [], + pasteCounter = 0 +): DashboardState => ({ ...initialState, dashboardConfiguration: { ...initialState.dashboardConfiguration, @@ -20,11 +26,19 @@ it('can toggle the state if no widgets are present', () => { }); it('can toggle the state if widgets are present', () => { - expect(toggleReadOnly(setupDashboardState([MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET])).readOnly).toEqual(true); + expect( + toggleReadOnly( + setupDashboardState([MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET]) + ).readOnly + ).toEqual(true); }); it('can toggle the state back and forth', () => { expect( - toggleReadOnly(toggleReadOnly(setupDashboardState([MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET]))).readOnly + toggleReadOnly( + toggleReadOnly( + setupDashboardState([MOCK_KPI_WIDGET, MOCK_LINE_CHART_WIDGET]) + ) + ).readOnly ).toEqual(false); }); diff --git a/packages/dashboard/src/store/actions/updateSignificantDigits/index.spec.ts b/packages/dashboard/src/store/actions/updateSignificantDigits/index.spec.ts index 055725ed6..02cdf644a 100644 --- a/packages/dashboard/src/store/actions/updateSignificantDigits/index.spec.ts +++ b/packages/dashboard/src/store/actions/updateSignificantDigits/index.spec.ts @@ -1,5 +1,8 @@ import { initialState } from '~/store/state'; -import { updateSignificantDigits, onUpdateSignificantDigitsAction } from './index'; +import { + updateSignificantDigits, + onUpdateSignificantDigitsAction, +} from './index'; it('can change significant digits', () => { expect( diff --git a/packages/dashboard/src/store/actions/updateWidget/index.spec.ts b/packages/dashboard/src/store/actions/updateWidget/index.spec.ts index 3635e065d..3560ba7fb 100644 --- a/packages/dashboard/src/store/actions/updateWidget/index.spec.ts +++ b/packages/dashboard/src/store/actions/updateWidget/index.spec.ts @@ -5,7 +5,9 @@ import { MOCK_TEXT_WIDGET, MockWidgetFactory } from '../../../../testing/mocks'; import type { DashboardState } from '../../state'; import type { DashboardWidget } from '~/types'; -const setupDashboardState = (widgets: DashboardWidget[] = []): DashboardState => ({ +const setupDashboardState = ( + widgets: DashboardWidget[] = [] +): DashboardState => ({ ...initialState, dashboardConfiguration: { ...initialState.dashboardConfiguration, diff --git a/packages/dashboard/src/store/actions/updateWidget/index.ts b/packages/dashboard/src/store/actions/updateWidget/index.ts index 02dccd85e..84e577c97 100644 --- a/packages/dashboard/src/store/actions/updateWidget/index.ts +++ b/packages/dashboard/src/store/actions/updateWidget/index.ts @@ -13,14 +13,24 @@ export interface UpdateWidgetsAction extends Action { payload: UpdateWidgetsActionPayload; } -export const onUpdateWidgetsAction = (payload: UpdateWidgetsActionPayload): UpdateWidgetsAction => ({ +export const onUpdateWidgetsAction = ( + payload: UpdateWidgetsActionPayload +): UpdateWidgetsAction => ({ type: 'UPDATE_WIDGET', payload, }); -export const updateWidgets = (state: DashboardState, action: UpdateWidgetsAction): DashboardState => { +export const updateWidgets = ( + state: DashboardState, + action: UpdateWidgetsAction +): DashboardState => { const widgets = action.payload.widgets - .map((w) => constrainWidgetPositionToGrid({ x: 0, y: 0, width: state.grid.width, height: state.grid.height }, w)) + .map((w) => + constrainWidgetPositionToGrid( + { x: 0, y: 0, width: state.grid.width, height: state.grid.height }, + w + ) + ) .map(trimRectPosition); const updatedWidgets = state.dashboardConfiguration.widgets.map((w) => { diff --git a/packages/dashboard/src/store/index.ts b/packages/dashboard/src/store/index.ts index a776d9287..64531e2cd 100644 --- a/packages/dashboard/src/store/index.ts +++ b/packages/dashboard/src/store/index.ts @@ -10,7 +10,9 @@ import type { RecursivePartial, DashboardConfiguration } from '~/types'; export type DashboardStore = Store; -export const configureDashboardStore = (preloadedState?: RecursivePartial) => { +export const configureDashboardStore = ( + preloadedState?: RecursivePartial +) => { /** * Merge modifies the source object so it must be cloned or initialState * will be shared between different instances of the dashboard store. @@ -31,7 +33,9 @@ export const configureDashboardStore = (preloadedState?: RecursivePartial => { +export const toDashboardState = ( + dashboardConfiguration: DashboardConfiguration +): RecursivePartial => { const { widgets, displaySettings } = dashboardConfiguration; const { numRows, numColumns, cellSize } = displaySettings; diff --git a/packages/dashboard/src/store/state.ts b/packages/dashboard/src/store/state.ts index 6c6ade19e..5c01de767 100644 --- a/packages/dashboard/src/store/state.ts +++ b/packages/dashboard/src/store/state.ts @@ -1,7 +1,9 @@ import type { DashboardWidget } from '~/types'; import { deepFreeze } from '~/util/deepFreeze'; -export type DashboardState = Record> = { +export type DashboardState< + Properties extends Record = Record +> = { grid: { enabled: boolean; width: number; diff --git a/packages/dashboard/src/types.ts b/packages/dashboard/src/types.ts index 228172adf..7de950dac 100644 --- a/packages/dashboard/src/types.ts +++ b/packages/dashboard/src/types.ts @@ -29,11 +29,17 @@ export type IoTSiteWiseDataStreamQuery = Partial< SiteWiseAssetQuery & SiteWisePropertyAliasQuery & SiteWiseAssetModelQuery >; -export type DashboardClientConfiguration = DashboardIotSiteWiseClients | DashboardClientCredentials; +export type DashboardClientConfiguration = + | DashboardIotSiteWiseClients + | DashboardClientCredentials; -export type DashboardSave = (dashboardConfiguration: DashboardConfiguration) => Promise; +export type DashboardSave = ( + dashboardConfiguration: DashboardConfiguration +) => Promise; -export type DashboardWidget = Record> = { +export type DashboardWidget< + Properties extends Record = Record +> = { type: string; id: string; x: number; @@ -51,13 +57,17 @@ export type DashboardDisplaySettings = { significantDigits?: number; }; -export type DashboardConfiguration = Record> = { +export type DashboardConfiguration< + Properties extends Record = Record +> = { displaySettings: DashboardDisplaySettings; widgets: DashboardWidget[]; viewport: Viewport; }; -export type DashboardWidgetsConfiguration = Record> = { +export type DashboardWidgetsConfiguration< + Properties extends Record = Record +> = { widgets: DashboardWidget[]; viewport: Viewport; }; @@ -82,5 +92,8 @@ export type RecursivePartial = { : T[P]; }; -export type PickRequiredOptional = Pick & - RecursivePartial>; +export type PickRequiredOptional< + T, + TRequired extends keyof T, + TOptional extends keyof T +> = Pick & RecursivePartial>; diff --git a/packages/dashboard/src/util/animate.spec.ts b/packages/dashboard/src/util/animate.spec.ts index 7f19d077a..45be353d8 100644 --- a/packages/dashboard/src/util/animate.spec.ts +++ b/packages/dashboard/src/util/animate.spec.ts @@ -12,8 +12,10 @@ describe(Animator, () => { }); it('executes the animation at 60fps', () => { - // @ts-expect-error number is not a timeout - jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => setTimeout(() => cb(), MS_PER_FRAME + 1)); + jest + .spyOn(window, 'requestAnimationFrame') + // @ts-expect-error number is not a timeout + .mockImplementation((cb) => setTimeout(() => cb(), MS_PER_FRAME + 1)); const animator = new Animator(); const animation = jest.fn(); @@ -34,8 +36,10 @@ describe(Animator, () => { it('does not execute the animation faster than 60fps', () => { const lessThan60fps = MS_PER_FRAME - 1; - // @ts-expect-error number is not a timeout - jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => setTimeout(() => cb(), lessThan60fps)); + jest + .spyOn(window, 'requestAnimationFrame') + // @ts-expect-error number is not a timeout + .mockImplementation((cb) => setTimeout(() => cb(), lessThan60fps)); const animator = new Animator(); const animation = jest.fn(); diff --git a/packages/dashboard/src/util/animate.ts b/packages/dashboard/src/util/animate.ts index 4a8b33348..df4b939d1 100644 --- a/packages/dashboard/src/util/animate.ts +++ b/packages/dashboard/src/util/animate.ts @@ -9,7 +9,9 @@ export const MS_PER_FRAME = 1000 / FPS; export class Animator { #msPrev = window.performance.now(); - public animate(animation: Animation): ReturnType { + public animate( + animation: Animation + ): ReturnType { return requestAnimationFrame(() => { const msNow = window.performance.now(); const msPassed = msNow - this.#msPrev; diff --git a/packages/dashboard/src/util/constrainWidgetPositionToGrid.ts b/packages/dashboard/src/util/constrainWidgetPositionToGrid.ts index ff17b3141..3c7e448af 100644 --- a/packages/dashboard/src/util/constrainWidgetPositionToGrid.ts +++ b/packages/dashboard/src/util/constrainWidgetPositionToGrid.ts @@ -3,10 +3,10 @@ import type { Rect } from '~/types'; const max = Math.max; const min = Math.min; -export const constrainWidgetPositionToGrid: (gridRect: Rect, rect: R) => R = ( - gridRect, - rect -) => ({ +export const constrainWidgetPositionToGrid: ( + gridRect: Rect, + rect: R +) => R = (gridRect, rect) => ({ ...rect, x: max(0, min(gridRect.width - rect.width, max(gridRect.x, rect.x))), y: max(0, min(gridRect.height - rect.height, max(gridRect.y, rect.y))), diff --git a/packages/dashboard/src/util/getSelectionBox.ts b/packages/dashboard/src/util/getSelectionBox.ts index 6bfc67cec..b20336506 100644 --- a/packages/dashboard/src/util/getSelectionBox.ts +++ b/packages/dashboard/src/util/getSelectionBox.ts @@ -1,15 +1,21 @@ import type { Rect, DashboardWidget } from '~/types'; // Returns the smallest rectangle which can contain all the selected widgets -export const getSelectionBox = (selectedWidgets: DashboardWidget[]): Rect | null => { +export const getSelectionBox = ( + selectedWidgets: DashboardWidget[] +): Rect | null => { if (selectedWidgets.length === 0) { return null; } const minX = Math.min(...selectedWidgets.map((widget) => widget.x)); - const maxX = Math.max(...selectedWidgets.map((widget) => widget.x + widget.width)); + const maxX = Math.max( + ...selectedWidgets.map((widget) => widget.x + widget.width) + ); const minY = Math.min(...selectedWidgets.map((widget) => widget.y)); - const maxY = Math.max(...selectedWidgets.map((widget) => widget.y + widget.height)); + const maxY = Math.max( + ...selectedWidgets.map((widget) => widget.y + widget.height) + ); return { x: minX, diff --git a/packages/dashboard/src/util/inputEvent.spec.ts b/packages/dashboard/src/util/inputEvent.spec.ts index ecd314078..bed4da818 100644 --- a/packages/dashboard/src/util/inputEvent.spec.ts +++ b/packages/dashboard/src/util/inputEvent.spec.ts @@ -2,7 +2,9 @@ import { BaseChangeDetail } from '@cloudscape-design/components/input/interfaces import { numberFromDetail } from './inputEvent'; import { NonCancelableCustomEvent } from '@cloudscape-design/components'; -const mockEventDetail = (value: BaseChangeDetail['value']): NonCancelableCustomEvent => ({ +const mockEventDetail = ( + value: BaseChangeDetail['value'] +): NonCancelableCustomEvent => ({ detail: { value }, initCustomEvent: function (): void { throw new Error('Function not implemented.'); diff --git a/packages/dashboard/src/util/inputEvent.ts b/packages/dashboard/src/util/inputEvent.ts index f26f98a7c..e6da90966 100644 --- a/packages/dashboard/src/util/inputEvent.ts +++ b/packages/dashboard/src/util/inputEvent.ts @@ -2,5 +2,6 @@ import { NonCancelableCustomEvent } from '@cloudscape-design/components'; import { BaseChangeDetail } from '@cloudscape-design/components/input/interfaces'; // Should never return NaN -export const numberFromDetail = (event: NonCancelableCustomEvent) => - parseInt(event.detail.value) || 0; +export const numberFromDetail = ( + event: NonCancelableCustomEvent +) => parseInt(event.detail.value) || 0; diff --git a/packages/dashboard/src/util/isDefined.ts b/packages/dashboard/src/util/isDefined.ts index ac4f0dcf9..4f7208e03 100644 --- a/packages/dashboard/src/util/isDefined.ts +++ b/packages/dashboard/src/util/isDefined.ts @@ -25,4 +25,5 @@ * type guards. * */ -export const isDefined = (value: T | null | undefined): value is T => value != null; +export const isDefined = (value: T | null | undefined): value is T => + value != null; diff --git a/packages/dashboard/src/util/maybe.ts b/packages/dashboard/src/util/maybe.ts index 663a6fc03..21ab2371f 100644 --- a/packages/dashboard/src/util/maybe.ts +++ b/packages/dashboard/src/util/maybe.ts @@ -44,7 +44,8 @@ export const Just = (value: T): Just => ({ /** * Checks if a maybe has a value and casts to Just type */ -export const isJust = (m: Maybe): m is Just => m.type === MaybeType.Just; +export const isJust = (m: Maybe): m is Just => + m.type === MaybeType.Just; /** * Gets the value of a maybe type or a default value diff --git a/packages/dashboard/src/util/mergeAssetQueries.spec.ts b/packages/dashboard/src/util/mergeAssetQueries.spec.ts index cfc095a9c..a87175a50 100644 --- a/packages/dashboard/src/util/mergeAssetQueries.spec.ts +++ b/packages/dashboard/src/util/mergeAssetQueries.spec.ts @@ -51,7 +51,10 @@ it('should merge queries', () => { expect(mergeAssetQueries(MOCK_QUERY.assets, MOCK_ASSET)).toMatchObject([ { assetId: 'assetId', - properties: [...MOCK_QUERY.assets[0].properties, ...MOCK_ASSET.properties], + properties: [ + ...MOCK_QUERY.assets[0].properties, + ...MOCK_ASSET.properties, + ], }, ]); }); @@ -65,7 +68,7 @@ it('should not contain duplicate properties', () => { a: SiteWiseAssetQuery['assets'][number]['properties'][number], b: SiteWiseAssetQuery['assets'][number]['properties'][number] ) => a.propertyId.localeCompare(b.propertyId); - expect(mergeAssetQueries(MOCK_QUERY.assets, MOCK_ASSET3)[0].properties.sort(sortFn)).toMatchObject( - [...MOCK_QUERY.assets[0].properties].sort(sortFn) - ); + expect( + mergeAssetQueries(MOCK_QUERY.assets, MOCK_ASSET3)[0].properties.sort(sortFn) + ).toMatchObject([...MOCK_QUERY.assets[0].properties].sort(sortFn)); }); diff --git a/packages/dashboard/src/util/mergeAssetQueries.ts b/packages/dashboard/src/util/mergeAssetQueries.ts index 0f8160227..6e09dcfb0 100644 --- a/packages/dashboard/src/util/mergeAssetQueries.ts +++ b/packages/dashboard/src/util/mergeAssetQueries.ts @@ -5,7 +5,9 @@ export const mergeAssetQueries = ( currentQueries: SiteWiseAssetQuery['assets'], newQuery: SiteWiseAssetQuery['assets'][number] ) => { - const existingAssetIndex = currentQueries.findIndex((a) => a.assetId === newQuery.assetId); + const existingAssetIndex = currentQueries.findIndex( + (a) => a.assetId === newQuery.assetId + ); if (existingAssetIndex >= 0) { return [ ...currentQueries.slice(0, existingAssetIndex), diff --git a/packages/dashboard/src/util/overlaps.spec.ts b/packages/dashboard/src/util/overlaps.spec.ts index 1d43f44c2..ab6401aea 100644 --- a/packages/dashboard/src/util/overlaps.spec.ts +++ b/packages/dashboard/src/util/overlaps.spec.ts @@ -3,21 +3,46 @@ import { overlaps } from './overlaps'; // Note: A degenerate rectangle is one with a side of length 0. it('two degenerate rectangles contained on the same point are contained', () => { - expect(overlaps({ width: 0, height: 0, x: 0, y: 0 }, { width: 0, height: 0, x: 0, y: 0 })).toBe(true); + expect( + overlaps( + { width: 0, height: 0, x: 0, y: 0 }, + { width: 0, height: 0, x: 0, y: 0 } + ) + ).toBe(true); }); it('two degenerate rectangles contained on different points are not contained', () => { - expect(overlaps({ width: 0, height: 0, x: 0, y: 0 }, { width: 0, height: 0, x: 1, y: 1 })).toBe(false); + expect( + overlaps( + { width: 0, height: 0, x: 0, y: 0 }, + { width: 0, height: 0, x: 1, y: 1 } + ) + ).toBe(false); }); it('two rectangles have no overlap, then it is not contained', () => { - expect(overlaps({ width: 1, height: 1, x: 0, y: 0 }, { width: 2, height: 2, x: 3, y: 3 })).toBe(false); + expect( + overlaps( + { width: 1, height: 1, x: 0, y: 0 }, + { width: 2, height: 2, x: 3, y: 3 } + ) + ).toBe(false); }); it('two rectangles have overlap at the border, then it is contained', () => { - expect(overlaps({ width: 1, height: 1, x: 0, y: 0 }, { width: 2, height: 2, x: 1, y: 1 })).toBe(true); + expect( + overlaps( + { width: 1, height: 1, x: 0, y: 0 }, + { width: 2, height: 2, x: 1, y: 1 } + ) + ).toBe(true); }); it('two rectangles have overlap within the area contained in the rectangles, then it is contained', () => { - expect(overlaps({ width: 2, height: 2, x: 0, y: 0 }, { width: 2, height: 2, x: 1, y: 1 })).toBe(true); + expect( + overlaps( + { width: 2, height: 2, x: 0, y: 0 }, + { width: 2, height: 2, x: 1, y: 1 } + ) + ).toBe(true); }); diff --git a/packages/dashboard/src/util/position.ts b/packages/dashboard/src/util/position.ts index 96f6a6a59..85370cbf6 100644 --- a/packages/dashboard/src/util/position.ts +++ b/packages/dashboard/src/util/position.ts @@ -5,7 +5,10 @@ import type { Position } from '~/types'; * Scale a position in real pixels on the grid to its grid position based on the grid cell size * */ -export const toGridPosition = (position: Position, cellSize: number): Position => { +export const toGridPosition = ( + position: Position, + cellSize: number +): Position => { if (cellSize <= 0) return { x: 0, y: 0 }; return { diff --git a/packages/dashboard/src/util/resizeSelectionBox.spec.ts b/packages/dashboard/src/util/resizeSelectionBox.spec.ts index 698bbab9a..4bc5dceb4 100644 --- a/packages/dashboard/src/util/resizeSelectionBox.spec.ts +++ b/packages/dashboard/src/util/resizeSelectionBox.spec.ts @@ -13,7 +13,9 @@ it('should resize selection box on top anchor', () => { const anchor = 'top'; const vector = { x: 10, y: 10 }; const expected = { x: 0, y: 10, width: 100, height: 90 }; - expect(resizeSelectionBox({ selectionBox: curr, anchor, vector, grid })).toEqual(expected); + expect( + resizeSelectionBox({ selectionBox: curr, anchor, vector, grid }) + ).toEqual(expected); }); it('should resize selection box on top-left anchor', () => { @@ -21,7 +23,9 @@ it('should resize selection box on top-left anchor', () => { const anchor = 'top-left'; const vector = { x: 10, y: 10 }; const expected = { x: 10, y: 10, width: 90, height: 90 }; - expect(resizeSelectionBox({ selectionBox: curr, anchor, vector, grid })).toEqual(expected); + expect( + resizeSelectionBox({ selectionBox: curr, anchor, vector, grid }) + ).toEqual(expected); }); it('should resize selection box on top-right anchor', () => { @@ -29,7 +33,9 @@ it('should resize selection box on top-right anchor', () => { const anchor = 'top-right'; const vector = { x: 10, y: 10 }; const expected = { x: 0, y: 10, width: 110, height: 90 }; - expect(resizeSelectionBox({ selectionBox: curr, anchor, vector, grid })).toEqual(expected); + expect( + resizeSelectionBox({ selectionBox: curr, anchor, vector, grid }) + ).toEqual(expected); }); it('should resize selection box on bottom anchor', () => { @@ -37,7 +43,9 @@ it('should resize selection box on bottom anchor', () => { const anchor = 'bottom'; const vector = { x: 10, y: 10 }; const expected = { x: 0, y: 0, width: 100, height: 110 }; - expect(resizeSelectionBox({ selectionBox: curr, anchor, vector, grid })).toEqual(expected); + expect( + resizeSelectionBox({ selectionBox: curr, anchor, vector, grid }) + ).toEqual(expected); }); it('should resize selection box on bottom-left anchor', () => { @@ -45,7 +53,9 @@ it('should resize selection box on bottom-left anchor', () => { const anchor = 'bottom-left'; const vector = { x: 10, y: 10 }; const expected = { x: 10, y: 0, width: 90, height: 110 }; - expect(resizeSelectionBox({ selectionBox: curr, anchor, vector, grid })).toEqual(expected); + expect( + resizeSelectionBox({ selectionBox: curr, anchor, vector, grid }) + ).toEqual(expected); }); it('should resize selection box on bottom-right anchor', () => { @@ -53,7 +63,9 @@ it('should resize selection box on bottom-right anchor', () => { const anchor = 'bottom-right'; const vector = { x: 10, y: 10 }; const expected = { x: 0, y: 0, width: 110, height: 110 }; - expect(resizeSelectionBox({ selectionBox: curr, anchor, vector, grid })).toEqual(expected); + expect( + resizeSelectionBox({ selectionBox: curr, anchor, vector, grid }) + ).toEqual(expected); }); it('should resize selection box on left anchor', () => { @@ -61,7 +73,9 @@ it('should resize selection box on left anchor', () => { const anchor = 'left'; const vector = { x: 10, y: 10 }; const expected = { x: 10, y: 0, width: 90, height: 100 }; - expect(resizeSelectionBox({ selectionBox: curr, anchor, vector, grid })).toEqual(expected); + expect( + resizeSelectionBox({ selectionBox: curr, anchor, vector, grid }) + ).toEqual(expected); }); it('should resize selection box on right anchor', () => { @@ -69,7 +83,9 @@ it('should resize selection box on right anchor', () => { const anchor = 'right'; const vector = { x: 10, y: 10 }; const expected = { x: 0, y: 0, width: 110, height: 100 }; - expect(resizeSelectionBox({ selectionBox: curr, anchor, vector, grid })).toEqual(expected); + expect( + resizeSelectionBox({ selectionBox: curr, anchor, vector, grid }) + ).toEqual(expected); }); describe('should not vertically resize selection box below minimum width', () => { @@ -78,7 +94,9 @@ describe('should not vertically resize selection box below minimum width', () => leftAnchors.forEach((anchor) => { const vector = { x: 10, y: 10 }; it(`should not resize selection box on ${anchor} anchor`, () => { - expect(resizeSelectionBox({ selectionBox: curr, anchor, vector, grid }).width).toEqual(1); + expect( + resizeSelectionBox({ selectionBox: curr, anchor, vector, grid }).width + ).toEqual(1); }); }); @@ -86,7 +104,9 @@ describe('should not vertically resize selection box below minimum width', () => rightAnchors.forEach((anchor) => { const vector = { x: -10, y: 10 }; it(`should not resize selection box on ${anchor} anchor`, () => { - expect(resizeSelectionBox({ selectionBox: curr, anchor, vector, grid }).width).toEqual(1); + expect( + resizeSelectionBox({ selectionBox: curr, anchor, vector, grid }).width + ).toEqual(1); }); }); }); @@ -97,7 +117,9 @@ describe('should not horizontally resize selection box below minimum height', () topAnchors.forEach((anchor) => { const vector = { x: 10, y: 10 }; it(`should not resize selection box on ${anchor} anchor`, () => { - expect(resizeSelectionBox({ selectionBox: curr, anchor, vector, grid }).height).toEqual(1); + expect( + resizeSelectionBox({ selectionBox: curr, anchor, vector, grid }).height + ).toEqual(1); }); }); @@ -105,7 +127,9 @@ describe('should not horizontally resize selection box below minimum height', () bottomAnchors.forEach((anchor) => { const vector = { x: 10, y: -10 }; it(`should not resize selection box on ${anchor} anchor`, () => { - expect(resizeSelectionBox({ selectionBox: curr, anchor, vector, grid }).height).toEqual(1); + expect( + resizeSelectionBox({ selectionBox: curr, anchor, vector, grid }).height + ).toEqual(1); }); }); }); @@ -113,10 +137,24 @@ const withinGrid = (newRect: Rect, gridRect: Rect) => { const { x, y, width, height } = newRect; const { x: gridX, y: gridY, width: gridWidth, height: gridHeight } = gridRect; - return x >= gridX && y >= gridY && x + width <= gridX + gridWidth && y + height <= gridY + gridHeight; + return ( + x >= gridX && + y >= gridY && + x + width <= gridX + gridWidth && + y + height <= gridY + gridHeight + ); }; describe('should not resize selection box beyond grid', () => { - const anchors: Anchor[] = ['top', 'top-left', 'top-right', 'bottom', 'bottom-left', 'bottom-right', 'left', 'right']; + const anchors: Anchor[] = [ + 'top', + 'top-left', + 'top-right', + 'bottom', + 'bottom-left', + 'bottom-right', + 'left', + 'right', + ]; const startingPoints = [ { x: 0, y: 0 }, { x: 90, y: 0 }, @@ -129,20 +167,39 @@ describe('should not resize selection box beyond grid', () => { { x: 20, y: -20 }, { x: -20, y: -20 }, ]; - type TestCase = { anchor: Anchor; startingPoint: (typeof startingPoints)[number]; vector: (typeof vectors)[number] }; + type TestCase = { + anchor: Anchor; + startingPoint: (typeof startingPoints)[number]; + vector: (typeof vectors)[number]; + }; const table: TestCase[] = anchors.flatMap((anchor) => - startingPoints.flatMap((startingPoint) => vectors.map((vector) => ({ anchor, startingPoint, vector }))) + startingPoints.flatMap((startingPoint) => + vectors.map((vector) => ({ anchor, startingPoint, vector })) + ) ); - it.concurrent.each(table)('on $anchor from $startingPoint with $vector', ({ anchor, startingPoint, vector }) => { - const curr = { x: startingPoint.x, y: startingPoint.y, width: 10, height: 10 }; - const newRect = resizeSelectionBox({ selectionBox: curr, anchor, vector, grid }); - expect( - withinGrid(newRect, { - x: 0, - y: 0, - ...grid, - }) - ).toBe(true); - }); + it.concurrent.each(table)( + 'on $anchor from $startingPoint with $vector', + ({ anchor, startingPoint, vector }) => { + const curr = { + x: startingPoint.x, + y: startingPoint.y, + width: 10, + height: 10, + }; + const newRect = resizeSelectionBox({ + selectionBox: curr, + anchor, + vector, + grid, + }); + expect( + withinGrid(newRect, { + x: 0, + y: 0, + ...grid, + }) + ).toBe(true); + } + ); }); diff --git a/packages/dashboard/src/util/select.spec.ts b/packages/dashboard/src/util/select.spec.ts index 054406ea4..ce69098c5 100644 --- a/packages/dashboard/src/util/select.spec.ts +++ b/packages/dashboard/src/util/select.spec.ts @@ -15,7 +15,15 @@ describe('getSelectedIds', () => { it('returns id of widget that is contained within the selected rectangle', () => { expect( getSelectedWidgetIds({ - dashboardWidgets: [MockWidgetFactory.getLineChartWidget({ x: 5, y: 5, width: 1, height: 1, id: 'some-id' })], + dashboardWidgets: [ + MockWidgetFactory.getLineChartWidget({ + x: 5, + y: 5, + width: 1, + height: 1, + id: 'some-id', + }), + ], cellSize: 10, selectedRect: { x: 0, y: 0, width: 100, height: 100 }, }) @@ -25,7 +33,15 @@ describe('getSelectedIds', () => { it('returns id of widget that overlaps the selected rectangle', () => { expect( getSelectedWidgetIds({ - dashboardWidgets: [MockWidgetFactory.getLineChartWidget({ x: 1, y: 1, width: 1, height: 1, id: 'some-id' })], + dashboardWidgets: [ + MockWidgetFactory.getLineChartWidget({ + x: 1, + y: 1, + width: 1, + height: 1, + id: 'some-id', + }), + ], cellSize: 10, selectedRect: { x: 10, y: 10, width: 10, height: 10 }, }) @@ -35,7 +51,15 @@ describe('getSelectedIds', () => { it('returns no ids when the widgets are not overlapping the selected rectangle', () => { expect( getSelectedWidgetIds({ - dashboardWidgets: [MockWidgetFactory.getLineChartWidget({ x: 0, y: 0, width: 1, height: 1, id: 'some-id' })], + dashboardWidgets: [ + MockWidgetFactory.getLineChartWidget({ + x: 0, + y: 0, + width: 1, + height: 1, + id: 'some-id', + }), + ], cellSize: 5, selectedRect: { x: 10, y: 10, width: 10, height: 10 }, }) @@ -46,8 +70,20 @@ describe('getSelectedIds', () => { expect( getSelectedWidgetIds({ dashboardWidgets: [ - MockWidgetFactory.getLineChartWidget({ x: 1, y: 1, width: 1, height: 1, id: 'some-id' }), - MockWidgetFactory.getLineChartWidget({ x: 50, y: 50, width: 1, height: 1, id: 'some-id-2' }), + MockWidgetFactory.getLineChartWidget({ + x: 1, + y: 1, + width: 1, + height: 1, + id: 'some-id', + }), + MockWidgetFactory.getLineChartWidget({ + x: 50, + y: 50, + width: 1, + height: 1, + id: 'some-id-2', + }), ], cellSize: 10, selectedRect: { x: 10, y: 10, width: 10, height: 10 }, @@ -61,8 +97,20 @@ describe('pointSelect', () => { expect( pointSelect({ dashboardWidgets: [ - MockWidgetFactory.getLineChartWidget({ x: 10, y: 10, width: 1, height: 1, id: 'some-id' }), - MockWidgetFactory.getLineChartWidget({ x: 50, y: 50, width: 1, height: 1, id: 'some-id-2' }), + MockWidgetFactory.getLineChartWidget({ + x: 10, + y: 10, + width: 1, + height: 1, + id: 'some-id', + }), + MockWidgetFactory.getLineChartWidget({ + x: 50, + y: 50, + width: 1, + height: 1, + id: 'some-id-2', + }), ], cellSize: 10, position: { x: 0, y: 0 }, @@ -74,8 +122,20 @@ describe('pointSelect', () => { expect( pointSelect({ dashboardWidgets: [ - MockWidgetFactory.getLineChartWidget({ x: 1, y: 1, width: 1, height: 1, id: 'some-id' }), - MockWidgetFactory.getLineChartWidget({ x: 50, y: 50, width: 1, height: 1, id: 'some-id-2' }), + MockWidgetFactory.getLineChartWidget({ + x: 1, + y: 1, + width: 1, + height: 1, + id: 'some-id', + }), + MockWidgetFactory.getLineChartWidget({ + x: 50, + y: 50, + width: 1, + height: 1, + id: 'some-id-2', + }), ], cellSize: 10, position: { x: 15, y: 15 }, @@ -85,8 +145,20 @@ describe('pointSelect', () => { expect( pointSelect({ dashboardWidgets: [ - MockWidgetFactory.getLineChartWidget({ x: 1, y: 1, width: 1, height: 1, id: 'some-id' }), - MockWidgetFactory.getLineChartWidget({ x: 50, y: 50, width: 1, height: 1, id: 'some-id-2' }), + MockWidgetFactory.getLineChartWidget({ + x: 1, + y: 1, + width: 1, + height: 1, + id: 'some-id', + }), + MockWidgetFactory.getLineChartWidget({ + x: 50, + y: 50, + width: 1, + height: 1, + id: 'some-id-2', + }), ], cellSize: 10, position: { x: 505, y: 505 }, @@ -98,8 +170,22 @@ describe('pointSelect', () => { expect( pointSelect({ dashboardWidgets: [ - MockWidgetFactory.getLineChartWidget({ x: 1, y: 1, width: 1, height: 1, z: 0, id: 'some-id' }), - MockWidgetFactory.getLineChartWidget({ x: 1, y: 1, width: 1, height: 1, z: 1, id: 'some-id-2' }), + MockWidgetFactory.getLineChartWidget({ + x: 1, + y: 1, + width: 1, + height: 1, + z: 0, + id: 'some-id', + }), + MockWidgetFactory.getLineChartWidget({ + x: 1, + y: 1, + width: 1, + height: 1, + z: 1, + id: 'some-id-2', + }), ], cellSize: 10, position: { x: 15, y: 15 }, diff --git a/packages/dashboard/src/util/select.ts b/packages/dashboard/src/util/select.ts index 61edf36be..c765655cd 100644 --- a/packages/dashboard/src/util/select.ts +++ b/packages/dashboard/src/util/select.ts @@ -40,7 +40,10 @@ export const getSelectedWidgetIds = ({ selectedRect: Rect | undefined; cellSize: number; dashboardWidgets: DashboardWidget[]; -}) => getSelectedWidgets({ selectedRect, cellSize, dashboardWidgets }).map((widget) => widget.id); +}) => + getSelectedWidgets({ selectedRect, cellSize, dashboardWidgets }).map( + (widget) => widget.id + ); /** * @@ -66,12 +69,20 @@ export const pointSelect = ({ cellSize: cellSize, }); - const sortableWidgets = intersectedWidgets.map((widget, index) => ({ id: widget.id, z: widget.z, index })); - const topMostWidgetId = last(sortBy(sortableWidgets, ['z', 'index']).map((widget) => widget.id)); + const sortableWidgets = intersectedWidgets.map((widget, index) => ({ + id: widget.id, + z: widget.z, + index, + })); + const topMostWidgetId = last( + sortBy(sortableWidgets, ['z', 'index']).map((widget) => widget.id) + ); return dashboardWidgets.find((widget) => widget.id === topMostWidgetId); }; -export const selectedRect = (selection: Selection | undefined): Rect | undefined => { +export const selectedRect = ( + selection: Selection | undefined +): Rect | undefined => { if (!selection) { return undefined; } diff --git a/packages/dashboard/src/util/transformWidget.spec.ts b/packages/dashboard/src/util/transformWidget.spec.ts index 705dedb62..d31ecfda6 100644 --- a/packages/dashboard/src/util/transformWidget.spec.ts +++ b/packages/dashboard/src/util/transformWidget.spec.ts @@ -6,7 +6,16 @@ import type { Rect, DashboardWidget } from '~/types'; import type { Anchor } from '~/store/actions'; import type { DashboardState } from '~/store/state'; -const anchors: Anchor[] = ['top', 'top-left', 'top-right', 'bottom', 'bottom-left', 'bottom-right', 'left', 'right']; +const anchors: Anchor[] = [ + 'top', + 'top-left', + 'top-right', + 'bottom', + 'bottom-left', + 'bottom-right', + 'left', + 'right', +]; const grid = { width: 100, height: 100, @@ -25,10 +34,16 @@ describe('resize single widget', () => { }; const selectionBox: Rect = baseWidget; - const transformerToBeTested = (newSelectionBox: Rect) => transformWidget(baseWidget, selectionBox, newSelectionBox); + const transformerToBeTested = (newSelectionBox: Rect) => + transformWidget(baseWidget, selectionBox, newSelectionBox); anchors.forEach((anchor) => { - const newSelectionBox = resizeSelectionBox({ selectionBox: selectionBox, anchor, vector: { x: 10, y: 10 }, grid }); + const newSelectionBox = resizeSelectionBox({ + selectionBox: selectionBox, + anchor, + vector: { x: 10, y: 10 }, + grid, + }); const expected: Rect = { ...baseWidget, ...newSelectionBox }; const result = transformerToBeTested(newSelectionBox); const keys = ['x', 'y', 'width', 'height'] as (keyof Rect)[]; @@ -59,8 +74,9 @@ describe('resize multiple widgets', () => { ] as DashboardWidget[]; const selectionBox = getSelectionBox(widgets)!; - const transformerToBeTested = (newSelectionBox: Rect) => (widget: DashboardWidget) => - transformWidget(widget, selectionBox, newSelectionBox); + const transformerToBeTested = + (newSelectionBox: Rect) => (widget: DashboardWidget) => + transformWidget(widget, selectionBox, newSelectionBox); anchors.forEach((anchor) => { const mapper = transformerToBeTested( diff --git a/packages/dashboard/src/util/transformWidget.ts b/packages/dashboard/src/util/transformWidget.ts index c90ea498c..21565746f 100644 --- a/packages/dashboard/src/util/transformWidget.ts +++ b/packages/dashboard/src/util/transformWidget.ts @@ -1,10 +1,10 @@ import type { Rect, DashboardWidget } from '~/types'; -export const transformWidget: (widget: DashboardWidget, pre: Rect, curr: Rect) => DashboardWidget = ( - widget, - prev, - curr -) => { +export const transformWidget: ( + widget: DashboardWidget, + pre: Rect, + curr: Rect +) => DashboardWidget = (widget, prev, curr) => { const offsetX = widget.x - prev.x; const offsetY = widget.y - prev.y; diff --git a/packages/dashboard/stories/dashboard/mockData.ts b/packages/dashboard/stories/dashboard/mockData.ts index 02a35c8d4..5ca2cbbd9 100644 --- a/packages/dashboard/stories/dashboard/mockData.ts +++ b/packages/dashboard/stories/dashboard/mockData.ts @@ -101,7 +101,9 @@ export const mockListAssets = (args: { filter: string }) => { if (args.filter === 'TOP_LEVEL') { return Promise.resolve(assetsTopLevel); } - throw new Error(`mockListAssets can't be called with a filter other than TOP_LEVEL.`); + throw new Error( + `mockListAssets can't be called with a filter other than TOP_LEVEL.` + ); }; export const mockListAssociatedAssets = (args: { assetId: string }) => { @@ -111,5 +113,7 @@ export const mockListAssociatedAssets = (args: { assetId: string }) => { if (args.assetId === 'e9a995de-149e-4f6d-afa6-f073b863a050') { return Promise.resolve(assetsTurbineSensors); } - throw new Error(`mockListAssociatedAssets must be called with Wind Farm asset's ID or Turbine 1 asset's ID`); + throw new Error( + `mockListAssociatedAssets must be called with Wind Farm asset's ID or Turbine 1 asset's ID` + ); }; diff --git a/packages/dashboard/stories/dashboard/mocked-dashboard.stories.tsx b/packages/dashboard/stories/dashboard/mocked-dashboard.stories.tsx index 38d97a70e..0f9a2ba20 100644 --- a/packages/dashboard/stories/dashboard/mocked-dashboard.stories.tsx +++ b/packages/dashboard/stories/dashboard/mocked-dashboard.stories.tsx @@ -82,6 +82,10 @@ export default { ], } as ComponentMeta; -export const Empty: ComponentStory = () => ; +export const Empty: ComponentStory = () => ( + +); -export const SingleWidget: ComponentStory = () => ; +export const SingleWidget: ComponentStory = () => ( + +); diff --git a/packages/dashboard/stories/dashboard/sitewise-dashboard.stories.tsx b/packages/dashboard/stories/dashboard/sitewise-dashboard.stories.tsx index 543f99be5..b81cc0595 100644 --- a/packages/dashboard/stories/dashboard/sitewise-dashboard.stories.tsx +++ b/packages/dashboard/stories/dashboard/sitewise-dashboard.stories.tsx @@ -9,9 +9,13 @@ import { getEnvCredentials } from '../../testing/getEnvCredentials'; import { DashboardClientConfiguration } from '../../src/types'; import { DashboardView } from '~/index'; -const getDashboardProperties = (defaultProps: DashboardProperties): DashboardProperties => { +const getDashboardProperties = ( + defaultProps: DashboardProperties +): DashboardProperties => { const cachedDashboardConfiguration = window.localStorage.getItem('dashboard'); - const dashboardConfiguration = cachedDashboardConfiguration ? JSON.parse(cachedDashboardConfiguration) : defaultProps; + const dashboardConfiguration = cachedDashboardConfiguration + ? JSON.parse(cachedDashboardConfiguration) + : defaultProps; return { ...defaultProps, @@ -58,6 +62,10 @@ registerPlugin('metricsRecorder', { }), }); -export const Main: ComponentStory = () => ; +export const Main: ComponentStory = () => ( + +); -export const View: ComponentStory = () => ; +export const View: ComponentStory = () => ( + +); diff --git a/packages/dashboard/testing/getEnvCredentials.ts b/packages/dashboard/testing/getEnvCredentials.ts index f97b7c913..de65fd8ce 100644 --- a/packages/dashboard/testing/getEnvCredentials.ts +++ b/packages/dashboard/testing/getEnvCredentials.ts @@ -11,11 +11,15 @@ export function getEnvCredentials(): Credentials | never { const sessionToken = process.env.AWS_SESSION_TOKEN; if (!accessKeyId) { - throw new Error('Missing credentials: AWS_ACCESS_KEY_ID. Update AWS_ACCESS_KEY_ID in root `.env` file.'); + throw new Error( + 'Missing credentials: AWS_ACCESS_KEY_ID. Update AWS_ACCESS_KEY_ID in root `.env` file.' + ); } if (!secretAccessKey) { - throw new Error('Missing credentials: AWS_SECRET_ACCESS_KEY. Update AWS_SECRET_ACCESS_KEY in root `.env` file.'); + throw new Error( + 'Missing credentials: AWS_SECRET_ACCESS_KEY. Update AWS_SECRET_ACCESS_KEY in root `.env` file.' + ); } return { diff --git a/packages/dashboard/testing/mocks.ts b/packages/dashboard/testing/mocks.ts index 68f664d2f..5cab644de 100644 --- a/packages/dashboard/testing/mocks.ts +++ b/packages/dashboard/testing/mocks.ts @@ -46,7 +46,9 @@ export const MOCK_KPI_WIDGET: KPIWidget = { assets: [ { assetId: DEMO_TURBINE_ASSET_1, - properties: [{ resolution: '0', propertyId: DEMO_TURBINE_ASSET_1_PROPERTY_4 }], + properties: [ + { resolution: '0', propertyId: DEMO_TURBINE_ASSET_1_PROPERTY_4 }, + ], }, ], }, @@ -72,7 +74,11 @@ export const MOCK_SCATTER_CHART_WIDGET: LineScatterChartWidget = { { assetId: DEMO_TURBINE_ASSET_1, properties: [ - { resolution: '0', propertyId: DEMO_TURBINE_ASSET_1_PROPERTY_3, line: { connectionStyle: 'none' } }, + { + resolution: '0', + propertyId: DEMO_TURBINE_ASSET_1_PROPERTY_3, + line: { connectionStyle: 'none' }, + }, ], }, ], @@ -211,23 +217,48 @@ export const MockWidgetFactory = { getRectangleWidget: createMockWidget(MOCK_RECTANGLE_WIDGET), }; -export const getRandomWidget = (partialWidget?: Partial): DashboardWidget => { +export const getRandomWidget = ( + partialWidget?: Partial +): DashboardWidget => { switch (random(0, 4)) { default: case 0: - return MockWidgetFactory.getKpiWidget({ height: 10, width: 30, ...partialWidget }); + return MockWidgetFactory.getKpiWidget({ + height: 10, + width: 30, + ...partialWidget, + }); case 1: - return MockWidgetFactory.getScatterChartWidget({ height: 20, width: 30, ...partialWidget }); + return MockWidgetFactory.getScatterChartWidget({ + height: 20, + width: 30, + ...partialWidget, + }); case 2: - return MockWidgetFactory.getLineChartWidget({ height: 20, width: 30, ...partialWidget }); + return MockWidgetFactory.getLineChartWidget({ + height: 20, + width: 30, + ...partialWidget, + }); case 3: - return MockWidgetFactory.getStatusTimelineWidget({ height: 20, width: 30, ...partialWidget }); + return MockWidgetFactory.getStatusTimelineWidget({ + height: 20, + width: 30, + ...partialWidget, + }); case 4: - return MockWidgetFactory.getRectangleWidget({ height: 20, width: 30, ...partialWidget }); + return MockWidgetFactory.getRectangleWidget({ + height: 20, + width: 30, + ...partialWidget, + }); } }; -export const MOCK_EMPTY_DASHBOARD: DashboardWidgetsConfiguration = { viewport: { duration: '5m' }, widgets: [] }; +export const MOCK_EMPTY_DASHBOARD: DashboardWidgetsConfiguration = { + viewport: { duration: '5m' }, + widgets: [], +}; export const createMockDashboard = ( partialDashboard?: Partial diff --git a/packages/dashboard/testing/mocks/index.ts b/packages/dashboard/testing/mocks/index.ts index 4e87716db..16e63a33c 100644 --- a/packages/dashboard/testing/mocks/index.ts +++ b/packages/dashboard/testing/mocks/index.ts @@ -12,7 +12,10 @@ import { /** * Shared mocks for testing purposes */ -import { DashboardWidgetsConfiguration, DashboardWidget } from '../../src/types'; +import { + DashboardWidgetsConfiguration, + DashboardWidget, +} from '../../src/types'; import { DEMO_TURBINE_ASSET_1, @@ -45,7 +48,9 @@ export const MOCK_KPI_WIDGET: KPIWidget = { assets: [ { assetId: DEMO_TURBINE_ASSET_1, - properties: [{ resolution: '0', propertyId: DEMO_TURBINE_ASSET_1_PROPERTY_4 }], + properties: [ + { resolution: '0', propertyId: DEMO_TURBINE_ASSET_1_PROPERTY_4 }, + ], }, ], }, @@ -70,7 +75,9 @@ export const MOCK_SCATTER_CHART_WIDGET: ScatterChartWidget = { assets: [ { assetId: DEMO_TURBINE_ASSET_1, - properties: [{ resolution: '0', propertyId: DEMO_TURBINE_ASSET_1_PROPERTY_3 }], + properties: [ + { resolution: '0', propertyId: DEMO_TURBINE_ASSET_1_PROPERTY_3 }, + ], }, ], }, @@ -168,21 +175,42 @@ export const MockWidgetFactory = { getTextWidget: createMockWidget(MOCK_TEXT_WIDGET), }; -export const getRandomWidget = (partialWidget?: Partial): DashboardWidget => { +export const getRandomWidget = ( + partialWidget?: Partial +): DashboardWidget => { switch (random(0, 3)) { default: case 0: - return MockWidgetFactory.getKpiWidget({ height: 10, width: 30, ...partialWidget }); + return MockWidgetFactory.getKpiWidget({ + height: 10, + width: 30, + ...partialWidget, + }); case 1: - return MockWidgetFactory.getScatterChartWidget({ height: 20, width: 30, ...partialWidget }); + return MockWidgetFactory.getScatterChartWidget({ + height: 20, + width: 30, + ...partialWidget, + }); case 2: - return MockWidgetFactory.getLineChartWidget({ height: 20, width: 30, ...partialWidget }); + return MockWidgetFactory.getLineChartWidget({ + height: 20, + width: 30, + ...partialWidget, + }); case 3: - return MockWidgetFactory.getStatusTimelineWidget({ height: 20, width: 30, ...partialWidget }); + return MockWidgetFactory.getStatusTimelineWidget({ + height: 20, + width: 30, + ...partialWidget, + }); } }; -export const MOCK_EMPTY_DASHBOARD: DashboardWidgetsConfiguration = { viewport: { duration: '5m' }, widgets: [] }; +export const MOCK_EMPTY_DASHBOARD: DashboardWidgetsConfiguration = { + viewport: { duration: '5m' }, + widgets: [], +}; export const createMockDashboard = ( partialDashboard?: Partial diff --git a/packages/dashboard/testing/mocks/siteWiseSDK.ts b/packages/dashboard/testing/mocks/siteWiseSDK.ts index 12a7333f1..96247c67c 100644 --- a/packages/dashboard/testing/mocks/siteWiseSDK.ts +++ b/packages/dashboard/testing/mocks/siteWiseSDK.ts @@ -1,12 +1,24 @@ -import { DescribeAssetResponse, PropertyDataType } from '@aws-sdk/client-iotsitewise'; +import { + DescribeAssetResponse, + PropertyDataType, +} from '@aws-sdk/client-iotsitewise'; export const mockAssetDescription: DescribeAssetResponse = { assetModelId: 'mock-asset-model-id', assetName: 'Mock Asset', assetId: 'mock-id', assetProperties: [ - { name: 'property one', id: 'property-1', dataType: PropertyDataType.DOUBLE, alias: 'P1' }, - { name: 'property two', id: 'property-2', dataType: PropertyDataType.STRING }, + { + name: 'property one', + id: 'property-1', + dataType: PropertyDataType.DOUBLE, + alias: 'P1', + }, + { + name: 'property two', + id: 'property-2', + dataType: PropertyDataType.STRING, + }, ], assetCompositeModels: [ { diff --git a/packages/dashboard/testing/siteWiseQueries.ts b/packages/dashboard/testing/siteWiseQueries.ts index 609a5ea16..3ab076f95 100644 --- a/packages/dashboard/testing/siteWiseQueries.ts +++ b/packages/dashboard/testing/siteWiseQueries.ts @@ -28,7 +28,11 @@ export const AGGREGATED_DATA_QUERY = { { assetId: AGGREGATED_DATA_ASSET, properties: [ - { propertyId: AGGREGATED_DATA_PROPERTY, resolution: '0', refId: 'testing' }, + { + propertyId: AGGREGATED_DATA_PROPERTY, + resolution: '0', + refId: 'testing', + }, { propertyId: AGGREGATED_DATA_PROPERTY_2 }, ], }, @@ -37,7 +41,10 @@ export const AGGREGATED_DATA_QUERY = { export const query = (() => { try { - return initialize({ awsCredentials: getEnvCredentials(), awsRegion: REGION }).query; + return initialize({ + awsCredentials: getEnvCredentials(), + awsRegion: REGION, + }).query; } catch (e) { return initialize({ awsCredentials: { @@ -51,7 +58,10 @@ export const query = (() => { export const mockQuery = ( timeSeriesData: TimeSeriesData[] = [generateMockTimeSeriesData()], - overrides?: { updateViewport?: (viewport: Viewport) => void; unsubscribe?: () => void } + overrides?: { + updateViewport?: (viewport: Viewport) => void; + unsubscribe?: () => void; + } ): SiteWiseQuery => { const { updateViewport = noop, unsubscribe = noop } = overrides || {}; return { diff --git a/packages/dashboard/turbowatch.ts b/packages/dashboard/turbowatch.ts index f05e7e13b..3d488642f 100644 --- a/packages/dashboard/turbowatch.ts +++ b/packages/dashboard/turbowatch.ts @@ -14,8 +14,16 @@ void watch({ ], [ 'anyof', - ['allof', ['dirname', 'react-components'], ['match', '*.tsx', 'basename']], - ['allof', ['dirname', 'react-components'], ['match', '*.ts', 'basename']], + [ + 'allof', + ['dirname', 'react-components'], + ['match', '*.tsx', 'basename'], + ], + [ + 'allof', + ['dirname', 'react-components'], + ['match', '*.ts', 'basename'], + ], ], ['allof', ['dirname', 'core'], ['match', '*.ts', 'basename']], ['allof', ['dirname', 'core-util'], ['match', '*.ts', 'basename']], diff --git a/packages/feature-toggles/src/hooks/__tests__/useFeature.spec.tsx b/packages/feature-toggles/src/hooks/__tests__/useFeature.spec.tsx index fb40501f8..4744a3e07 100644 --- a/packages/feature-toggles/src/hooks/__tests__/useFeature.spec.tsx +++ b/packages/feature-toggles/src/hooks/__tests__/useFeature.spec.tsx @@ -37,8 +37,14 @@ describe('useFeature', () => { const { getByTestId } = render(); const sut = getByTestId('feature-toggle-component'); - await waitFor(() => expect(sut.getAttribute('data-loading')).toEqual('true')); - await waitFor(() => expect(sut.textContent).toEqual(JSON.stringify(mockFeature))); - await waitFor(() => expect(sut.getAttribute('data-loading')).toEqual('false')); + await waitFor(() => + expect(sut.getAttribute('data-loading')).toEqual('true') + ); + await waitFor(() => + expect(sut.textContent).toEqual(JSON.stringify(mockFeature)) + ); + await waitFor(() => + expect(sut.getAttribute('data-loading')).toEqual('false') + ); }); }); diff --git a/packages/feature-toggles/src/providers/__tests__/feature-provider.spec.tsx b/packages/feature-toggles/src/providers/__tests__/feature-provider.spec.tsx index cda7ef29a..a6d93d8cf 100644 --- a/packages/feature-toggles/src/providers/__tests__/feature-provider.spec.tsx +++ b/packages/feature-toggles/src/providers/__tests__/feature-provider.spec.tsx @@ -39,6 +39,8 @@ describe('', () => { ); - await waitFor(() => expect(container.textContent).toEqual(JSON.stringify(expectedContent))); + await waitFor(() => + expect(container.textContent).toEqual(JSON.stringify(expectedContent)) + ); }); }); diff --git a/packages/feature-toggles/src/providers/feature-provider.tsx b/packages/feature-toggles/src/providers/feature-provider.tsx index a51cab69d..adb6947c7 100644 --- a/packages/feature-toggles/src/providers/feature-provider.tsx +++ b/packages/feature-toggles/src/providers/feature-provider.tsx @@ -7,8 +7,13 @@ interface FeatureProviderProps extends PropsWithChildren { repository: IFeatureRepository; } -const FeatureProvider: React.FC = ({ repository: fallbackRepository, children }) => { - const [repository] = useState(new OverridableFeatureRepository(fallbackRepository)); // We wrap user's repository in the overridable decorator, which enables developers and testers to override this control, and expose whatever features they want. +const FeatureProvider: React.FC = ({ + repository: fallbackRepository, + children, +}) => { + const [repository] = useState( + new OverridableFeatureRepository(fallbackRepository) + ); // We wrap user's repository in the overridable decorator, which enables developers and testers to override this control, and expose whatever features they want. const getFeature = useCallback( async (feature: string) => { diff --git a/packages/feature-toggles/src/resources/EvidentlyFeatureRepository.ts b/packages/feature-toggles/src/resources/EvidentlyFeatureRepository.ts index 4d1a858a3..a9fceb6de 100644 --- a/packages/feature-toggles/src/resources/EvidentlyFeatureRepository.ts +++ b/packages/feature-toggles/src/resources/EvidentlyFeatureRepository.ts @@ -1,4 +1,7 @@ -import Evidently, { ClientConfiguration, EvaluateFeatureRequest } from 'aws-sdk/clients/evidently'; +import Evidently, { + ClientConfiguration, + EvaluateFeatureRequest, +} from 'aws-sdk/clients/evidently'; import IFeature from '../models/feature'; import IFeatureRepository from './IFeatureRepository'; import { Memoize } from 'typescript-memoize'; @@ -6,7 +9,11 @@ import { Memoize } from 'typescript-memoize'; export default class EvidentlyFeatureRepository implements IFeatureRepository { private client: Evidently; - constructor(private project: string, private entityId: string, options: ClientConfiguration) { + constructor( + private project: string, + private entityId: string, + options: ClientConfiguration + ) { this.client = new Evidently(options); } diff --git a/packages/feature-toggles/src/resources/OverridableFeatureRepository.ts b/packages/feature-toggles/src/resources/OverridableFeatureRepository.ts index 1aadaa4d3..1969b7b2c 100644 --- a/packages/feature-toggles/src/resources/OverridableFeatureRepository.ts +++ b/packages/feature-toggles/src/resources/OverridableFeatureRepository.ts @@ -18,7 +18,9 @@ import { Memoize } from 'typescript-memoize'; * * If it doesn't find an override for a given feature in localStorage, it instead will resolve using the Fallback Repository provided */ -export default class OverridableFeatureRepository implements IFeatureRepository { +export default class OverridableFeatureRepository + implements IFeatureRepository +{ private fallback?: IFeatureRepository; constructor(fallbackRepository?: IFeatureRepository) { diff --git a/packages/feature-toggles/src/resources/__tests__/OverridableFeatureRepository.spec.ts b/packages/feature-toggles/src/resources/__tests__/OverridableFeatureRepository.spec.ts index a5d94b377..376e00161 100644 --- a/packages/feature-toggles/src/resources/__tests__/OverridableFeatureRepository.spec.ts +++ b/packages/feature-toggles/src/resources/__tests__/OverridableFeatureRepository.spec.ts @@ -7,7 +7,9 @@ jest.mock('js-cookie', () => ({ })); const mockFeatureRepository: IFeatureRepository = { - evaluate: jest.fn(() => Promise.resolve({ variation: 'test', value: 'test' })), + evaluate: jest.fn(() => + Promise.resolve({ variation: 'test', value: 'test' }) + ), }; describe('OverridableFeatureRepository', () => { diff --git a/packages/feature-toggles/src/resources/__tests__/StaticFeatureRepository.spec.ts b/packages/feature-toggles/src/resources/__tests__/StaticFeatureRepository.spec.ts index 62ac31766..58e7bfbfc 100644 --- a/packages/feature-toggles/src/resources/__tests__/StaticFeatureRepository.spec.ts +++ b/packages/feature-toggles/src/resources/__tests__/StaticFeatureRepository.spec.ts @@ -1,4 +1,6 @@ -import StaticFeatureRepository, { StaticFeatures } from '../StaticFeatureRepository'; +import StaticFeatureRepository, { + StaticFeatures, +} from '../StaticFeatureRepository'; describe('StaticFeatureRepository', () => { it('should respond with value true for available feature', async () => { const features: StaticFeatures = { diff --git a/packages/react-components/.eslintrc.js b/packages/react-components/.eslintrc.js index dab2e4f75..19b3d8024 100644 --- a/packages/react-components/.eslintrc.js +++ b/packages/react-components/.eslintrc.js @@ -1,6 +1,10 @@ module.exports = { root: true, - extends: ['iot-app-kit', 'plugin:react-hooks/recommended', 'plugin:jsx-a11y/recommended'], + extends: [ + 'iot-app-kit', + 'plugin:react-hooks/recommended', + 'plugin:jsx-a11y/recommended', + ], plugins: ['jsx-a11y'], overrides: [ { diff --git a/packages/react-components/e2e/tests/charts/charts.spec.ts b/packages/react-components/e2e/tests/charts/charts.spec.ts index 806c453b4..74ce1a27f 100644 --- a/packages/react-components/e2e/tests/charts/charts.spec.ts +++ b/packages/react-components/e2e/tests/charts/charts.spec.ts @@ -3,34 +3,44 @@ import { test, expect } from '@playwright/test'; const TEST_IFRAME = '#storybook-preview-iframe'; const COMPONENT_SELECTOR = '#story-container'; const TEST_PAGE_LINE_CHART = '/?path=/story/widgets-charts--line-chart-example'; -const TEST_PAGE_SCATTER_CHART = '/?path=/story/widgets-charts--scatter-chart-example'; +const TEST_PAGE_SCATTER_CHART = + '/?path=/story/widgets-charts--scatter-chart-example'; const TEST_PAGE_BAR_CHART = '/?path=/story/widgets-charts--bar-chart-example'; -const TEST_PAGE_STATUS_TIMELINE = '/?path=/story/widgets-charts--status-timeline-example'; +const TEST_PAGE_STATUS_TIMELINE = + '/?path=/story/widgets-charts--status-timeline-example'; test('line-chart', async ({ page }) => { await page.goto(TEST_PAGE_LINE_CHART); const frame = page.frameLocator(TEST_IFRAME); await expect(frame.locator(COMPONENT_SELECTOR)).toContainText('2000'); - await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot('line-chart.png'); + await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot( + 'line-chart.png' + ); }); test('bar-chart', async ({ page }) => { await page.goto(TEST_PAGE_BAR_CHART); const frame = page.frameLocator(TEST_IFRAME); await expect(frame.locator(COMPONENT_SELECTOR)).toContainText('2000'); - await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot('bar-chart.png'); + await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot( + 'bar-chart.png' + ); }); test('scatter-chart', async ({ page }) => { await page.goto(TEST_PAGE_SCATTER_CHART); const frame = page.frameLocator(TEST_IFRAME); await expect(frame.locator(COMPONENT_SELECTOR)).toContainText('2000'); - await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot('scatter-chart.png'); + await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot( + 'scatter-chart.png' + ); }); test('status-timeline', async ({ page }) => { await page.goto(TEST_PAGE_STATUS_TIMELINE); const frame = page.frameLocator(TEST_IFRAME); await expect(frame.locator(COMPONENT_SELECTOR)).toContainText('2000'); - await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot('status-timeline.png'); + await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot( + 'status-timeline.png' + ); }); diff --git a/packages/react-components/e2e/tests/kpi/kpi.spec.ts b/packages/react-components/e2e/tests/kpi/kpi.spec.ts index 3dba37352..5a2f2132a 100644 --- a/packages/react-components/e2e/tests/kpi/kpi.spec.ts +++ b/packages/react-components/e2e/tests/kpi/kpi.spec.ts @@ -13,7 +13,9 @@ test('kpi', async ({ page }) => { // KPI will always show value shows value await expect(frame.locator(COMPONENT_SELECTOR)).toContainText('100'); await page.evaluate(() => document.fonts.ready); - await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot('default.png'); + await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot( + 'default.png' + ); // unit will display const unit = 'mph'; @@ -32,7 +34,9 @@ test('kpi', async ({ page }) => { // displays as loading await page.goto(`${TEST_PAGE}&args=isLoading:true`); await page.evaluate(() => document.fonts.ready); - await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot('loading.png'); + await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot( + 'loading.png' + ); // error will display const errorMsg = 'my-custom-error-msg'; @@ -44,7 +48,9 @@ test('kpi', async ({ page }) => { // font-sizes can be customized await page.goto(`${TEST_PAGE}&args=fontSize:70;secondaryFontSize:30`); await page.evaluate(() => document.fonts.ready); - await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot('custom-font-sizes.png'); + await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot( + 'custom-font-sizes.png' + ); // displays icon await page.goto(`${TEST_PAGE}&args=icon:acknowledged`); @@ -54,5 +60,7 @@ test('kpi', async ({ page }) => { // displays empty state await page.goto(`${TEST_PAGE}&args=propertyPoint:!null`); await page.evaluate(() => document.fonts.ready); - await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot('empty-state.png'); + await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot( + 'empty-state.png' + ); }); diff --git a/packages/react-components/e2e/tests/status/status.spec.ts b/packages/react-components/e2e/tests/status/status.spec.ts index e298f296a..ae7221d67 100644 --- a/packages/react-components/e2e/tests/status/status.spec.ts +++ b/packages/react-components/e2e/tests/status/status.spec.ts @@ -9,7 +9,9 @@ test('status', async ({ page }) => { const frame = page.frameLocator(TEST_IFRAME); // Need to go into frame otherwise the `locator` won't locate the selection. await expect(frame.locator(COMPONENT_SELECTOR)).toContainText('Windmill'); - await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot('default.png'); + await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot( + 'default.png' + ); // unit will display const unit = 'mph'; @@ -25,7 +27,9 @@ test('status', async ({ page }) => { // displays as loading await page.goto(`${TEST_PAGE}&args=isLoading:true`); - await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot('loading.png'); + await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot( + 'loading.png' + ); // error will display const errorMsg = 'my-custom-error-msg'; @@ -35,7 +39,9 @@ test('status', async ({ page }) => { // font-sizes can be customized await page.goto(`${TEST_PAGE}&args=fontSize:70;secondaryFontSize:30`); - await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot('custom-font-sizes.png'); + await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot( + 'custom-font-sizes.png' + ); // displays icon await page.goto(`${TEST_PAGE}&args=icon:acknowledged`); @@ -43,5 +49,7 @@ test('status', async ({ page }) => { // displays empty state await page.goto(`${TEST_PAGE}&args=propertyPoint:!null`); - await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot('empty-state.png'); + await expect(frame.locator(COMPONENT_SELECTOR)).toHaveScreenshot( + 'empty-state.png' + ); }); diff --git a/packages/react-components/jest.config.ts b/packages/react-components/jest.config.ts index 28207bb65..4917c132a 100644 --- a/packages/react-components/jest.config.ts +++ b/packages/react-components/jest.config.ts @@ -15,7 +15,10 @@ const config = { ...reactConfig.transform, '^.+\\.svg$': '/svgTransform.js', }, - setupFilesAfterEnv: [...reactConfig.setupFilesAfterEnv, '/jest-setup.js'], + setupFilesAfterEnv: [ + ...reactConfig.setupFilesAfterEnv, + '/jest-setup.js', + ], globalSetup: './global-jest-setup.js', coveragePathIgnorePatterns: [ '/src/components/knowledge-graph/KnowledgeGraphPanel.tsx', diff --git a/packages/react-components/src/common/constants.ts b/packages/react-components/src/common/constants.ts index ac0fe57e0..e2dabba5c 100644 --- a/packages/react-components/src/common/constants.ts +++ b/packages/react-components/src/common/constants.ts @@ -87,6 +87,9 @@ export enum TREND_TYPE { export const DEFAULT_VIEWPORT: Viewport = { duration: '10m' }; -export const DEFAULT_LEGEND: LegendConfig = { position: LEGEND_POSITION.BOTTOM, width: 0 }; +export const DEFAULT_LEGEND: LegendConfig = { + position: LEGEND_POSITION.BOTTOM, + width: 0, +}; export const ECHARTS_GESTURE = 'echarts-gesture'; diff --git a/packages/react-components/src/common/dataTypes.ts b/packages/react-components/src/common/dataTypes.ts index 3a82354fd..142af6309 100644 --- a/packages/react-components/src/common/dataTypes.ts +++ b/packages/react-components/src/common/dataTypes.ts @@ -40,13 +40,15 @@ export const DEFAULT_MESSAGE_OVERRIDES: Required = { liveTimeFrameValueLabel: 'Value', historicalTimeFrameValueLabel: 'Value', noDataStreamsPresentHeader: 'No properties or alarms', - noDataStreamsPresentSubHeader: "This widget doesn't have any properties or alarms.", + noDataStreamsPresentSubHeader: + "This widget doesn't have any properties or alarms.", noDataPresentHeader: 'No data', noDataPresentSubHeader: "There's no data to display for this time range.", liveModeOnly: 'This visualization displays only live data. Choose a live time frame to display data in this visualization.', unsupportedDataTypeHeader: 'Unable to render your data', - unsupportedDataTypeSubHeader: 'This chart only supports the following DataType(s):', + unsupportedDataTypeSubHeader: + 'This chart only supports the following DataType(s):', supportedTypes: 'Number, String, Boolean', }; diff --git a/packages/react-components/src/common/iconUtils.tsx b/packages/react-components/src/common/iconUtils.tsx index dcc4683df..43f420601 100644 --- a/packages/react-components/src/common/iconUtils.tsx +++ b/packages/react-components/src/common/iconUtils.tsx @@ -10,7 +10,12 @@ const DEFAULT_SIZE_PX = 16; export const icons: Icons = { normal(color?: string, size: number = DEFAULT_SIZE_PX) { return ( - + @@ -18,9 +23,20 @@ export const icons: Icons = { }, active(color?: string, size: number = DEFAULT_SIZE_PX) { return ( - + - + @@ -36,7 +52,12 @@ export const icons: Icons = { acknowledged(color?: string, size: number = DEFAULT_SIZE_PX) { return ( - + + @@ -68,7 +94,12 @@ export const icons: Icons = { latched(color?: string, size: number = DEFAULT_SIZE_PX) { return ( - + @@ -77,7 +108,12 @@ export const icons: Icons = { snoozed(color?: string, size: number = DEFAULT_SIZE_PX) { return ( - + @@ -88,7 +124,13 @@ export const icons: Icons = { error(color?: string, size: number = DEFAULT_SIZE_PX) { return ( - + { +export const getIcons = ( + name: StatusIconType, + color?: string, + size?: number +) => { if (icons[name]) { return icons[name](color, size); } diff --git a/packages/react-components/src/common/widgetPropertiesFromInput.spec.ts b/packages/react-components/src/common/widgetPropertiesFromInput.spec.ts index a329d4613..c46030f23 100644 --- a/packages/react-components/src/common/widgetPropertiesFromInput.spec.ts +++ b/packages/react-components/src/common/widgetPropertiesFromInput.spec.ts @@ -16,7 +16,10 @@ it('returns no points when provided no data streams', () => { describe('parsing alarm information', () => { it('returns alarm point when provided data stream of alarm type with a data point', () => { - const dataPoint: DataPoint = { x: new Date(2000, 0, 0).getTime(), y: 'ALARM' }; + const dataPoint: DataPoint = { + x: new Date(2000, 0, 0).getTime(), + y: 'ALARM', + }; const DATA_STREAM: DataStream = { name: 'alarm-stream', id: 'alarms', @@ -36,7 +39,10 @@ describe('parsing alarm information', () => { }); it('returns no alarm point when all data points fall after the viewport', () => { - const dataPoint: DataPoint = { x: new Date(2000, 0, 0).getTime(), y: 'ALARM' }; + const dataPoint: DataPoint = { + x: new Date(2000, 0, 0).getTime(), + y: 'ALARM', + }; const DATA_STREAM: DataStream = { name: 'alarm-stream', id: 'alarms', @@ -56,7 +62,10 @@ describe('parsing alarm information', () => { }); it('returns alarm point when datapoint is before viewport', () => { - const dataPoint: DataPoint = { x: new Date(2000, 0, 0).getTime(), y: 'ALARM' }; + const dataPoint: DataPoint = { + x: new Date(2000, 0, 0).getTime(), + y: 'ALARM', + }; const DATA_STREAM: DataStream = { name: 'alarm-stream', id: 'alarms', @@ -78,7 +87,10 @@ describe('parsing alarm information', () => { describe('parsing property information', () => { it('returns property point when provided data stream with no specified stream type', () => { - const dataPoint: DataPoint = { x: new Date(2000, 0, 0).getTime(), y: 'ALARM' }; + const dataPoint: DataPoint = { + x: new Date(2000, 0, 0).getTime(), + y: 'ALARM', + }; const DATA_STREAM: DataStream = { name: 'property-stream', id: 'alarms', @@ -97,7 +109,10 @@ describe('parsing property information', () => { }); it('returns no property point when all data points fall after the viewport', () => { - const dataPoint: DataPoint = { x: new Date(2000, 0, 0).getTime(), y: 'ALARM' }; + const dataPoint: DataPoint = { + x: new Date(2000, 0, 0).getTime(), + y: 'ALARM', + }; const DATA_STREAM: DataStream = { name: 'property-stream', id: 'alarms', @@ -116,7 +131,10 @@ describe('parsing property information', () => { }); it('returns property point when datapoint is before the viewport', () => { - const dataPoint: DataPoint = { x: new Date(2000, 0, 0).getTime(), y: 'ALARM' }; + const dataPoint: DataPoint = { + x: new Date(2000, 0, 0).getTime(), + y: 'ALARM', + }; const DATA_STREAM: DataStream = { name: 'property-stream', id: 'alarms', diff --git a/packages/react-components/src/common/widgetPropertiesFromInputs.ts b/packages/react-components/src/common/widgetPropertiesFromInputs.ts index 97688c261..b9258dd2c 100644 --- a/packages/react-components/src/common/widgetPropertiesFromInputs.ts +++ b/packages/react-components/src/common/widgetPropertiesFromInputs.ts @@ -2,7 +2,12 @@ import { viewportEndDate } from '@iot-app-kit/core'; import { DATA_ALIGNMENT, StreamType } from './constants'; import { breachedThreshold } from '../utils/breachedThreshold'; import { closestPoint } from '../utils/closestPoint'; -import type { DataStream, DataPoint, Viewport, Threshold } from '@iot-app-kit/core'; +import type { + DataStream, + DataPoint, + Viewport, + Threshold, +} from '@iot-app-kit/core'; const propertyInfo = ({ dataStreams, @@ -47,7 +52,9 @@ const alarmInfo = ({ thresholds: Threshold[]; viewport: Viewport; }) => { - const dataStream = dataStreams.find(({ streamType }) => streamType == StreamType.ALARM); + const dataStream = dataStreams.find( + ({ streamType }) => streamType == StreamType.ALARM + ); const points: DataPoint[] = dataStream?.data || []; const date = viewportEndDate(viewport); const point = closestPoint(points, date, DATA_ALIGNMENT.LEFT); diff --git a/packages/react-components/src/components/bar-chart/barChart.spec.tsx b/packages/react-components/src/components/bar-chart/barChart.spec.tsx index 702d4b9bb..0a4fc5150 100644 --- a/packages/react-components/src/components/bar-chart/barChart.spec.tsx +++ b/packages/react-components/src/components/bar-chart/barChart.spec.tsx @@ -6,7 +6,13 @@ import { BarChart } from './barChart'; const VIEWPORT = { duration: '5m' }; -const DATA_STREAM: DataStream = { id: 'abc-1', data: [], resolution: 0, name: 'my-name', color: 'black' }; +const DATA_STREAM: DataStream = { + id: 'abc-1', + data: [], + resolution: 0, + name: 'my-name', + color: 'black', +}; it('renders', async () => { const query = mockTimeSeriesDataQuery([ @@ -17,7 +23,9 @@ it('renders', async () => { }, ]); - const { container } = render(); + const { container } = render( + + ); const widget = container.querySelector('iot-app-kit-vis-bar-chart'); expect(widget).not.toBeNull(); diff --git a/packages/react-components/src/components/bar-chart/barChart.tsx b/packages/react-components/src/components/bar-chart/barChart.tsx index 7189d4822..243a9975b 100644 --- a/packages/react-components/src/components/bar-chart/barChart.tsx +++ b/packages/react-components/src/components/bar-chart/barChart.tsx @@ -1,10 +1,23 @@ import React from 'react'; -import { StyleSettingsMap, Threshold, TimeSeriesDataQuery, Viewport, ThresholdSettings } from '@iot-app-kit/core'; +import { + StyleSettingsMap, + Threshold, + TimeSeriesDataQuery, + Viewport, + ThresholdSettings, +} from '@iot-app-kit/core'; import { BarChart as BarChartBase } from '@iot-app-kit/charts'; -import type { DataStream as DataStreamViz, YAnnotation } from '@iot-app-kit/charts-core'; +import type { + DataStream as DataStreamViz, + YAnnotation, +} from '@iot-app-kit/charts-core'; import { useTimeSeriesData } from '../../hooks/useTimeSeriesData'; import { useViewport } from '../../hooks/useViewport'; -import { DEFAULT_LEGEND, DEFAULT_VIEWPORT, ECHARTS_GESTURE } from '../../common/constants'; +import { + DEFAULT_LEGEND, + DEFAULT_VIEWPORT, + ECHARTS_GESTURE, +} from '../../common/constants'; import { AxisSettings, ChartSize } from '../../common/chartTypes'; const HOUR_IN_MS = 1000 * 60 * 60; @@ -61,7 +74,9 @@ export const BarChart = (props: BarChartProps) => { // if using echarts then echarts gesture overrides passed in viewport // else explicitly passed in viewport overrides viewport group const utilizedViewport = - (lastUpdatedBy === ECHARTS_GESTURE ? viewport : passedInViewport || viewport) ?? DEFAULT_VIEWPORT; + (lastUpdatedBy === ECHARTS_GESTURE + ? viewport + : passedInViewport || viewport) ?? DEFAULT_VIEWPORT; return ( { setViewport={setViewport} annotations={{ y: allThresholds as YAnnotation[], - thresholdOptions: { showColor: thresholdSettings?.colorBreachedData ?? true }, + thresholdOptions: { + showColor: thresholdSettings?.colorBreachedData ?? true, + }, }} aggregationType={aggregationType} legend={DEFAULT_LEGEND} diff --git a/packages/react-components/src/components/chart/baseChart.tsx b/packages/react-components/src/components/chart/baseChart.tsx index c3e5779c6..4aa9f8ce6 100644 --- a/packages/react-components/src/components/chart/baseChart.tsx +++ b/packages/react-components/src/components/chart/baseChart.tsx @@ -23,7 +23,11 @@ import { MultiYAxisLegend } from './multiYAxis/multiYAxis'; import './chart.css'; import { useContextMenu } from './contextMenu/useContextMenu'; // import { useViewportToMS } from './hooks/useViewportToMS'; -import { DEFAULT_CHART_VISUALIZATION, DEFAULT_TOOLBOX_CONFIG, PERFORMANCE_MODE_THRESHOLD } from './eChartsConstants'; +import { + DEFAULT_CHART_VISUALIZATION, + DEFAULT_TOOLBOX_CONFIG, + PERFORMANCE_MODE_THRESHOLD, +} from './eChartsConstants'; import { useDataZoom } from './hooks/useDataZoom'; import { useViewport } from '../../hooks/useViewport'; import { getXAxis } from './chartOptions/axes/xAxis'; @@ -49,7 +53,12 @@ import TanstackLegend from './legend/tanstackLegend'; /** * Base chart to display Line, Scatter, and Bar charts. */ -const BaseChart = ({ viewport, queries, size = { width: 500, height: 500 }, ...options }: ChartOptions) => { +const BaseChart = ({ + viewport, + queries, + size = { width: 500, height: 500 }, + ...options +}: ChartOptions) => { // Setup instance of echarts const { ref, chartRef } = useECharts(options?.theme); @@ -79,7 +88,12 @@ const BaseChart = ({ viewport, queries, size = { width: 500, height: 500 }, ...o minConstraints, maxConstraints, leftLegendRef, - } = useResizeableEChart(chartRef, size, options.legend?.visible, isBottomAligned); + } = useResizeableEChart( + chartRef, + size, + options.legend?.visible, + isBottomAligned + ); // apply group to echarts useGroupableEChart(chartRef, group); @@ -99,7 +113,12 @@ const BaseChart = ({ viewport, queries, size = { width: 500, height: 500 }, ...o performanceMode, }); - const { handleContextMenu, showContextMenu, contextMenuPos, setShowContextMenu } = useContextMenu(); + const { + handleContextMenu, + showContextMenu, + contextMenuPos, + setShowContextMenu, + } = useContextMenu(); //handle dataZoom updates, which are dependent on user events and viewportInMS changes const viewportInMs = useDataZoom(chartRef, utilizedViewport); @@ -107,7 +126,12 @@ const BaseChart = ({ viewport, queries, size = { width: 500, height: 500 }, ...o const xAxis = getXAxis(options.axis); // this will handle all the Trend Cursors operations - const { onContextMenuClickHandler, trendCursors, trendCursorKeyMap, trendCursorHandlers } = useTrendCursors({ + const { + onContextMenuClickHandler, + trendCursors, + trendCursorKeyMap, + trendCursorHandlers, + } = useTrendCursors({ chartRef, initialGraphic: options.graphic, size: { width: chartWidth, height: chartHeight }, @@ -116,14 +140,20 @@ const BaseChart = ({ viewport, queries, size = { width: 500, height: 500 }, ...o viewportInMs, groupId: group, onContextMenu: handleContextMenu, - visualization: options.defaultVisualizationType ?? DEFAULT_CHART_VISUALIZATION, + visualization: + options.defaultVisualizationType ?? DEFAULT_CHART_VISUALIZATION, significantDigits: options.significantDigits, yAxisOptions: { yAxis, }, }); - const menuOptionClickHandler = ({ action }: { action: Action; e: React.MouseEvent }) => { + const menuOptionClickHandler = ({ + action, + }: { + action: Action; + e: React.MouseEvent; + }) => { onContextMenuClickHandler({ action, posX: contextMenuPos.x }); setShowContextMenu(false); }; @@ -138,7 +168,8 @@ const BaseChart = ({ viewport, queries, size = { width: 500, height: 500 }, ...o const settings = useChartSetOptionSettings(dataStreams); // handle chart event updates - const { chartEventsOptions, chartEventsKeyMap, chartEventsHandlers } = useHandleChartEvents(chartRef); + const { chartEventsOptions, chartEventsKeyMap, chartEventsHandlers } = + useHandleChartEvents(chartRef); const toolTipOptions = { ...convertedOptions.tooltip, @@ -193,7 +224,9 @@ const BaseChart = ({ viewport, queries, size = { width: 500, height: 500 }, ...o }; return ( -
    +
    } @@ -211,7 +248,11 @@ const BaseChart = ({ viewport, queries, size = { width: 500, height: 500 }, ...o onResizeStop={(e) => e.stopPropagation()} resizeHandles={[...setHandles(options.legend?.position || 'right')]} > - +
    @@ -245,9 +286,19 @@ const BaseChart = ({ viewport, queries, size = { width: 500, height: 500 }, ...o > {/* TODO: Need to remove cloudscape table after new tanstack table approved and make tanstack table as default */} {showTanstackTable ? ( - + ) : ( - + )}
    )} diff --git a/packages/react-components/src/components/chart/chartOptions/axes/yAxis.ts b/packages/react-components/src/components/chart/chartOptions/axes/yAxis.ts index 62077ef0d..1a174d194 100644 --- a/packages/react-components/src/components/chart/chartOptions/axes/yAxis.ts +++ b/packages/react-components/src/components/chart/chartOptions/axes/yAxis.ts @@ -2,7 +2,9 @@ import { YAXisComponentOption } from 'echarts'; import { ChartAxisOptions } from '../../types'; import { DEFAULT_Y_AXIS } from '../../eChartsConstants'; -export const convertYAxis = (axis: ChartAxisOptions | undefined): YAXisComponentOption => ({ +export const convertYAxis = ( + axis: ChartAxisOptions | undefined +): YAXisComponentOption => ({ ...DEFAULT_Y_AXIS, name: axis?.yLabel, show: axis?.showY ?? DEFAULT_Y_AXIS.show, diff --git a/packages/react-components/src/components/chart/chartOptions/convertLegend.ts b/packages/react-components/src/components/chart/chartOptions/convertLegend.ts index 7cb305032..08940bd27 100644 --- a/packages/react-components/src/components/chart/chartOptions/convertLegend.ts +++ b/packages/react-components/src/components/chart/chartOptions/convertLegend.ts @@ -4,8 +4,13 @@ import { ChartOptions } from '../types'; const unsetPosition = { top: undefined, bottom: undefined }; -export const convertLegend = (legend: ChartOptions['legend']): LegendComponentOption => { - const configuredPosition = legend?.position && { ...unsetPosition, [legend.position]: 0 }; +export const convertLegend = ( + legend: ChartOptions['legend'] +): LegendComponentOption => { + const configuredPosition = legend?.position && { + ...unsetPosition, + [legend.position]: 0, + }; return { ...DEFAULT_LEGEND, diff --git a/packages/react-components/src/components/chart/chartOptions/convertOptions.ts b/packages/react-components/src/components/chart/chartOptions/convertOptions.ts index 0e8bd6339..ca76817ef 100644 --- a/packages/react-components/src/components/chart/chartOptions/convertOptions.ts +++ b/packages/react-components/src/components/chart/chartOptions/convertOptions.ts @@ -6,7 +6,12 @@ import { EChartsOption, SeriesOption } from 'echarts'; type ConvertChartOptions = Pick< ChartOptions, - 'backgroundColor' | 'axis' | 'gestures' | 'legend' | 'significantDigits' | 'titleText' + | 'backgroundColor' + | 'axis' + | 'gestures' + | 'legend' + | 'significantDigits' + | 'titleText' >; /** diff --git a/packages/react-components/src/components/chart/chartOptions/convertThresholds.ts b/packages/react-components/src/components/chart/chartOptions/convertThresholds.ts index 69ddfc76d..c40869f60 100644 --- a/packages/react-components/src/components/chart/chartOptions/convertThresholds.ts +++ b/packages/react-components/src/components/chart/chartOptions/convertThresholds.ts @@ -1,8 +1,15 @@ -import { AnnotationValue, COMPARISON_OPERATOR, ComparisonOperator } from '@iot-app-kit/core'; +import { + AnnotationValue, + COMPARISON_OPERATOR, + ComparisonOperator, +} from '@iot-app-kit/core'; import { COMPARATOR_MAP } from '../../../common/constants'; import { StyledThreshold } from '../types'; -const comparisonOperatorToLowerYAxis = (comparisonOperator: ComparisonOperator, value: AnnotationValue) => { +const comparisonOperatorToLowerYAxis = ( + comparisonOperator: ComparisonOperator, + value: AnnotationValue +) => { switch (comparisonOperator) { case COMPARISON_OPERATOR.GT: case COMPARISON_OPERATOR.GTE: @@ -13,7 +20,10 @@ const comparisonOperatorToLowerYAxis = (comparisonOperator: ComparisonOperator, } }; -const comparisonOperatorToUpperYAxis = (comparisonOperator: ComparisonOperator, value: AnnotationValue) => { +const comparisonOperatorToUpperYAxis = ( + comparisonOperator: ComparisonOperator, + value: AnnotationValue +) => { switch (comparisonOperator) { case COMPARISON_OPERATOR.LT: case COMPARISON_OPERATOR.LTE: diff --git a/packages/react-components/src/components/chart/chartOptions/convertTooltip.ts b/packages/react-components/src/components/chart/chartOptions/convertTooltip.ts index d45aca61a..d7f30c720 100644 --- a/packages/react-components/src/components/chart/chartOptions/convertTooltip.ts +++ b/packages/react-components/src/components/chart/chartOptions/convertTooltip.ts @@ -4,13 +4,22 @@ import { isNumeric, round } from '@iot-app-kit/core-util'; import { ChartOptions } from '../types'; import { DEFAULT_TOOLTIP } from '../eChartsConstants'; -const formatValue = (significantDigits: ChartOptions['significantDigits']) => (value: string | number | Date) => - value instanceof Date ? value.toISOString() : isNumeric(value) ? `${round(value, significantDigits)}` : value; +const formatValue = + (significantDigits: ChartOptions['significantDigits']) => + (value: string | number | Date) => + value instanceof Date + ? value.toISOString() + : isNumeric(value) + ? `${round(value, significantDigits)}` + : value; -export const convertTooltip = (significantDigits: ChartOptions['significantDigits']): TooltipComponentOption => ({ +export const convertTooltip = ( + significantDigits: ChartOptions['significantDigits'] +): TooltipComponentOption => ({ ...DEFAULT_TOOLTIP, valueFormatter: (value) => { - if (Array.isArray(value)) return value.map(formatValue(significantDigits)).join(', '); + if (Array.isArray(value)) + return value.map(formatValue(significantDigits)).join(', '); return formatValue(significantDigits)(value); }, }); diff --git a/packages/react-components/src/components/chart/chartOptions/converters.spec.ts b/packages/react-components/src/components/chart/chartOptions/converters.spec.ts index b325ddf09..1816587f8 100644 --- a/packages/react-components/src/components/chart/chartOptions/converters.spec.ts +++ b/packages/react-components/src/components/chart/chartOptions/converters.spec.ts @@ -112,7 +112,12 @@ describe('testing converters', () => { it('converts chart options to echarts options', async () => { const { result } = renderHook(() => useConvertedOptions({ - options: { backgroundColor: 'white', axis: MOCK_AXIS, legend: MOCK_LEGEND, significantDigits: 2 }, + options: { + backgroundColor: 'white', + axis: MOCK_AXIS, + legend: MOCK_LEGEND, + significantDigits: 2, + }, series: [], }) ); @@ -211,7 +216,8 @@ describe('testing converters', () => { expect(convertedTooltip.valueFormatter).toBeFunction(); const valueFormatter = convertedTooltip.valueFormatter; - if (valueFormatter) expect(valueFormatter([300, 10, 20000])).toBe('300, 10, 20000'); + if (valueFormatter) + expect(valueFormatter([300, 10, 20000])).toBe('300, 10, 20000'); }); }); @@ -245,8 +251,14 @@ it('converts thresholds to echarts markLine and markArea', async () => { expect(convertedThresholds).toHaveProperty('markArea.data[0][0].yAxis', 10); expect(convertedThresholds).toHaveProperty('markArea.data[0][1].yAxis', 10); - expect(convertedThresholds).toHaveProperty('markArea.data[1][0].yAxis', undefined); + expect(convertedThresholds).toHaveProperty( + 'markArea.data[1][0].yAxis', + undefined + ); expect(convertedThresholds).toHaveProperty('markArea.data[1][1].yAxis', 15); expect(convertedThresholds).toHaveProperty('markArea.data[2][0].yAxis', 5); - expect(convertedThresholds).toHaveProperty('markArea.data[2][1].yAxis', undefined); + expect(convertedThresholds).toHaveProperty( + 'markArea.data[2][1].yAxis', + undefined + ); }); diff --git a/packages/react-components/src/components/chart/chartOptions/seriesAndYAxis/convertSeriesAndYAxis.ts b/packages/react-components/src/components/chart/chartOptions/seriesAndYAxis/convertSeriesAndYAxis.ts index f0b9694b8..64c2cfe89 100644 --- a/packages/react-components/src/components/chart/chartOptions/seriesAndYAxis/convertSeriesAndYAxis.ts +++ b/packages/react-components/src/components/chart/chartOptions/seriesAndYAxis/convertSeriesAndYAxis.ts @@ -1,28 +1,39 @@ import { DataPoint, DataStream, Threshold } from '@iot-app-kit/core'; import { BarSeriesOption, SeriesOption, YAXisComponentOption } from 'echarts'; -import { ChartAxisOptions, ChartStyleSettingsOptions, Visualization } from '../../types'; +import { + ChartAxisOptions, + ChartStyleSettingsOptions, + Visualization, +} from '../../types'; import { convertDataPoint } from '../convertDataPoint'; -import { StyleSettingsMap, getChartStyleSettingsFromMap } from '../style/convertStyles'; +import { + StyleSettingsMap, + getChartStyleSettingsFromMap, +} from '../style/convertStyles'; import { convertYAxis as convertChartYAxis } from '../axes/yAxis'; import { convertThresholds } from '../convertThresholds'; import { ChartStyleSettingsWithDefaults } from '../../utils/getStyles'; -import { DEEMPHASIZE_OPACITY, EMPHASIZE_SCALE_CONSTANT } from '../../eChartsConstants'; +import { + DEEMPHASIZE_OPACITY, + EMPHASIZE_SCALE_CONSTANT, +} from '../../eChartsConstants'; import { GenericSeries } from '../../../../echarts/types'; export const dataValue = (point: DataPoint) => point.y; -const stepTypes: NonNullable[] = [ - 'step-end', - 'step-middle', - 'step-start', -]; +const stepTypes: NonNullable[] = + ['step-end', 'step-middle', 'step-start']; const convertVisualizationType = ( - visualizationType: Required>['visualizationType'] + visualizationType: Required< + Pick + >['visualizationType'] ) => (stepTypes.includes(visualizationType) ? 'line' : visualizationType); const convertStep = ( - visualizationType: Required>['visualizationType'] + visualizationType: Required< + Pick + >['visualizationType'] ) => { if (!stepTypes.includes(visualizationType)) return false; switch (visualizationType) { @@ -37,10 +48,18 @@ const convertStep = ( } }; -const addVisualizationSpecificOptions = (visualizationType: Visualization, series: SeriesOption) => { +const addVisualizationSpecificOptions = ( + visualizationType: Visualization, + series: SeriesOption +) => { switch (visualizationType) { case 'bar': - return { ...series, barGap: '0%', barWidth: '10%', barCategoryGap: '0%' } as BarSeriesOption; + return { + ...series, + barGap: '0%', + barWidth: '10%', + barCategoryGap: '0%', + } as BarSeriesOption; default: return series; } @@ -66,9 +85,16 @@ const convertSeries = ( if (hidden) { opacity = 0; } - const scaledSymbolSize = emphasis === 'emphasize' ? symbolSize + EMPHASIZE_SCALE_CONSTANT : symbolSize; - const scaledLineThickness = emphasis === 'emphasize' ? lineThickness + EMPHASIZE_SCALE_CONSTANT : lineThickness; - const symbolStyle = visualizationType !== 'scatter' && performanceMode ? 'none' : symbol; + const scaledSymbolSize = + emphasis === 'emphasize' + ? symbolSize + EMPHASIZE_SCALE_CONSTANT + : symbolSize; + const scaledLineThickness = + emphasis === 'emphasize' + ? lineThickness + EMPHASIZE_SCALE_CONSTANT + : lineThickness; + const symbolStyle = + visualizationType !== 'scatter' && performanceMode ? 'none' : symbol; const genericSeries = { id, @@ -96,7 +122,10 @@ const convertSeries = ( return addVisualizationSpecificOptions(visualizationType, genericSeries); }; -const convertYAxis = ({ color, yAxis }: ChartStyleSettingsOptions): YAXisComponentOption | undefined => +const convertYAxis = ({ + color, + yAxis, +}: ChartStyleSettingsOptions): YAXisComponentOption | undefined => yAxis && { /** * showing the axis only to ensure that the horizontal @@ -123,7 +152,9 @@ const convertYAxis = ({ color, yAxis }: ChartStyleSettingsOptions): YAXisCompone export const convertSeriesAndYAxis = ( styles: ChartStyleSettingsWithDefaults, - { performanceMode }: { performanceMode?: boolean } = { performanceMode: false } + { performanceMode }: { performanceMode?: boolean } = { + performanceMode: false, + } ) => (datastream: DataStream) => { const series = convertSeries(datastream, styles, { performanceMode }); @@ -135,7 +166,10 @@ export const convertSeriesAndYAxis = }; }; -const addYAxisIndex = (series: T, yAxisIndex = 0): T => ({ +const addYAxisIndex = ( + series: T, + yAxisIndex = 0 +): T => ({ ...series, yAxisIndex, }); @@ -188,14 +222,23 @@ export const useSeriesAndYAxis = ( axis, thresholds, performanceMode, - }: { styleSettings: StyleSettingsMap; thresholds: Threshold[]; axis?: ChartAxisOptions; performanceMode?: boolean } + }: { + styleSettings: StyleSettingsMap; + thresholds: Threshold[]; + axis?: ChartAxisOptions; + performanceMode?: boolean; + } ) => { const defaultYAxis: YAXisComponentOption[] = [convertChartYAxis(axis)]; const convertedThresholds = convertThresholds(thresholds); const getStyles = getChartStyleSettingsFromMap(styleSettings); const { series, yAxis } = datastreams - .map((datastream) => convertSeriesAndYAxis(getStyles(datastream), { performanceMode })(datastream)) + .map((datastream) => + convertSeriesAndYAxis(getStyles(datastream), { performanceMode })( + datastream + ) + ) .reduce(reduceSeriesAndYAxis, { series: [], yAxis: defaultYAxis }); if (series.length > 0) { diff --git a/packages/react-components/src/components/chart/chartOptions/style/convertStyles.spec.tsx b/packages/react-components/src/components/chart/chartOptions/style/convertStyles.spec.tsx index 385d1306b..fff10d45a 100644 --- a/packages/react-components/src/components/chart/chartOptions/style/convertStyles.spec.tsx +++ b/packages/react-components/src/components/chart/chartOptions/style/convertStyles.spec.tsx @@ -1,27 +1,54 @@ import React from 'react'; import { renderHook } from '@testing-library/react'; -import { getChartStyleSettingsFromMap, useChartStyleSettings } from './convertStyles'; +import { + getChartStyleSettingsFromMap, + useChartStyleSettings, +} from './convertStyles'; import { ChartOptions } from '../../types'; import { ChartStoreProvider } from '../../store'; -const DATA_STREAM = { id: 'abc-1', data: [], resolution: 0, name: 'my-name', color: 'red' }; -const DATA_STREAM_WITH_REF = { id: 'abc-2', refId: 'custom', data: [], resolution: 0, name: 'my-name' }; +const DATA_STREAM = { + id: 'abc-1', + data: [], + resolution: 0, + name: 'my-name', + color: 'red', +}; +const DATA_STREAM_WITH_REF = { + id: 'abc-2', + refId: 'custom', + data: [], + resolution: 0, + name: 'my-name', +}; it('can get the correct style settings from the style settings map', () => { - const { result } = renderHook(() => useChartStyleSettings([DATA_STREAM, DATA_STREAM_WITH_REF], {}), { - wrapper: ({ children }) => {children}, - }); + const { result } = renderHook( + () => useChartStyleSettings([DATA_STREAM, DATA_STREAM_WITH_REF], {}), + { + wrapper: ({ children }) => ( + {children} + ), + } + ); const [map] = result.current; - expect(getChartStyleSettingsFromMap(map)(DATA_STREAM)).toEqual(map[DATA_STREAM.id]); + expect(getChartStyleSettingsFromMap(map)(DATA_STREAM)).toEqual( + map[DATA_STREAM.id] + ); }); it('converts default styles for all datastreams', () => { const chartStyleSettings = { // none }; - const { result } = renderHook(() => useChartStyleSettings([DATA_STREAM], chartStyleSettings), { - wrapper: ({ children }) => {children}, - }); + const { result } = renderHook( + () => useChartStyleSettings([DATA_STREAM], chartStyleSettings), + { + wrapper: ({ children }) => ( + {children} + ), + } + ); const [styles, getStyles] = result.current; expect(styles).toHaveProperty(DATA_STREAM.id); @@ -32,7 +59,10 @@ it('converts default styles for all datastreams', () => { }); it('converts styles with custom options for all datastreams', () => { - const chartStyleSettings: Pick = { + const chartStyleSettings: Pick< + ChartOptions, + 'defaultVisualizationType' | 'styleSettings' + > = { defaultVisualizationType: 'scatter', styleSettings: { custom: { @@ -40,9 +70,14 @@ it('converts styles with custom options for all datastreams', () => { }, }, }; - const { result } = renderHook(() => useChartStyleSettings([DATA_STREAM_WITH_REF], chartStyleSettings), { - wrapper: ({ children }) => {children}, - }); + const { result } = renderHook( + () => useChartStyleSettings([DATA_STREAM_WITH_REF], chartStyleSettings), + { + wrapper: ({ children }) => ( + {children} + ), + } + ); const [styles, getStyles] = result.current; expect(styles).toHaveProperty(DATA_STREAM_WITH_REF.id); diff --git a/packages/react-components/src/components/chart/chartOptions/style/convertStyles.ts b/packages/react-components/src/components/chart/chartOptions/style/convertStyles.ts index 6b5dc8d22..0c1b0464c 100644 --- a/packages/react-components/src/components/chart/chartOptions/style/convertStyles.ts +++ b/packages/react-components/src/components/chart/chartOptions/style/convertStyles.ts @@ -1,12 +1,20 @@ import { useMemo } from 'react'; import { DataStream } from '@iot-app-kit/core'; import { ChartOptions } from '../../types'; -import { ChartStyleSettingsWithDefaults, Emphasis, getDefaultStyles, getStyles } from '../../utils/getStyles'; +import { + ChartStyleSettingsWithDefaults, + Emphasis, + getDefaultStyles, + getStyles, +} from '../../utils/getStyles'; import { useChartStore } from '../../store'; import { isDataStreamInList } from '../../../../utils/isDataStreamInList'; import merge from 'lodash.merge'; -type ConvertChartOptions = Pick; +type ConvertChartOptions = Pick< + ChartOptions, + 'defaultVisualizationType' | 'styleSettings' | 'significantDigits' +>; export const convertStyles = ({ @@ -17,7 +25,10 @@ export const convertStyles = hidden, }: ConvertChartOptions & { emphasis?: Emphasis } & { hidden?: boolean }) => ({ refId, color }: DataStream): ChartStyleSettingsWithDefaults => { - const defaultStyles = getDefaultStyles(defaultVisualizationType, significantDigits); + const defaultStyles = getDefaultStyles( + defaultVisualizationType, + significantDigits + ); const userDefinedStyles = getStyles(refId, styleSettings); const emphasisWithDefault = emphasis ?? 'none'; @@ -51,14 +62,21 @@ export const getChartStyleSettingsFromMap = * * @returns [StyleSettingsMap, (datastream: DataStream) => ChartStyleSettingsOptions] */ -export const useChartStyleSettings = (datastreams: DataStream[], chartOptions: ConvertChartOptions) => { - const highlightedDataStreams = useChartStore((state) => state.highlightedDataStreams); +export const useChartStyleSettings = ( + datastreams: DataStream[], + chartOptions: ConvertChartOptions +) => { + const highlightedDataStreams = useChartStore( + (state) => state.highlightedDataStreams + ); const isDataStreamHighlighted = isDataStreamInList(highlightedDataStreams); const hiddenDataStreams = useChartStore((state) => state.hiddenDataStreams); const isDataStreamHidden = isDataStreamInList(hiddenDataStreams); - const datastreamDeps = JSON.stringify(datastreams.map(({ id, refId }) => `${id}-${refId}`)); + const datastreamDeps = JSON.stringify( + datastreams.map(({ id, refId }) => `${id}-${refId}`) + ); const optionsDeps = JSON.stringify(chartOptions); return useMemo(() => { @@ -67,13 +85,26 @@ export const useChartStyleSettings = (datastreams: DataStream[], chartOptions: C const map = datastreams.reduce((styleMap, datastream) => { const isDatastreamHighlighted = isDataStreamHighlighted(datastream); - const emphasis: Emphasis = shouldUseEmphasis ? (isDatastreamHighlighted ? 'emphasize' : 'de-emphasize') : 'none'; + const emphasis: Emphasis = shouldUseEmphasis + ? isDatastreamHighlighted + ? 'emphasize' + : 'de-emphasize' + : 'none'; const isDatastreamHidden = isDataStreamHidden(datastream); - styleMap[datastream.id] = convertStyles({ ...chartOptions, emphasis, hidden: isDatastreamHidden })(datastream); + styleMap[datastream.id] = convertStyles({ + ...chartOptions, + emphasis, + hidden: isDatastreamHidden, + })(datastream); return styleMap; }, {}); return [map, getChartStyleSettingsFromMap(map)] as const; // disabling because dataStreams and options are stringified // eslint-disable-next-line react-hooks/exhaustive-deps - }, [datastreamDeps, optionsDeps, highlightedDataStreams, isDataStreamHighlighted]); + }, [ + datastreamDeps, + optionsDeps, + highlightedDataStreams, + isDataStreamHighlighted, + ]); }; diff --git a/packages/react-components/src/components/chart/chartOptions/useChartSetOptionSettings.ts b/packages/react-components/src/components/chart/chartOptions/useChartSetOptionSettings.ts index 856ffec3f..76c51c747 100644 --- a/packages/react-components/src/components/chart/chartOptions/useChartSetOptionSettings.ts +++ b/packages/react-components/src/components/chart/chartOptions/useChartSetOptionSettings.ts @@ -17,7 +17,10 @@ export const useChartSetOptionSettings = (datastreams: DataStream[]) => { * https://echarts.apache.org/en/api.html#echartsInstance.setOption * */ - const settings = datastreamsDepsRef.current !== datastreamsDeps ? { replaceMerge: ['series'] } : undefined; + const settings = + datastreamsDepsRef.current !== datastreamsDeps + ? { replaceMerge: ['series'] } + : undefined; datastreamsDepsRef.current = datastreamsDeps; return { ...settings, lazyUpdate: true }; }, [datastreamsDeps]); diff --git a/packages/react-components/src/components/chart/contextMenu/ChartContextMenu.tsx b/packages/react-components/src/components/chart/contextMenu/ChartContextMenu.tsx index 90938d7fb..9e350f5a9 100644 --- a/packages/react-components/src/components/chart/contextMenu/ChartContextMenu.tsx +++ b/packages/react-components/src/components/chart/contextMenu/ChartContextMenu.tsx @@ -7,7 +7,13 @@ import { InternalGraphicComponentGroupOption } from '../trendCursor/types'; export type Action = 'add' | 'delete' | 'copy'; interface ChartContextMenu { position: { x: number; y: number }; - menuOptionClickHandler: ({ action, e }: { action: Action; e: React.MouseEvent }) => void; + menuOptionClickHandler: ({ + action, + e, + }: { + action: Action; + e: React.MouseEvent; + }) => void; onOutSideClickHandler: (e: PointerEvent) => void; trendCursors: InternalGraphicComponentGroupOption[]; } @@ -19,7 +25,11 @@ const ChartContextMenu = ({ }: ChartContextMenu) => { return ( { e.stop(); }; - return { handleContextMenu, showContextMenu, contextMenuPos, setShowContextMenu }; + return { + handleContextMenu, + showContextMenu, + contextMenuPos, + setShowContextMenu, + }; }; diff --git a/packages/react-components/src/components/chart/events/useHandleChartEvents.ts b/packages/react-components/src/components/chart/events/useHandleChartEvents.ts index 7b9a22c5a..64c467dc3 100644 --- a/packages/react-components/src/components/chart/events/useHandleChartEvents.ts +++ b/packages/react-components/src/components/chart/events/useHandleChartEvents.ts @@ -2,8 +2,12 @@ import { MutableRefObject, useCallback, useEffect, useState } from 'react'; import { EChartsOption, EChartsType, ElementEvent } from 'echarts'; import { KeyMap } from 'react-hotkeys'; -export const useHandleChartEvents = (chartRef: MutableRefObject) => { - const [chartEventsOptions, setChartEventsOptions] = useState({}); +export const useHandleChartEvents = ( + chartRef: MutableRefObject +) => { + const [chartEventsOptions, setChartEventsOptions] = useState( + {} + ); const [shiftDown, setShiftDown] = useState(false); const [prevIsPanning, setPrevIsPanning] = useState(false); diff --git a/packages/react-components/src/components/chart/hooks/useChartId.ts b/packages/react-components/src/components/chart/hooks/useChartId.ts index b0de6c3e5..729c003d7 100644 --- a/packages/react-components/src/components/chart/hooks/useChartId.ts +++ b/packages/react-components/src/components/chart/hooks/useChartId.ts @@ -2,7 +2,8 @@ import { useMemo } from 'react'; import { v4 as uuid } from 'uuid'; -const generateId = (id?: string) => (id && id.length > 0 ? id : `chart-${uuid()}`); +const generateId = (id?: string) => + id && id.length > 0 ? id : `chart-${uuid()}`; /** * Hook that provides a memoized id for the chart. diff --git a/packages/react-components/src/components/chart/hooks/useDataZoom.ts b/packages/react-components/src/components/chart/hooks/useDataZoom.ts index bba0cd5e9..412e35a98 100644 --- a/packages/react-components/src/components/chart/hooks/useDataZoom.ts +++ b/packages/react-components/src/components/chart/hooks/useDataZoom.ts @@ -3,7 +3,10 @@ import { DataZoomComponentOption, EChartsType } from 'echarts'; import { Viewport, viewportManager } from '@iot-app-kit/core'; import { useViewport } from '../../../hooks/useViewport'; import { ECHARTS_GESTURE } from '../../../common/constants'; -import { DEFAULT_DATA_ZOOM, LIVE_MODE_REFRESH_RATE_MS } from '../eChartsConstants'; +import { + DEFAULT_DATA_ZOOM, + LIVE_MODE_REFRESH_RATE_MS, +} from '../eChartsConstants'; import { convertViewportToMs } from '../trendCursor/calculations/viewport'; import { DEFAULT_VIEWPORT } from '../../time-sync'; import { useEffectOnce } from 'react-use'; @@ -12,7 +15,9 @@ type ValidOption = { startValue: number; endValue: number; }; -const isValidZoomOption = (option: DataZoomComponentOption | undefined): option is ValidOption => +const isValidZoomOption = ( + option: DataZoomComponentOption | undefined +): option is ValidOption => option != null && option.startValue != null && option.endValue != null && @@ -20,15 +25,19 @@ const isValidZoomOption = (option: DataZoomComponentOption | undefined): option typeof option.endValue === 'number'; type ViewportMode = 'live' | 'static'; -const getViewportMode = (convertedViewport: ReturnType): ViewportMode => - convertedViewport.isDurationViewport ? 'live' : 'static'; +const getViewportMode = ( + convertedViewport: ReturnType +): ViewportMode => (convertedViewport.isDurationViewport ? 'live' : 'static'); type TickState = { mode: ViewportMode; viewport: Viewport | undefined; convertedViewport: ReturnType; }; -type TickAction = { type: 'tick' } | { type: 'pause' } | { type: 'updateViewport'; viewport: Viewport | undefined }; +type TickAction = + | { type: 'tick' } + | { type: 'pause' } + | { type: 'updateViewport'; viewport: Viewport | undefined }; const stateFromViewport = (viewport: Viewport | undefined) => { const convertedViewport = convertViewportToMs(viewport); return { @@ -55,9 +64,15 @@ const reducer = (state: TickState, action: TickAction): TickState => { return state; }; -export const useDataZoom = (chartRef: MutableRefObject, viewport: Viewport | undefined) => { +export const useDataZoom = ( + chartRef: MutableRefObject, + viewport: Viewport | undefined +) => { const { setViewport, group } = useViewport(); - const [{ mode, convertedViewport }, dispatch] = useReducer(reducer, stateFromViewport(viewport)); + const [{ mode, convertedViewport }, dispatch] = useReducer( + reducer, + stateFromViewport(viewport) + ); /** * function for setting the dataZoom chart option on the echart instance @@ -121,11 +136,15 @@ export const useDataZoom = (chartRef: MutableRefObject, view // Synchronize animation with refresh rate frame = requestAnimationFrame(() => { // there should only be 1 datazoom option for the x axis - const dataZoomOptions = chart.getOption().dataZoom as DataZoomComponentOption[]; + const dataZoomOptions = chart.getOption() + .dataZoom as DataZoomComponentOption[]; const horizontalZoom = dataZoomOptions.at(0); if (!isValidZoomOption(horizontalZoom)) return; setViewport( - { start: new Date(horizontalZoom.startValue), end: new Date(horizontalZoom.endValue) }, + { + start: new Date(horizontalZoom.startValue), + end: new Date(horizontalZoom.endValue), + }, ECHARTS_GESTURE ); }); @@ -168,7 +187,10 @@ export const useDataZoom = (chartRef: MutableRefObject, view * to the chart dataZoom */ useEffect(() => { - const { unsubscribe } = viewportManager.subscribe(group, handleViewportUpdate); + const { unsubscribe } = viewportManager.subscribe( + group, + handleViewportUpdate + ); return unsubscribe; }, [handleViewportUpdate, group]); diff --git a/packages/react-components/src/components/chart/hooks/useVisualizedDataStreams.ts b/packages/react-components/src/components/chart/hooks/useVisualizedDataStreams.ts index 45c5f9e43..eafd7c46a 100644 --- a/packages/react-components/src/components/chart/hooks/useVisualizedDataStreams.ts +++ b/packages/react-components/src/components/chart/hooks/useVisualizedDataStreams.ts @@ -1,18 +1,29 @@ import { useMemo } from 'react'; -import { DataStream, TimeSeriesDataQuery, Viewport, getVisibleData } from '@iot-app-kit/core'; +import { + DataStream, + TimeSeriesDataQuery, + Viewport, + getVisibleData, +} from '@iot-app-kit/core'; import { useTimeSeriesData } from '../../../hooks/useTimeSeriesData'; import { useViewport } from '../../../hooks/useViewport'; import { DEFAULT_VIEWPORT, StreamType } from '../../../common/constants'; -const isNotAlarmStream = ({ streamType }: DataStream) => streamType !== StreamType.ALARM; +const isNotAlarmStream = ({ streamType }: DataStream) => + streamType !== StreamType.ALARM; const dataStreamIsLoading = ({ isLoading }: DataStream) => isLoading; const dataStreamHasError = ({ error }: DataStream) => error != null; -export const useVisualizedDataStreams = (queries: TimeSeriesDataQuery[], passedInViewport?: Viewport) => { +export const useVisualizedDataStreams = ( + queries: TimeSeriesDataQuery[], + passedInViewport?: Viewport +) => { const { viewport, lastUpdatedBy } = useViewport(); // synchro-charts gesture overrides passed in viewport else explicitly passed in viewport overrides viewport group - const utilizedViewport = (lastUpdatedBy ? viewport : passedInViewport || viewport) ?? DEFAULT_VIEWPORT; + const utilizedViewport = + (lastUpdatedBy ? viewport : passedInViewport || viewport) ?? + DEFAULT_VIEWPORT; const { dataStreams, thresholds } = useTimeSeriesData({ viewport: utilizedViewport, @@ -25,14 +36,19 @@ export const useVisualizedDataStreams = (queries: TimeSeriesDataQuery[], passedI // Line | Scatter | Bar charts do not support alarm streams. const dataStreamsWithoutAlarms = dataStreams.filter(isNotAlarmStream); - const hasError = useMemo(() => dataStreamsWithoutAlarms.some(dataStreamHasError), [dataStreamsWithoutAlarms]); + const hasError = useMemo( + () => dataStreamsWithoutAlarms.some(dataStreamHasError), + [dataStreamsWithoutAlarms] + ); const isLoading = useMemo( () => !hasError && dataStreamsWithoutAlarms.some(dataStreamIsLoading), [hasError, dataStreamsWithoutAlarms] ); - const visibleData = dataStreamsWithoutAlarms.flatMap(({ data }) => getVisibleData(data, utilizedViewport, false)); + const visibleData = dataStreamsWithoutAlarms.flatMap(({ data }) => + getVisibleData(data, utilizedViewport, false) + ); return { hasError, diff --git a/packages/react-components/src/components/chart/legend/tanstackLegend.tsx b/packages/react-components/src/components/chart/legend/tanstackLegend.tsx index 656eef5b2..6b5b8ae65 100644 --- a/packages/react-components/src/components/chart/legend/tanstackLegend.tsx +++ b/packages/react-components/src/components/chart/legend/tanstackLegend.tsx @@ -16,13 +16,17 @@ type legendOptions = { }; const TanstackLegend = (legendOptions: legendOptions) => { - const { items: allItemsTanstack, columnDefinitions: columnDefinitionsTanstack } = - useChartsLegendTanstack(legendOptions); + const { + items: allItemsTanstack, + columnDefinitions: columnDefinitionsTanstack, + } = useChartsLegendTanstack(legendOptions); return ( []} + columnDefinitions={ + columnDefinitionsTanstack as ColumnDef[] + } stickyColumns={{ first: 1 }} stickyHeader /> diff --git a/packages/react-components/src/components/chart/legend/tanstackTable.tsx b/packages/react-components/src/components/chart/legend/tanstackTable.tsx index 142ce5a25..fbf188a0d 100644 --- a/packages/react-components/src/components/chart/legend/tanstackTable.tsx +++ b/packages/react-components/src/components/chart/legend/tanstackTable.tsx @@ -31,7 +31,12 @@ type tanstackTableOptions = { stickyHeader?: boolean; }; -export const TanstackTable = ({ data, columnDefinitions, stickyColumns, stickyHeader }: tanstackTableOptions) => { +export const TanstackTable = ({ + data, + columnDefinitions, + stickyColumns, + stickyHeader, +}: tanstackTableOptions) => { const columns: ColumnDef[] = [...columnDefinitions]; const [sorting, setSorting] = useState([]); const sortingIcons = { @@ -54,14 +59,20 @@ export const TanstackTable = ({ data, columnDefinitions, stickyColumns, stickyHe return (
    -
    +
    {legendData.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header, i) => (
    - {flexRender(header.column.columnDef.header, header.getContext())} + {flexRender( + header.column.columnDef.header, + header.getContext() + )} {{ asc: ( - sorted asending + sorted asending ), desc: ( - sorted desending + sorted desending ), }[header.column.getIsSorted() as string] ?? (stickyColumns?.first !== i + 1 && ( - desending not active + desending not active ))} @@ -112,7 +139,9 @@ export const TanstackTable = ({ data, columnDefinitions, stickyColumns, stickyHe { it('populates Legend Cell correctly', () => { const { result: chart } = renderHook(() => - useChartsLegend({ datastreams: [DATA_STREAM], series: mockSeries, width: 100, graphic: [] }) + useChartsLegend({ + datastreams: [DATA_STREAM], + series: mockSeries, + width: 100, + graphic: [], + }) ); expect(chart.current.items).toStrictEqual([ { @@ -82,7 +87,12 @@ describe('useChartsLegend sets correct items', () => { it('populates column definitions correctly', () => { const { result: chartData } = renderHook(() => - useChartsLegend({ datastreams: [DATA_STREAM], series: mockSeries, width: 100, graphic: [] }) + useChartsLegend({ + datastreams: [DATA_STREAM], + series: mockSeries, + width: 100, + graphic: [], + }) ); const e = { name: 'Average Wind Speed', diff --git a/packages/react-components/src/components/chart/legend/useChartsLegend.tsx b/packages/react-components/src/components/chart/legend/useChartsLegend.tsx index 2efbf52dd..aa9030a3f 100644 --- a/packages/react-components/src/components/chart/legend/useChartsLegend.tsx +++ b/packages/react-components/src/components/chart/legend/useChartsLegend.tsx @@ -23,14 +23,27 @@ import Hide from './hide.svg'; import Show from './show.svg'; import Button from '@cloudscape-design/components/button'; -const LegendCell = (e: { datastream: DataStream; lineColor: string; name: string; width: number }) => { +const LegendCell = (e: { + datastream: DataStream; + lineColor: string; + name: string; + width: number; +}) => { const { datastream, lineColor, name, width } = e; - const highlightDataStream = useChartStore((state) => state.highlightDataStream); - const unHighlightDataStream = useChartStore((state) => state.unHighlightDataStream); - const highlightedDataStreams = useChartStore((state) => state.highlightedDataStreams); + const highlightDataStream = useChartStore( + (state) => state.highlightDataStream + ); + const unHighlightDataStream = useChartStore( + (state) => state.unHighlightDataStream + ); + const highlightedDataStreams = useChartStore( + (state) => state.highlightedDataStreams + ); const isDataStreamHighlighted = isDataStreamInList(highlightedDataStreams); const nameRef = useRef(null); - const isNameTruncated = nameRef.current?.scrollWidth && nameRef.current?.scrollWidth > nameRef.current?.clientWidth; + const isNameTruncated = + nameRef.current?.scrollWidth && + nameRef.current?.scrollWidth > nameRef.current?.clientWidth; const toggleHighlighted = () => { if (isDataStreamHighlighted(datastream)) { unHighlightDataStream(datastream); @@ -63,10 +76,14 @@ const LegendCell = (e: { datastream: DataStream; lineColor: string; name: string // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
    -
    )); @@ -112,7 +139,9 @@ const LegendCell = (e: { datastream: DataStream; lineColor: string; name: string {hideShowButton} {lineIcon}
    Data streams
    , - cell: (e: { datastream: DataStream; lineColor: string; name: string; width: number }) => , + cell: (e: { + datastream: DataStream; + lineColor: string; + name: string; + width: number; + }) => , isRowHeader: true, }; @@ -161,30 +195,40 @@ const useChartsLegend = ({ const getHeaderNode = (g: InternalGraphicComponentGroupOption) => { const headerGroup = g.children[1] as GraphicComponentGroupOption; - const text = headerGroup.children.find((c) => c.type === 'text') as GraphicComponentTextOption; + const text = headerGroup.children.find( + (c) => c.type === 'text' + ) as GraphicComponentTextOption; return (
    {getTcHeader(text?.style?.text ?? '')}
    -
    +
    ); }; - const [columnDefinitions, setColumnDefinitions] = useState>>([ - legendColumnDefinition, - ]); + const [columnDefinitions, setColumnDefinitions] = useState< + Array> + >([legendColumnDefinition]); const [items, setItems] = useState>([]); const graphicDeps = JSON.stringify(graphic); const seriesDeps = JSON.stringify(series); - const TcCell = (e: { datastream: DataStream; tc: { [id: string]: number } }, id: string) => { + const TcCell = ( + e: { datastream: DataStream; tc: { [id: string]: number } }, + id: string + ) => { const { datastream } = e; const hiddenDataStreams = useChartStore((state) => state.hiddenDataStreams); const isDataStreamHidden = isDataStreamInList(hiddenDataStreams); return ( <> -
    {e.tc[id]}
    +
    + {e.tc[id]} +
    ); }; @@ -195,7 +239,8 @@ const useChartsLegend = ({ return { id, header: getHeaderNode(g), - cell: (e: { datastream: DataStream; tc: { [id: string]: number } }) => TcCell(e, id), + cell: (e: { datastream: DataStream; tc: { [id: string]: number } }) => + TcCell(e, id), sortingField: id, }; }); @@ -205,8 +250,14 @@ const useChartsLegend = ({ // currItems will hold values in the { gId: value } format const currItems = series.map((lineItem, index) => { const values = graphic - .map((gr) => ({ key: gr.id as string, value: gr.yAxisMarkerValue[index] })) - .reduce((obj, item) => Object.assign(obj, { [item.key]: item.value }), {}); + .map((gr) => ({ + key: gr.id as string, + value: gr.yAxisMarkerValue[index], + })) + .reduce( + (obj, item) => Object.assign(obj, { [item.key]: item.value }), + {} + ); return { name: lineItem.name, // TODO: may need to update this for non-line type graphs diff --git a/packages/react-components/src/components/chart/legend/useChartsLegendTanstack.tsx b/packages/react-components/src/components/chart/legend/useChartsLegendTanstack.tsx index 1e2211d3d..ee21c4e48 100644 --- a/packages/react-components/src/components/chart/legend/useChartsLegendTanstack.tsx +++ b/packages/react-components/src/components/chart/legend/useChartsLegendTanstack.tsx @@ -21,22 +21,48 @@ import { LEGEND_NAME_MIN_WIDTH_FACTOR } from '../eChartsConstants'; import Hide from './hide.svg'; import Show from './show.svg'; import Button from '@cloudscape-design/components/button'; -import { ColumnDef, createColumnHelper, CellContext as TanCellContext, RowData } from '@tanstack/react-table'; +import { + ColumnDef, + createColumnHelper, + CellContext as TanCellContext, + RowData, +} from '@tanstack/react-table'; -type CellContext = TanCellContext & { +type CellContext = TanCellContext< + TData, + TValue +> & { additionalProp: string; }; -type TableRowType = { datastream: DataStream; lineColor: string; name: string; width: number }; +type TableRowType = { + datastream: DataStream; + lineColor: string; + name: string; + width: number; +}; -const LegendCell = (e: { datastream: DataStream; lineColor: string; name: string; width: number }) => { +const LegendCell = (e: { + datastream: DataStream; + lineColor: string; + name: string; + width: number; +}) => { const { datastream, lineColor, name, width } = e; - const highlightDataStream = useChartStore((state) => state.highlightDataStream); - const unHighlightDataStream = useChartStore((state) => state.unHighlightDataStream); - const highlightedDataStreams = useChartStore((state) => state.highlightedDataStreams); + const highlightDataStream = useChartStore( + (state) => state.highlightDataStream + ); + const unHighlightDataStream = useChartStore( + (state) => state.unHighlightDataStream + ); + const highlightedDataStreams = useChartStore( + (state) => state.highlightedDataStreams + ); const isDataStreamHighlighted = isDataStreamInList(highlightedDataStreams); const nameRef = useRef(null); - const isNameTruncated = nameRef.current?.scrollWidth && nameRef.current?.scrollWidth > nameRef.current?.clientWidth; + const isNameTruncated = + nameRef.current?.scrollWidth && + nameRef.current?.scrollWidth > nameRef.current?.clientWidth; const toggleHighlighted = () => { if (isDataStreamHighlighted(datastream)) { unHighlightDataStream(datastream); @@ -69,10 +95,14 @@ const LegendCell = (e: { datastream: DataStream; lineColor: string; name: string // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
    -
    )); @@ -118,7 +158,9 @@ const LegendCell = (e: { datastream: DataStream; lineColor: string; name: string {hideShowButton} {lineIcon}
    'Legends', { id: 'Legends', - header: () =>
    Data streams
    , + header: () => ( +
    Data streams
    + ), cell: (info: unknown) => { const e = info as CellContext; return ; @@ -175,30 +219,40 @@ const useChartsLegendTanstack = ({ if (!headerGroup) { return null; } - const text = headerGroup.children.find((c) => c.type === 'text') as GraphicComponentTextOption; + const text = headerGroup.children.find( + (c) => c.type === 'text' + ) as GraphicComponentTextOption; return (
    {getTcHeader(text?.style?.text ?? '')}
    -
    +
    ); }; - const [columnDefinitions, setColumnDefinitions] = useState[]>([ - legendColumnDefinitions, - ]); + const [columnDefinitions, setColumnDefinitions] = useState< + ColumnDef[] + >([legendColumnDefinitions]); const [items, setItems] = useState>([]); const graphicDeps = JSON.stringify(graphic); const seriesDeps = JSON.stringify(series); - const TcCell = (e: { datastream: DataStream; tc: { [id: string]: number } }, id: string) => { + const TcCell = ( + e: { datastream: DataStream; tc: { [id: string]: number } }, + id: string + ) => { const { datastream } = e; const hiddenDataStreams = useChartStore((state) => state.hiddenDataStreams); const isDataStreamHidden = isDataStreamInList(hiddenDataStreams); return ( <> -
    {e.tc[id]}
    +
    + {e.tc[id]} +
    ); }; @@ -210,7 +264,10 @@ const useChartsLegendTanstack = ({ id: id, header: () => getHeaderNode(g), cell: (info: unknown) => { - const e = info as CellContext<{ datastream: DataStream; tc: { [id: string]: number } }, string>; + const e = info as CellContext< + { datastream: DataStream; tc: { [id: string]: number } }, + string + >; return TcCell(e.row.original, id); }, enableSorting: true, @@ -222,8 +279,14 @@ const useChartsLegendTanstack = ({ // currItems will hold values in the { gId: value } format const currItems = series.map((lineItem, index) => { const values = graphic - .map((gr) => ({ key: gr.id as string, value: gr.yAxisMarkerValue[index] })) - .reduce((obj, item) => Object.assign(obj, { [item.key]: item.value }), {}); + .map((gr) => ({ + key: gr.id as string, + value: gr.yAxisMarkerValue[index], + })) + .reduce( + (obj, item) => Object.assign(obj, { [item.key]: item.value }), + {} + ); return { name: lineItem.name, // TODO: may need to update this for non-line type graphs diff --git a/packages/react-components/src/components/chart/multiYAxis/multiYAxis.spec.tsx b/packages/react-components/src/components/chart/multiYAxis/multiYAxis.spec.tsx index a8333ec34..053fe5fb2 100644 --- a/packages/react-components/src/components/chart/multiYAxis/multiYAxis.spec.tsx +++ b/packages/react-components/src/components/chart/multiYAxis/multiYAxis.spec.tsx @@ -17,15 +17,21 @@ const DATA_STREAM: DataStream = { }; const setupStore = () => { - const { result: setYMax } = renderHook(() => useChartStore((state) => state.setYMax)); - const { result: setYMin } = renderHook(() => useChartStore((state) => state.setYMin)); + const { result: setYMax } = renderHook(() => + useChartStore((state) => state.setYMax) + ); + const { result: setYMin } = renderHook(() => + useChartStore((state) => state.setYMin) + ); act(() => { setYMax.current(DATA_STREAM.id, { value: 1 }); setYMin.current(DATA_STREAM.id, { value: 0 }); }); }; const teardownStore = () => { - const { result: clearYAxis } = renderHook(() => useChartStore((state) => state.clearYAxis)); + const { result: clearYAxis } = renderHook(() => + useChartStore((state) => state.clearYAxis) + ); act(() => { clearYAxis.current(DATA_STREAM.id); }); @@ -70,17 +76,24 @@ describe('MultiYAxisLegend', () => { act(() => { fireEvent.pointerEnter(screen.getByText('0')); }); - const { result: highlightedDataStreams, rerender: rerenderHighlightedDataStreams } = renderHook(() => + const { + result: highlightedDataStreams, + rerender: rerenderHighlightedDataStreams, + } = renderHook(() => useChartStore((state) => state.highlightedDataStreams) ); expect(highlightedDataStreams.current).toEqual([DATA_STREAM]); - expect(isDataStreamInList(highlightedDataStreams.current)(DATA_STREAM)).toBeTrue(); + expect( + isDataStreamInList(highlightedDataStreams.current)(DATA_STREAM) + ).toBeTrue(); act(() => { fireEvent.pointerLeave(screen.getByText('0')); }); rerenderHighlightedDataStreams(); expect(highlightedDataStreams.current).toEqual([]); - expect(isDataStreamInList(highlightedDataStreams.current)(DATA_STREAM)).toBeFalse(); + expect( + isDataStreamInList(highlightedDataStreams.current)(DATA_STREAM) + ).toBeFalse(); }); }); diff --git a/packages/react-components/src/components/chart/multiYAxis/multiYAxis.tsx b/packages/react-components/src/components/chart/multiYAxis/multiYAxis.tsx index 88ad71321..64884751a 100644 --- a/packages/react-components/src/components/chart/multiYAxis/multiYAxis.tsx +++ b/packages/react-components/src/components/chart/multiYAxis/multiYAxis.tsx @@ -21,7 +21,10 @@ type MultiYAxisLegendOptions = { * yMin: list of YAxisLegendOption for the min values of a datastream with a custom yAxis * yMax: list of YAxisLegendOption for the max values of a datastream with a custom yAxis */ -export const MultiYAxisLegend = ({ height, datastreams }: MultiYAxisLegendOptions) => { +export const MultiYAxisLegend = ({ + height, + datastreams, +}: MultiYAxisLegendOptions) => { const { yMax, yMin } = useCustomYAxis(datastreams); const marginHeight = DEFAULT_MARGIN * 2; @@ -39,7 +42,12 @@ export const MultiYAxisLegend = ({ height, datastreams }: MultiYAxisLegendOption }} > - +
    ); }; diff --git a/packages/react-components/src/components/chart/multiYAxis/useCustomYAxis.ts b/packages/react-components/src/components/chart/multiYAxis/useCustomYAxis.ts index ba156fc5e..0da715a4a 100644 --- a/packages/react-components/src/components/chart/multiYAxis/useCustomYAxis.ts +++ b/packages/react-components/src/components/chart/multiYAxis/useCustomYAxis.ts @@ -9,7 +9,11 @@ type HandleMapOptionsProps = { datastreams: DataStream[]; }; -const handleMapOptions = ({ map, handleClear, datastreams }: HandleMapOptionsProps) => +const handleMapOptions = ({ + map, + handleClear, + datastreams, +}: HandleMapOptionsProps) => Object.entries(map).map(([datastreamId, value]) => { const datastream = datastreams.find(({ id }) => id === datastreamId); if (!datastream) handleClear(datastreamId); @@ -24,9 +28,17 @@ export const useCustomYAxis = (datastreams: DataStream[]) => { const yMins = useChartStore((state) => state.yMins); const handleClear = useChartStore((state) => state.clearYAxis); - const yMax: YAxisLegendOption[] = handleMapOptions({ map: yMaxes, datastreams, handleClear }); + const yMax: YAxisLegendOption[] = handleMapOptions({ + map: yMaxes, + datastreams, + handleClear, + }); - const yMin: YAxisLegendOption[] = handleMapOptions({ map: yMins, datastreams, handleClear }); + const yMin: YAxisLegendOption[] = handleMapOptions({ + map: yMins, + datastreams, + handleClear, + }); return { yMax, diff --git a/packages/react-components/src/components/chart/multiYAxis/yAxisMenu.tsx b/packages/react-components/src/components/chart/multiYAxis/yAxisMenu.tsx index 904547a07..6de35070b 100644 --- a/packages/react-components/src/components/chart/multiYAxis/yAxisMenu.tsx +++ b/packages/react-components/src/components/chart/multiYAxis/yAxisMenu.tsx @@ -35,10 +35,21 @@ type YAxisLegendOptions = { axes: YAxisLegendOption[]; menuPosition?: 'bottom' | 'top'; }; -export const YAxisLegend = ({ maxHeight, label, axes, menuPosition }: YAxisLegendOptions) => { - const highlightDataStream = useChartStore((state) => state.highlightDataStream); - const unHighlightDataStream = useChartStore((state) => state.unHighlightDataStream); - const highlightedDataStreams = useChartStore((state) => state.highlightedDataStreams); +export const YAxisLegend = ({ + maxHeight, + label, + axes, + menuPosition, +}: YAxisLegendOptions) => { + const highlightDataStream = useChartStore( + (state) => state.highlightDataStream + ); + const unHighlightDataStream = useChartStore( + (state) => state.unHighlightDataStream + ); + const highlightedDataStreams = useChartStore( + (state) => state.highlightedDataStreams + ); const isDataStreamHighlighted = isDataStreamInList(highlightedDataStreams); const [menuOpen, setMenuOpen] = useState(false); const [menuSizeRef, { height }] = useMeasure(); @@ -51,14 +62,21 @@ export const YAxisLegend = ({ maxHeight, label, axes, menuPosition }: YAxisLegen preventViewportOverflow={false} classNames='axis-menu' styles={{ width: MULTI_Y_AXIS_LEGEND_WIDTH, maxHeight: menuMaxHeight }} - placement={menuPosition && menuPosition === 'top' ? 'top-start' : 'bottom-start'} + placement={ + menuPosition && menuPosition === 'top' ? 'top-start' : 'bottom-start' + } shadow open={menuOpen} offset={[0, MENU_OFFSET]} referenceElement={(ref) => (
    - + { unHighlightDataStream(datastream); }} - iconEnd={() => {datastream?.unit}} + iconEnd={() => ( + {datastream?.unit} + )} highlighted={isDataStreamHighlighted(datastream)} >
    {value != null ? ( diff --git a/packages/react-components/src/components/chart/store/context.tsx b/packages/react-components/src/components/chart/store/context.tsx index e4982deaf..45f56faf2 100644 --- a/packages/react-components/src/components/chart/store/context.tsx +++ b/packages/react-components/src/components/chart/store/context.tsx @@ -1,11 +1,21 @@ -import React, { createContext, useRef, PropsWithChildren, useEffect } from 'react'; +import React, { + createContext, + useRef, + PropsWithChildren, + useEffect, +} from 'react'; import { createChartStore } from './store'; import useDataStore from '../../../store'; // Recommended usage from zustand https://docs.pmnd.rs/zustand/previous-versions/zustand-v3-create-context -export const ChartStoreContext = createContext>(createChartStore()); +export const ChartStoreContext = createContext< + ReturnType +>(createChartStore()); -export const ChartStoreProvider = ({ id, children }: PropsWithChildren<{ id: string }>) => { +export const ChartStoreProvider = ({ + id, + children, +}: PropsWithChildren<{ id: string }>) => { const storeRef = useRef>(); useEffect( () => () => { @@ -21,5 +31,9 @@ export const ChartStoreProvider = ({ id, children }: PropsWithChildren<{ id: str if (!store) store = storeState.addChart(id); storeRef.current = store; } - return {children}; + return ( + + {children} + + ); }; diff --git a/packages/react-components/src/components/chart/store/contextDataStreams.ts b/packages/react-components/src/components/chart/store/contextDataStreams.ts index f47ded267..5d3b2afd1 100644 --- a/packages/react-components/src/components/chart/store/contextDataStreams.ts +++ b/packages/react-components/src/components/chart/store/contextDataStreams.ts @@ -13,18 +13,26 @@ export interface DataStreamsState extends DataStreamsData { unHideDataStream: (datastream?: DataStream) => void; } -export const createDataStreamsSlice: StateCreator = (set) => ({ +export const createDataStreamsSlice: StateCreator = ( + set +) => ({ highlightedDataStreams: [], hiddenDataStreams: [], highlightDataStream: (datastream?: DataStream) => set((state) => { if (!datastream) return state; - return { highlightedDataStreams: [...state.highlightedDataStreams, datastream] }; + return { + highlightedDataStreams: [...state.highlightedDataStreams, datastream], + }; }), unHighlightDataStream: (datastream) => set((state) => { if (!datastream) return state; - return { highlightedDataStreams: state.highlightedDataStreams.filter(({ id }) => id !== datastream.id) }; + return { + highlightedDataStreams: state.highlightedDataStreams.filter( + ({ id }) => id !== datastream.id + ), + }; }), hideDataStream: (datastream?: DataStream) => set((state) => { @@ -34,6 +42,10 @@ export const createDataStreamsSlice: StateCreator = (set) => ( unHideDataStream: (datastream) => set((state) => { if (!datastream) return state; - return { hiddenDataStreams: state.hiddenDataStreams.filter(({ id }) => id !== datastream.id) }; + return { + hiddenDataStreams: state.hiddenDataStreams.filter( + ({ id }) => id !== datastream.id + ), + }; }), }); diff --git a/packages/react-components/src/components/chart/store/useChartStore.spec.ts b/packages/react-components/src/components/chart/store/useChartStore.spec.ts index 0789e48f5..576d940d4 100644 --- a/packages/react-components/src/components/chart/store/useChartStore.spec.ts +++ b/packages/react-components/src/components/chart/store/useChartStore.spec.ts @@ -25,8 +25,12 @@ const DATA_STREAM_2: DataStream = { }; const setupStore = () => { - const { result: setDataStreamHidden } = renderHook(() => useChartStore((state) => state.hideDataStream)); - const { result: setDataStreamHighlighted } = renderHook(() => useChartStore((state) => state.highlightDataStream)); + const { result: setDataStreamHidden } = renderHook(() => + useChartStore((state) => state.hideDataStream) + ); + const { result: setDataStreamHighlighted } = renderHook(() => + useChartStore((state) => state.highlightDataStream) + ); act(() => { setDataStreamHidden.current(DATA_STREAM); setDataStreamHighlighted.current(DATA_STREAM); @@ -34,7 +38,9 @@ const setupStore = () => { }; const teardownStore = () => { - const { result: setDataStreamUnHidden } = renderHook(() => useChartStore((state) => state.unHideDataStream)); + const { result: setDataStreamUnHidden } = renderHook(() => + useChartStore((state) => state.unHideDataStream) + ); const { result: setDataStreamUnHighlighted } = renderHook(() => useChartStore((state) => state.unHighlightDataStream) ); @@ -49,15 +55,21 @@ describe('Data Stream Store Hide/Show and Highlighting', () => { afterEach(teardownStore); it('adds hidden datastreams to data stream store', () => { - const { result: hiddenDataStreams } = renderHook(() => useChartStore((state) => state.hiddenDataStreams)); + const { result: hiddenDataStreams } = renderHook(() => + useChartStore((state) => state.hiddenDataStreams) + ); const isDataStreamHidden = isDataStreamInList(hiddenDataStreams.current); expect(isDataStreamHidden(DATA_STREAM)).toBe(true); expect(isDataStreamHidden(DATA_STREAM_2)).toBe(false); }); it('adds highlighted datastreams to data stream store', () => { - const { result: highlightedDataStreams } = renderHook(() => useChartStore((state) => state.highlightedDataStreams)); - const isDataStreamHidden = isDataStreamInList(highlightedDataStreams.current); + const { result: highlightedDataStreams } = renderHook(() => + useChartStore((state) => state.highlightedDataStreams) + ); + const isDataStreamHidden = isDataStreamInList( + highlightedDataStreams.current + ); expect(isDataStreamHidden(DATA_STREAM)).toBe(true); expect(isDataStreamHidden(DATA_STREAM_2)).toBe(false); }); diff --git a/packages/react-components/src/components/chart/store/useChartStore.ts b/packages/react-components/src/components/chart/store/useChartStore.ts index cd8b39a1e..9aa43ec60 100644 --- a/packages/react-components/src/components/chart/store/useChartStore.ts +++ b/packages/react-components/src/components/chart/store/useChartStore.ts @@ -2,4 +2,5 @@ import { useContext } from 'react'; import { ChartStoreContext } from './context'; import { StateData } from './store'; -export const useChartStore = (selector: (state: StateData) => T) => useContext(ChartStoreContext)(selector); +export const useChartStore = (selector: (state: StateData) => T) => + useContext(ChartStoreContext)(selector); diff --git a/packages/react-components/src/components/chart/tests/baseChart.spec.tsx b/packages/react-components/src/components/chart/tests/baseChart.spec.tsx index 80f451e7e..bb9c43be6 100644 --- a/packages/react-components/src/components/chart/tests/baseChart.spec.tsx +++ b/packages/react-components/src/components/chart/tests/baseChart.spec.tsx @@ -9,7 +9,12 @@ import { TanstackTable } from '../legend/tanstackTable'; const VIEWPORT = { duration: '5m' }; -const DATA_STREAM: DataStream = { id: 'abc-1', data: [], resolution: 0, name: 'my-name' }; +const DATA_STREAM: DataStream = { + id: 'abc-1', + data: [], + resolution: 0, + name: 'my-name', +}; jest.mock('echarts', () => ({ use: jest.fn(), init: jest.fn(), @@ -40,12 +45,24 @@ describe('Chart Component Testing', () => { }, ]); - const element = render(); + const element = render( + + ); expect(element).not.toBeNull(); }); it('Chart renders', () => { - const element = render(); + const element = render( + + ); expect(element).not.toBeNull(); }); }); @@ -73,7 +90,9 @@ describe('Chart slider testing', () => { }; const { container } = render(); - expect(container.getElementsByClassName('react-resizable-handle-se').length).toBe(1); + expect( + container.getElementsByClassName('react-resizable-handle-se').length + ).toBe(1); }); it('should show resize slider when show legend feature is off', () => { @@ -98,7 +117,9 @@ describe('Chart slider testing', () => { }; const { container } = render(); - expect(container.getElementsByClassName('react-resizable-handle-se').length).toBe(0); + expect( + container.getElementsByClassName('react-resizable-handle-se').length + ).toBe(0); }); }); @@ -120,6 +141,8 @@ describe('Tanstack Table testing', () => { stickyHeader /> ); - expect(container.getElementsByClassName('tanstack-table-container').length).toBe(1); + expect( + container.getElementsByClassName('tanstack-table-container').length + ).toBe(1); }); }); diff --git a/packages/react-components/src/components/chart/trendCursor/calculations/calculateSeriesMakers.ts b/packages/react-components/src/components/chart/trendCursor/calculations/calculateSeriesMakers.ts index a4c62d52b..3e9a5e27d 100644 --- a/packages/react-components/src/components/chart/trendCursor/calculations/calculateSeriesMakers.ts +++ b/packages/react-components/src/components/chart/trendCursor/calculations/calculateSeriesMakers.ts @@ -24,7 +24,8 @@ const convertValueIntoPixels = ( value: number, chartRef: MutableRefObject, seriesIndex: LineSeriesOption['yAxisIndex'] -): number => chartRef.current?.convertToPixel({ yAxisIndex: seriesIndex }, value) ?? 0; +): number => + chartRef.current?.convertToPixel({ yAxisIndex: seriesIndex }, value) ?? 0; export const calculateSeriesMakers = ( series: SeriesOption[], @@ -50,10 +51,18 @@ export const calculateSeriesMakers = ( // There is no right value , so we take the last available value value = data[data.length - 1][1]; } else { - value = handleDataValueInterpolation({ visualization, data, timestampInMs, leftIndex, rightIndex }); + value = handleDataValueInterpolation({ + visualization, + data, + timestampInMs, + leftIndex, + rightIndex, + }); } - trendCursorsSeriesMakersValue[seriesIndex] = Number(value.toFixed(significantDigits ?? DEFAULT_PRECISION)); + trendCursorsSeriesMakersValue[seriesIndex] = Number( + value.toFixed(significantDigits ?? DEFAULT_PRECISION) + ); // Converting the Y axis value to pixels trendCursorsSeriesMakersInPixels[seriesIndex] = convertValueIntoPixels( diff --git a/packages/react-components/src/components/chart/trendCursor/calculations/calculations.ts b/packages/react-components/src/components/chart/trendCursor/calculations/calculations.ts index 0b5698e1b..8e8b361eb 100644 --- a/packages/react-components/src/components/chart/trendCursor/calculations/calculations.ts +++ b/packages/react-components/src/components/chart/trendCursor/calculations/calculations.ts @@ -13,13 +13,22 @@ export const setXWithBounds = (size: SizeConfig, x: number) => { }; // this calculated the new X in pixels when the chart is resized. -export const calculateXFromTimestamp = (timestampInMs: number, chartRef: MutableRefObject) => - chartRef.current?.convertToPixel({ xAxisId: DEFAULT_X_AXIS_ID }, timestampInMs) ?? 0; +export const calculateXFromTimestamp = ( + timestampInMs: number, + chartRef: MutableRefObject +) => + chartRef.current?.convertToPixel( + { xAxisId: DEFAULT_X_AXIS_ID }, + timestampInMs + ) ?? 0; // for a given user's right click co-ordinate's timestamp, find the nearest trend cursor // all trend cursors is associated with a timestamp, // we use that to see which TC is the closest to user's right click -export const calculateNearestTcIndex = (graphic: InternalGraphicComponentGroupOption[], clickTimestamp: number) => { +export const calculateNearestTcIndex = ( + graphic: InternalGraphicComponentGroupOption[], + clickTimestamp: number +) => { const tcTimestamps = graphic.map((g) => g.timestampInMs); let closest = -1; let min = Number.MAX_VALUE; @@ -39,13 +48,20 @@ export const calculateNearestTcIndex = (graphic: InternalGraphicComponentGroupOp // TC timestamp // Cop timestamp // Series name : value -export const formatCopyData = (graphic: InternalGraphicComponentGroupOption, series: SeriesOption[]) => { - let output = `Trend cursor timestamp \t ${new Date(graphic.timestampInMs).toLocaleDateString()}${new Date( +export const formatCopyData = ( + graphic: InternalGraphicComponentGroupOption, + series: SeriesOption[] +) => { + let output = `Trend cursor timestamp \t ${new Date( + graphic.timestampInMs + ).toLocaleDateString()}${new Date( graphic.timestampInMs ).toLocaleTimeString()} \n`; const currDate = new Date(); - output = output + `Copy timestamp \t ${currDate.toLocaleDateString()} ${currDate.toLocaleTimeString()} \n`; + output = + output + + `Copy timestamp \t ${currDate.toLocaleDateString()} ${currDate.toLocaleTimeString()} \n`; series.forEach((s, index) => { output = output + `${s.name} \t ${graphic.yAxisMarkerValue[index]} \n`; }); diff --git a/packages/react-components/src/components/chart/trendCursor/calculations/interpolation.ts b/packages/react-components/src/components/chart/trendCursor/calculations/interpolation.ts index b2d873eb4..b14aa701f 100644 --- a/packages/react-components/src/components/chart/trendCursor/calculations/interpolation.ts +++ b/packages/react-components/src/components/chart/trendCursor/calculations/interpolation.ts @@ -29,7 +29,9 @@ export const handleDataValueInterpolation = ({ const timeMax = data[rightIndex][0]; const LeftDelta = Math.abs(timeMin - timestampInMs); const rightDelta = Math.abs(timeMax - timestampInMs); - const ratio = Number((LeftDelta / rightDelta).toPrecision(DEFAULT_PRECISION)); + const ratio = Number( + (LeftDelta / rightDelta).toPrecision(DEFAULT_PRECISION) + ); if (ratio < 1) { return data[leftIndex][1]; } else { diff --git a/packages/react-components/src/components/chart/trendCursor/calculations/timestamp.ts b/packages/react-components/src/components/chart/trendCursor/calculations/timestamp.ts index d91bc54d9..1282932c3 100644 --- a/packages/react-components/src/components/chart/trendCursor/calculations/timestamp.ts +++ b/packages/react-components/src/components/chart/trendCursor/calculations/timestamp.ts @@ -5,10 +5,18 @@ import { DEFAULT_X_AXIS_ID } from '../../eChartsConstants'; // this function calculated the timestamp of the location of the user click. // the timestamp is calculated based on the viewport and X value of the click point[x, y] // this is a simple linear interpolation -export const calculateTimeStamp = (xInPixel: number, chartRef: MutableRefObject) => - chartRef.current?.convertFromPixel({ xAxisId: DEFAULT_X_AXIS_ID }, xInPixel) ?? 0; +export const calculateTimeStamp = ( + xInPixel: number, + chartRef: MutableRefObject +) => + chartRef.current?.convertFromPixel( + { xAxisId: DEFAULT_X_AXIS_ID }, + xInPixel + ) ?? 0; export const getTrendCursorHeaderTimestampText = (timestampInMs: number) => { return [ - `{timestamp|${new Date(timestampInMs).toLocaleDateString()} ${new Date(timestampInMs).toLocaleTimeString()}}`, + `{timestamp|${new Date(timestampInMs).toLocaleDateString()} ${new Date( + timestampInMs + ).toLocaleTimeString()}}`, ].join('\n'); }; diff --git a/packages/react-components/src/components/chart/trendCursor/calculations/viewport.ts b/packages/react-components/src/components/chart/trendCursor/calculations/viewport.ts index 1c8545542..b4ebf03cf 100644 --- a/packages/react-components/src/components/chart/trendCursor/calculations/viewport.ts +++ b/packages/react-components/src/components/chart/trendCursor/calculations/viewport.ts @@ -1,7 +1,9 @@ import { DurationViewport, Viewport } from '@iot-app-kit/core'; import { parseDuration } from '../../../../utils/time'; -export const isDurationViewport = (viewport: Viewport): viewport is DurationViewport => +export const isDurationViewport = ( + viewport: Viewport +): viewport is DurationViewport => (viewport as DurationViewport).duration !== undefined; // TODO: test this once echarts live mode is supported // the width here represents the width of the view port in milli seconds @@ -10,7 +12,12 @@ export const convertViewportToMs = (viewport?: Viewport) => { const isDuration = !!viewport && isDurationViewport(viewport); if (isDuration) { const duration = parseDuration(viewport.duration); - return { widthInMs: duration, initial: Date.now() - duration, end: Date.now(), isDurationViewport: isDuration }; + return { + widthInMs: duration, + initial: Date.now() - duration, + end: Date.now(), + isDurationViewport: isDuration, + }; } else { const start = new Date(viewport?.start ?? 0).getTime(); const end = new Date(viewport?.end ?? 0).getTime(); diff --git a/packages/react-components/src/components/chart/trendCursor/chartOptions/handleOptions.ts b/packages/react-components/src/components/chart/trendCursor/chartOptions/handleOptions.ts index eb417ea16..3baf17da3 100644 --- a/packages/react-components/src/components/chart/trendCursor/chartOptions/handleOptions.ts +++ b/packages/react-components/src/components/chart/trendCursor/chartOptions/handleOptions.ts @@ -3,7 +3,13 @@ import { useEffect, useRef } from 'react'; import { calculateSeriesMakers } from '../calculations/calculateSeriesMakers'; import { updateTrendCursorLineMarkers } from '../getTrendCursor/components/markers'; -export const useHandleChartOptions = ({ graphic, setGraphic, chartRef, series, ...options }: handleChangeProps) => { +export const useHandleChartOptions = ({ + graphic, + setGraphic, + chartRef, + series, + ...options +}: handleChangeProps) => { const graphicRef = useRef(graphic); const optionsRef = useRef(options); const seriesRef = useRef(series); @@ -17,7 +23,10 @@ export const useHandleChartOptions = ({ graphic, setGraphic, chartRef, series, . useEffect(() => { const newG = graphicRef.current.map((g) => { - const { trendCursorsSeriesMakersValue, trendCursorsSeriesMakersInPixels } = calculateSeriesMakers( + const { + trendCursorsSeriesMakersValue, + trendCursorsSeriesMakersInPixels, + } = calculateSeriesMakers( seriesRef.current, g.timestampInMs, chartRef, @@ -25,7 +34,10 @@ export const useHandleChartOptions = ({ graphic, setGraphic, chartRef, series, . optionsRef.current.significantDigits ); g.yAxisMarkerValue = trendCursorsSeriesMakersValue; - g.children = updateTrendCursorLineMarkers(g.children, trendCursorsSeriesMakersInPixels); + g.children = updateTrendCursorLineMarkers( + g.children, + trendCursorsSeriesMakersInPixels + ); return g; }); diff --git a/packages/react-components/src/components/chart/trendCursor/constants/index.ts b/packages/react-components/src/components/chart/trendCursor/constants/index.ts index 8c9f8fd02..4d463bad4 100644 --- a/packages/react-components/src/components/chart/trendCursor/constants/index.ts +++ b/packages/react-components/src/components/chart/trendCursor/constants/index.ts @@ -1,6 +1,13 @@ // Trend Cursor constants -export const DEBUG_TREND_CURSORS = localStorage.getItem('DEBUG_TREND_CURSORS') ?? false; -export const TREND_CURSOR_HEADER_COLORS = ['#DA7596', '#2EA597', '#688AE8', '#A783E1', '#E07941']; +export const DEBUG_TREND_CURSORS = + localStorage.getItem('DEBUG_TREND_CURSORS') ?? false; +export const TREND_CURSOR_HEADER_COLORS = [ + '#DA7596', + '#2EA597', + '#688AE8', + '#A783E1', + '#E07941', +]; export const TREND_CURSOR_HEADER_WIDTH = 120; export const TREND_CURSOR_LINE_COLOR = 'black'; export const TREND_CURSOR_LINE_WIDTH = 2; @@ -9,7 +16,8 @@ export const MAX_TREND_CURSORS = 5; export const TREND_CURSOR_HEADER_TEXT_COLOR = 'white'; export const TREND_CURSOR_HEADER_OFFSET = 28; export const TREND_CURSOR_HEADER_BACKGROUND_COLOR = 'black'; -export const TREND_CURSOR_CLOSE_BUTTON_Y_OFFSET = TREND_CURSOR_HEADER_OFFSET + 0.5; +export const TREND_CURSOR_CLOSE_BUTTON_Y_OFFSET = + TREND_CURSOR_HEADER_OFFSET + 0.5; export const TREND_CURSOR_CLOSE_BUTTON_X_OFFSET = 45; export const TREND_CURSOR_MARKER_RADIUS = 5; export const TREND_CURSOR_LINE_GRAPHIC_INDEX = 0; diff --git a/packages/react-components/src/components/chart/trendCursor/getTrendCursor/components/deleteButton/index.ts b/packages/react-components/src/components/chart/trendCursor/getTrendCursor/components/deleteButton/index.ts index 3d1c4c968..69cdd18e3 100644 --- a/packages/react-components/src/components/chart/trendCursor/getTrendCursor/components/deleteButton/index.ts +++ b/packages/react-components/src/components/chart/trendCursor/getTrendCursor/components/deleteButton/index.ts @@ -6,7 +6,9 @@ import { TREND_CURSOR_Z_INDEX, } from '../../../constants'; -export const addTCDeleteButton = (uId: string): GraphicComponentImageOption => ({ +export const addTCDeleteButton = ( + uId: string +): GraphicComponentImageOption => ({ id: `delete-button-${uId}`, type: 'image', z: TREND_CURSOR_Z_INDEX + 1, diff --git a/packages/react-components/src/components/chart/trendCursor/getTrendCursor/components/header/index.ts b/packages/react-components/src/components/chart/trendCursor/getTrendCursor/components/header/index.ts index 15eab8ee3..f91186ebe 100644 --- a/packages/react-components/src/components/chart/trendCursor/getTrendCursor/components/header/index.ts +++ b/packages/react-components/src/components/chart/trendCursor/getTrendCursor/components/header/index.ts @@ -7,7 +7,11 @@ import { } from '../../../constants'; import { getTrendCursorHeaderTimestampText } from '../../../calculations/timestamp'; -export const addTCHeader = (uId: string, timestampInMs: number, color?: string) => ({ +export const addTCHeader = ( + uId: string, + timestampInMs: number, + color?: string +) => ({ type: 'group', id: `header-${uId}`, draggable: 'horizontal' as const, diff --git a/packages/react-components/src/components/chart/trendCursor/getTrendCursor/components/markers/index.ts b/packages/react-components/src/components/chart/trendCursor/getTrendCursor/components/markers/index.ts index e83f137e7..63c49ace2 100644 --- a/packages/react-components/src/components/chart/trendCursor/getTrendCursor/components/markers/index.ts +++ b/packages/react-components/src/components/chart/trendCursor/getTrendCursor/components/markers/index.ts @@ -44,8 +44,15 @@ export const updateTrendCursorLineMarkers = ( // TREND_CURSOR_HEADER_GRAPHIC_INDEX --> TC header // TREND_CURSOR_CLOSE_GRAPHIC_INDEX --> close button // from index TREND_CURSOR_LINE_MARKERS_GRAPHIC_INDEX --> series markers - for (let i = TREND_CURSOR_LINE_MARKERS_GRAPHIC_INDEX; i < elements.length; i++) { - elements[i].y = trendCursorsSeriesMakersInPixels[i - TREND_CURSOR_LINE_MARKERS_GRAPHIC_INDEX]; + for ( + let i = TREND_CURSOR_LINE_MARKERS_GRAPHIC_INDEX; + i < elements.length; + i++ + ) { + elements[i].y = + trendCursorsSeriesMakersInPixels[ + i - TREND_CURSOR_LINE_MARKERS_GRAPHIC_INDEX + ]; } return elements; }; @@ -56,10 +63,15 @@ export const upsertTrendCursorLineMarkers = ( series: SeriesOption[] ) => { // read all the non-marker elements - const newElements = [...elements.slice(0, TREND_CURSOR_LINE_MARKERS_GRAPHIC_INDEX)]; + const newElements = [ + ...elements.slice(0, TREND_CURSOR_LINE_MARKERS_GRAPHIC_INDEX), + ]; // read the unique Id from the delete button, which is in the format delete-button-uuid - const uId = elements[TREND_CURSOR_CLOSE_GRAPHIC_INDEX].id?.toString().split('delete-button-')[1] ?? ''; + const uId = + elements[TREND_CURSOR_CLOSE_GRAPHIC_INDEX].id + ?.toString() + .split('delete-button-')[1] ?? ''; // add the new markers for (let i = 0; i < trendCursorsSeriesMakersInPixels.length; i++) { newElements.push( @@ -88,7 +100,11 @@ export const upsertTrendCursorLineMarkers = ( return newElements; }; -export const addTCMarkers = (uId: string, yAxisMarkers: number[], series: SeriesOption[]) => +export const addTCMarkers = ( + uId: string, + yAxisMarkers: number[], + series: SeriesOption[] +) => yAxisMarkers.map((marker, index) => ({ id: `circle-${index}-${uId}`, type: 'circle', diff --git a/packages/react-components/src/components/chart/trendCursor/getTrendCursor/getTrendCursor.ts b/packages/react-components/src/components/chart/trendCursor/getTrendCursor/getTrendCursor.ts index 452c917e8..43ca11089 100644 --- a/packages/react-components/src/components/chart/trendCursor/getTrendCursor/getTrendCursor.ts +++ b/packages/react-components/src/components/chart/trendCursor/getTrendCursor/getTrendCursor.ts @@ -30,13 +30,14 @@ export const getNewTrendCursor = ({ const timestampInMs = timestamp ?? calculateTimeStamp(posX, chartRef); const boundedX = setXWithBounds(size, posX); - const { trendCursorsSeriesMakersValue, trendCursorsSeriesMakersInPixels } = calculateSeriesMakers( - series, - timestampInMs, - chartRef, - visualization, - significantDigits - ); + const { trendCursorsSeriesMakersValue, trendCursorsSeriesMakersInPixels } = + calculateSeriesMakers( + series, + timestampInMs, + chartRef, + visualization, + significantDigits + ); // this check makes sure that the chart lines are rendered first and only then we render the TC markers // this avoids the re-render issue because of changing key on change in query diff --git a/packages/react-components/src/components/chart/trendCursor/mouseEvents/handlers/drag/update.ts b/packages/react-components/src/components/chart/trendCursor/mouseEvents/handlers/drag/update.ts index 34d58b8f7..dca883432 100644 --- a/packages/react-components/src/components/chart/trendCursor/mouseEvents/handlers/drag/update.ts +++ b/packages/react-components/src/components/chart/trendCursor/mouseEvents/handlers/drag/update.ts @@ -8,18 +8,26 @@ import { TREND_CURSOR_LINE_GRAPHIC_INDEX, } from '../../../constants'; import { setXWithBounds } from '../../../calculations/calculations'; -import { ondragUpdateGraphicProps, ondragUpdateTrendCursorElementsProps } from '../../../types'; +import { + ondragUpdateGraphicProps, + ondragUpdateTrendCursorElementsProps, +} from '../../../types'; import { updateTrendCursorLineMarkers } from '../../../getTrendCursor/components/markers'; import { getTrendCursorHeaderTimestampText } from '../../../calculations/timestamp'; import { calculateSeriesMakers } from '../../../calculations/calculateSeriesMakers'; -const onDragUpdateTrendCursorLine = (elements: GraphicComponentElementOption[]) => { +const onDragUpdateTrendCursorLine = ( + elements: GraphicComponentElementOption[] +) => { // specifically setting the line graphic x value to 0 so that it follows the parent's X const lineGraphic = elements[TREND_CURSOR_LINE_GRAPHIC_INDEX]; lineGraphic.x = 0; return lineGraphic; }; -const onDragUpdateTrendCursorHeaderText = (elements: GraphicComponentElementOption[], timeInMs: number) => { +const onDragUpdateTrendCursorHeaderText = ( + elements: GraphicComponentElementOption[], + timeInMs: number +) => { const headerGraphic = elements[TREND_CURSOR_HEADER_GRAPHIC_INDEX]; headerGraphic.x = 0; @@ -47,10 +55,10 @@ export const onDragUpdateTrendCursorElements = ({ return [ onDragUpdateTrendCursorLine(elements), onDragUpdateTrendCursorHeaderText(elements, timeInMs), - ...updateTrendCursorLineMarkers(elements, trendCursorsSeriesMakersInPixels).slice( - TREND_CURSOR_CLOSE_GRAPHIC_INDEX, - elements.length - ), + ...updateTrendCursorLineMarkers( + elements, + trendCursorsSeriesMakersInPixels + ).slice(TREND_CURSOR_CLOSE_GRAPHIC_INDEX, elements.length), ]; }; export const onDragUpdateTrendCursor = ({ @@ -64,13 +72,14 @@ export const onDragUpdateTrendCursor = ({ significantDigits, }: ondragUpdateGraphicProps) => { // calculate the new Y for the series markers - const { trendCursorsSeriesMakersInPixels, trendCursorsSeriesMakersValue } = calculateSeriesMakers( - series, - timeInMs, - chartRef, - visualization, - significantDigits - ); + const { trendCursorsSeriesMakersInPixels, trendCursorsSeriesMakersValue } = + calculateSeriesMakers( + series, + timeInMs, + chartRef, + visualization, + significantDigits + ); // this section updates the internal data of graphic, this data is used to render the legend component data // update the X value of the TC diff --git a/packages/react-components/src/components/chart/trendCursor/mouseEvents/useTrendCursorsEvents.ts b/packages/react-components/src/components/chart/trendCursor/mouseEvents/useTrendCursorsEvents.ts index 19613d953..9fa95d009 100644 --- a/packages/react-components/src/components/chart/trendCursor/mouseEvents/useTrendCursorsEvents.ts +++ b/packages/react-components/src/components/chart/trendCursor/mouseEvents/useTrendCursorsEvents.ts @@ -1,12 +1,19 @@ import { useCallback, useEffect, useRef } from 'react'; import { getPlugin } from '@iot-app-kit/core'; -import { calculateNearestTcIndex, calculateXFromTimestamp, formatCopyData } from '../calculations/calculations'; +import { + calculateNearestTcIndex, + calculateXFromTimestamp, + formatCopyData, +} from '../calculations/calculations'; import { v4 as uuid } from 'uuid'; import { getNewTrendCursor } from '../getTrendCursor/getTrendCursor'; import useDataStore from '../../../../store'; import { Action } from '../../contextMenu/ChartContextMenu'; import copy from 'copy-to-clipboard'; -import { MAX_TREND_CURSORS, TREND_CURSOR_CLOSE_GRAPHIC_INDEX } from '../constants'; +import { + MAX_TREND_CURSORS, + TREND_CURSOR_CLOSE_GRAPHIC_INDEX, +} from '../constants'; import { UseEventsProps } from '../types'; import { onDragUpdateTrendCursor } from './handlers/drag/update'; import { calculateTimeStamp } from '../calculations/timestamp'; @@ -30,9 +37,15 @@ const useTrendCursorsEvents = ({ getColor, }: UseEventsProps) => { // sync mode actions - const addTrendCursorsToSyncState = useDataStore((state) => state.addTrendCursors); - const updateTrendCursorsInSyncState = useDataStore((state) => state.updateTrendCursors); - const deleteTrendCursorsInSyncState = useDataStore((state) => state.deleteTrendCursors); + const addTrendCursorsToSyncState = useDataStore( + (state) => state.addTrendCursors + ); + const updateTrendCursorsInSyncState = useDataStore( + (state) => state.updateTrendCursors + ); + const deleteTrendCursorsInSyncState = useDataStore( + (state) => state.deleteTrendCursors + ); const seriesRef = useRef(series); const sizeRef = useRef(size); @@ -54,13 +67,25 @@ const useTrendCursorsEvents = ({ setGraphicRef.current = setGraphic; visualizationRef.current = visualization; significantDigitsRef.current = significantDigits; - }, [size, isInCursorAddMode, setGraphic, isInSyncMode, visualization, significantDigits, series, graphic]); + }, [ + size, + isInCursorAddMode, + setGraphic, + isInSyncMode, + visualization, + significantDigits, + series, + graphic, + ]); // shared add function between the context menu and on click action const addNewTrendCursor = useCallback( ({ posX, ignoreHotKey }: { posX: number; ignoreHotKey: boolean }) => { // when adding through the context menu, we can ignore the hot key press - if ((ignoreHotKey || isInCursorAddModeRef.current) && graphicRef.current.length < MAX_TREND_CURSORS) { + if ( + (ignoreHotKey || isInCursorAddModeRef.current) && + graphicRef.current.length < MAX_TREND_CURSORS + ) { if (isInSyncModeRef.current) { const timestampInMs = calculateTimeStamp(posX, chartRef); addTrendCursorsToSyncState({ @@ -120,7 +145,10 @@ const useTrendCursorsEvents = ({ ); const deleteTrendCursorByPosition = (clickedPosX: number) => { const timestampOfClick = calculateTimeStamp(clickedPosX, chartRef); - const toBeDeletedGraphicIndex = calculateNearestTcIndex(graphicRef.current, timestampOfClick); + const toBeDeletedGraphicIndex = calculateNearestTcIndex( + graphicRef.current, + timestampOfClick + ); deleteTrendCursor(toBeDeletedGraphicIndex); }; // The below event handlers will handle both sync and standalone mode @@ -128,13 +156,21 @@ const useTrendCursorsEvents = ({ // standalone mode --> updates the local state useEffect(() => { const currentChart = chartRef.current; - if (isInCursorAddModeRef.current && graphicRef.current.length < MAX_TREND_CURSORS) { + if ( + isInCursorAddModeRef.current && + graphicRef.current.length < MAX_TREND_CURSORS + ) { chartRef.current?.getZr().setCursorStyle('crosshair'); } else { chartRef.current?.getZr().setCursorStyle('default'); // in case we get out of add mode and mouse does not move, switch back to default cursor } - const mouseMoveEventHandler = () => mouseoverHandler(isInCursorAddModeRef.current, graphicRef.current, chartRef); + const mouseMoveEventHandler = () => + mouseoverHandler( + isInCursorAddModeRef.current, + graphicRef.current, + chartRef + ); // this handles all the clicks on the chart // this click would be either @@ -143,7 +179,8 @@ const useTrendCursorsEvents = ({ const clickEventHandler = (event: ElementEvent) => { // index of the clicked graphic const graphicIndex = graphicRef.current.findIndex( - (g) => g.children[TREND_CURSOR_CLOSE_GRAPHIC_INDEX].id === event?.target?.id + (g) => + g.children[TREND_CURSOR_CLOSE_GRAPHIC_INDEX].id === event?.target?.id ); if (graphicIndex !== -1) { deleteTrendCursor(graphicIndex); @@ -159,13 +196,19 @@ const useTrendCursorsEvents = ({ event.stop(); chartRef.current?.getZr().setCursorStyle('grabbing'); - let graphicIndex = graphicRef.current.findIndex((g) => g.children[0].id === event.target.id); + let graphicIndex = graphicRef.current.findIndex( + (g) => g.children[0].id === event.target.id + ); if (graphicIndex === -1) { - graphicIndex = graphicRef.current.findIndex((g) => g.children[1].id === event.target.id); + graphicIndex = graphicRef.current.findIndex( + (g) => g.children[1].id === event.target.id + ); } - const posX = (event.offsetX ?? 0) + (graphicRef.current[graphicIndex]?.dragDeltaInPixels ?? 0); + const posX = + (event.offsetX ?? 0) + + (graphicRef.current[graphicIndex]?.dragDeltaInPixels ?? 0); const timeInMs = calculateTimeStamp(posX, chartRef); if (isInSyncModeRef.current) { @@ -198,14 +241,21 @@ const useTrendCursorsEvents = ({ // we need this to avoid snapping the TC line to the center of the point of the mouse's X const dragStartEventHandler = (event: ElementEvent) => { - let graphicIndex = graphicRef.current.findIndex((g) => g.children[0].id === event.target.id); + let graphicIndex = graphicRef.current.findIndex( + (g) => g.children[0].id === event.target.id + ); if (graphicIndex === -1) { - graphicIndex = graphicRef.current.findIndex((g) => g.children[1].id === event.target.id); + graphicIndex = graphicRef.current.findIndex( + (g) => g.children[1].id === event.target.id + ); } const dragDeltaInPixels = - calculateXFromTimestamp(graphicRef.current[graphicIndex].timestampInMs, chartRef) - event.offsetX; + calculateXFromTimestamp( + graphicRef.current[graphicIndex].timestampInMs, + chartRef + ) - event.offsetX; graphicRef.current[graphicIndex] = { ...graphicRef.current[graphicIndex], @@ -218,7 +268,10 @@ const useTrendCursorsEvents = ({ const zoomHandler = () => { const newG = graphicRef.current.map((g) => { // updating the series line marker's y value - const { trendCursorsSeriesMakersInPixels, trendCursorsSeriesMakersValue } = calculateSeriesMakers( + const { + trendCursorsSeriesMakersInPixels, + trendCursorsSeriesMakersValue, + } = calculateSeriesMakers( seriesRef.current, g.timestampInMs, chartRef, @@ -227,7 +280,11 @@ const useTrendCursorsEvents = ({ ); g.yAxisMarkerValue = trendCursorsSeriesMakersValue; // update line height and markers - g.children = onResizeUpdateTrendCursorYValues(g.children, trendCursorsSeriesMakersInPixels, sizeRef.current); + g.children = onResizeUpdateTrendCursorYValues( + g.children, + trendCursorsSeriesMakersInPixels, + sizeRef.current + ); // updating x of the graphic g.x = calculateXFromTimestamp(g.timestampInMs, chartRef); return g; @@ -265,13 +322,28 @@ const useTrendCursorsEvents = ({ const copyTrendCursorData = (posX: number) => { const timestampOfClick = calculateTimeStamp(posX, chartRef); - const toBeCopiedGraphicIndex = calculateNearestTcIndex(graphicRef.current, timestampOfClick); + const toBeCopiedGraphicIndex = calculateNearestTcIndex( + graphicRef.current, + timestampOfClick + ); // using copy-to-clipboard library to copy in a Excel sheet paste-able format - copy(formatCopyData(graphicRef.current[toBeCopiedGraphicIndex], seriesRef.current), { format: 'text/plain' }); + copy( + formatCopyData( + graphicRef.current[toBeCopiedGraphicIndex], + seriesRef.current + ), + { format: 'text/plain' } + ); }; // this handles the user interaction via context menu - const onContextMenuClickHandler = ({ action, posX }: { action: Action; posX: number }) => { + const onContextMenuClickHandler = ({ + action, + posX, + }: { + action: Action; + posX: number; + }) => { switch (action) { case 'add': addNewTrendCursor({ posX: posX, ignoreHotKey: true }); diff --git a/packages/react-components/src/components/chart/trendCursor/resize/updateYValues.ts b/packages/react-components/src/components/chart/trendCursor/resize/updateYValues.ts index 05140646a..a1456df57 100644 --- a/packages/react-components/src/components/chart/trendCursor/resize/updateYValues.ts +++ b/packages/react-components/src/components/chart/trendCursor/resize/updateYValues.ts @@ -12,17 +12,22 @@ export const onResizeUpdateTrendCursorYValues = ( trendCursorsSeriesMakersInPixels: number[], size: SizeConfig ) => { - (elements[TREND_CURSOR_LINE_GRAPHIC_INDEX] ?? {}).children = elements[TREND_CURSOR_LINE_GRAPHIC_INDEX].children?.map( - (lineElement, index) => { - if (index === 0) { - (((lineElement ?? {}) as GraphicComponentZRPathOption).shape ?? {}).y2 = size.height - DEFAULT_MARGIN; - } else { - // refer to the creation of the line graphic the height is the size minus top and bottom white space - (((lineElement ?? {}) as GraphicComponentZRPathOption).shape ?? {}).height = - size.height - DEFAULT_MARGIN * 2 + 6; - } - return lineElement; + (elements[TREND_CURSOR_LINE_GRAPHIC_INDEX] ?? {}).children = elements[ + TREND_CURSOR_LINE_GRAPHIC_INDEX + ].children?.map((lineElement, index) => { + if (index === 0) { + (((lineElement ?? {}) as GraphicComponentZRPathOption).shape ?? {}).y2 = + size.height - DEFAULT_MARGIN; + } else { + // refer to the creation of the line graphic the height is the size minus top and bottom white space + ( + ((lineElement ?? {}) as GraphicComponentZRPathOption).shape ?? {} + ).height = size.height - DEFAULT_MARGIN * 2 + 6; } + return lineElement; + }); + return updateTrendCursorLineMarkers( + elements, + trendCursorsSeriesMakersInPixels ); - return updateTrendCursorLineMarkers(elements, trendCursorsSeriesMakersInPixels); }; diff --git a/packages/react-components/src/components/chart/trendCursor/resize/useHandleResize.ts b/packages/react-components/src/components/chart/trendCursor/resize/useHandleResize.ts index 6ec854bfa..649e611af 100644 --- a/packages/react-components/src/components/chart/trendCursor/resize/useHandleResize.ts +++ b/packages/react-components/src/components/chart/trendCursor/resize/useHandleResize.ts @@ -34,7 +34,10 @@ const useHandleResize = ({ const update = () => { const newG = graphicRef.current.map((g) => { // updating the series line marker's y value - const { trendCursorsSeriesMakersInPixels, trendCursorsSeriesMakersValue } = calculateSeriesMakers( + const { + trendCursorsSeriesMakersInPixels, + trendCursorsSeriesMakersValue, + } = calculateSeriesMakers( seriesRef.current, g.timestampInMs, chartRef, @@ -43,7 +46,11 @@ const useHandleResize = ({ ); g.yAxisMarkerValue = trendCursorsSeriesMakersValue; // update line height and markers - g.children = onResizeUpdateTrendCursorYValues(g.children, trendCursorsSeriesMakersInPixels, sizeRef.current); + g.children = onResizeUpdateTrendCursorYValues( + g.children, + trendCursorsSeriesMakersInPixels, + sizeRef.current + ); // updating x of the graphic g.x = calculateXFromTimestamp(g.timestampInMs, chartRef); return g; diff --git a/packages/react-components/src/components/chart/trendCursor/series/useHandleSeries.ts b/packages/react-components/src/components/chart/trendCursor/series/useHandleSeries.ts index 20cab496f..3adee3bb6 100644 --- a/packages/react-components/src/components/chart/trendCursor/series/useHandleSeries.ts +++ b/packages/react-components/src/components/chart/trendCursor/series/useHandleSeries.ts @@ -17,7 +17,9 @@ export const useHandleSeries = ({ const visualizationRef = useRef(visualization); const graphicRef = useRef(graphic); const significantDigitsRef = useRef(significantDigits); - const hiddenSeriesCount = series.filter((s) => (s as LineSeriesOption).lineStyle?.opacity === 0).length; + const hiddenSeriesCount = series.filter( + (s) => (s as LineSeriesOption).lineStyle?.opacity === 0 + ).length; useEffect(() => { graphicRef.current = graphic; @@ -29,7 +31,10 @@ export const useHandleSeries = ({ useEffect(() => { const update = () => { const newG = graphicRef.current.map((g) => { - const { trendCursorsSeriesMakersInPixels, trendCursorsSeriesMakersValue } = calculateSeriesMakers( + const { + trendCursorsSeriesMakersInPixels, + trendCursorsSeriesMakersValue, + } = calculateSeriesMakers( seriesRef.current, g.timestampInMs, chartRef, @@ -38,7 +43,11 @@ export const useHandleSeries = ({ ); g.yAxisMarkerValue = trendCursorsSeriesMakersValue; - g.children = upsertTrendCursorLineMarkers(g.children, trendCursorsSeriesMakersInPixels, seriesRef.current); + g.children = upsertTrendCursorLineMarkers( + g.children, + trendCursorsSeriesMakersInPixels, + seriesRef.current + ); return g; }); diff --git a/packages/react-components/src/components/chart/trendCursor/sync/calculateSyncDelta.ts b/packages/react-components/src/components/chart/trendCursor/sync/calculateSyncDelta.ts index ca3b914b7..764236ec1 100644 --- a/packages/react-components/src/components/chart/trendCursor/sync/calculateSyncDelta.ts +++ b/packages/react-components/src/components/chart/trendCursor/sync/calculateSyncDelta.ts @@ -3,10 +3,14 @@ import { SyncChanges } from '../types'; // This handles the sync between the Zustand sync state and local state // a user action can be one of the following // add , delete or drag. given only one can happen at a given time returning as soon as we find that an action has occurred -export const calculateSyncDelta = ({ syncedTrendCursors = {}, graphic }: SyncChanges) => { +export const calculateSyncDelta = ({ + syncedTrendCursors = {}, + graphic, +}: SyncChanges) => { const existingIds: string[] = graphic.map((g) => g.id as string); const toBeAdded = Object.keys(syncedTrendCursors).filter( - (syncedTrendCursorId) => existingIds.findIndex((id) => id === syncedTrendCursorId) === -1 + (syncedTrendCursorId) => + existingIds.findIndex((id) => id === syncedTrendCursorId) === -1 ); if (toBeAdded.length) @@ -24,8 +28,12 @@ export const calculateSyncDelta = ({ syncedTrendCursors = {}, graphic }: SyncCha newTimestamp: syncedTrendCursors[g.id as string]?.timestamp, })) .filter((g) => { - const syncedTrendCursorTimeStamp = syncedTrendCursors[g.id as string]?.timestamp; - return !!(syncedTrendCursorTimeStamp && syncedTrendCursorTimeStamp !== g.timestampInMs); + const syncedTrendCursorTimeStamp = + syncedTrendCursors[g.id as string]?.timestamp; + return !!( + syncedTrendCursorTimeStamp && + syncedTrendCursorTimeStamp !== g.timestampInMs + ); }); if (toBeUpdated.length) diff --git a/packages/react-components/src/components/chart/trendCursor/sync/useHandleSync.ts b/packages/react-components/src/components/chart/trendCursor/sync/useHandleSync.ts index 9f68d60da..4d245e592 100644 --- a/packages/react-components/src/components/chart/trendCursor/sync/useHandleSync.ts +++ b/packages/react-components/src/components/chart/trendCursor/sync/useHandleSync.ts @@ -18,7 +18,9 @@ const useHandleSync = ({ significantDigits, getColor, }: UseSyncProps) => { - const syncedTrendCursors = useDataStore((state) => state.trendCursorGroups[groupId ?? '']); + const syncedTrendCursors = useDataStore( + (state) => state.trendCursorGroups[groupId ?? ''] + ); if (chartRef && isInSyncMode && syncedTrendCursors) { const { toBeAdded, toBeDeleted, toBeUpdated } = calculateSyncDelta({ @@ -27,7 +29,10 @@ const useHandleSync = ({ }); // if no changes, we skip setting the state - if ((toBeAdded.length || toBeDeleted.length || toBeUpdated.length) && series.length > 0) { + if ( + (toBeAdded.length || toBeDeleted.length || toBeUpdated.length) && + series.length > 0 + ) { // add a new trend cursor if (toBeAdded.length) { toBeAdded.forEach((tcId) => { diff --git a/packages/react-components/src/components/chart/trendCursor/tests/getTrendCursor.spec.tsx b/packages/react-components/src/components/chart/trendCursor/tests/getTrendCursor.spec.tsx index 65af3931b..ca5dc3094 100644 --- a/packages/react-components/src/components/chart/trendCursor/tests/getTrendCursor.spec.tsx +++ b/packages/react-components/src/components/chart/trendCursor/tests/getTrendCursor.spec.tsx @@ -90,7 +90,8 @@ describe('Testing getNewTrendCursor file', () => { it('should update timestamp on drag', () => { const { result } = renderHook(() => useECharts('dark')); const newTrendCursor = mockGraphic; - const timestamp = Date.parse('2023-07-13T16:00:00.000Z') + 1000 * 60 * 60 * 2; // 1689271200000 + const timestamp = + Date.parse('2023-07-13T16:00:00.000Z') + 1000 * 60 * 60 * 2; // 1689271200000 onDragUpdateTrendCursor({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -130,7 +131,10 @@ describe('Testing getNewTrendCursor file', () => { describe('testing markers calculations, calculateTrendCursorsSeriesMakers', () => { it('calculateTrendCursorsSeriesMakers should populate the required values', () => { const { result } = renderHook(() => useECharts('dark')); - const { trendCursorsSeriesMakersInPixels, trendCursorsSeriesMakersValue } = calculateSeriesMakers( + const { + trendCursorsSeriesMakersInPixels, + trendCursorsSeriesMakersValue, + } = calculateSeriesMakers( mockSeries, 1689264600000, result.current.chartRef, diff --git a/packages/react-components/src/components/chart/trendCursor/tests/handleSyncTrendCursors.spec.ts b/packages/react-components/src/components/chart/trendCursor/tests/handleSyncTrendCursors.spec.ts index 615079a03..400f3a3ad 100644 --- a/packages/react-components/src/components/chart/trendCursor/tests/handleSyncTrendCursors.spec.ts +++ b/packages/react-components/src/components/chart/trendCursor/tests/handleSyncTrendCursors.spec.ts @@ -13,7 +13,8 @@ describe('calculateSyncDelta', () => { }); }); it('return toBeCreated when there is a new sync TC', () => { - const timestamp = Date.parse('2023-07-13T16:00:00.000Z') + 1000 * 60 * 60 * 2; // 1689271200000 + const timestamp = + Date.parse('2023-07-13T16:00:00.000Z') + 1000 * 60 * 60 * 2; // 1689271200000 const delta = calculateSyncDelta({ syncedTrendCursors: { 'trendCursor-1': { timestamp } }, graphic: [], @@ -26,7 +27,8 @@ describe('calculateSyncDelta', () => { }); it('return toBeUpdated the timestamps do not match', () => { - const timestamp = Date.parse('2023-07-13T16:00:00.000Z') + 1000 * 60 * 60 * 2; // 1689271200000 + const timestamp = + Date.parse('2023-07-13T16:00:00.000Z') + 1000 * 60 * 60 * 2; // 1689271200000 const delta = calculateSyncDelta({ syncedTrendCursors: { 'trendCursor-1': { timestamp } }, graphic: [ @@ -52,7 +54,8 @@ describe('calculateSyncDelta', () => { }); it('return toBeDeleted when sync TC does not have the existing graphic', () => { - const timestamp = Date.parse('2023-07-13T16:00:00.000Z') + 1000 * 60 * 60 * 2; // 1689271200000 + const timestamp = + Date.parse('2023-07-13T16:00:00.000Z') + 1000 * 60 * 60 * 2; // 1689271200000 const delta = calculateSyncDelta({ syncedTrendCursors: {}, graphic: [ diff --git a/packages/react-components/src/components/chart/trendCursor/tests/useTrendCursorsEvents.spec.tsx b/packages/react-components/src/components/chart/trendCursor/tests/useTrendCursorsEvents.spec.tsx index 1ac6db261..bb0e62ef9 100644 --- a/packages/react-components/src/components/chart/trendCursor/tests/useTrendCursorsEvents.spec.tsx +++ b/packages/react-components/src/components/chart/trendCursor/tests/useTrendCursorsEvents.spec.tsx @@ -11,7 +11,14 @@ import { Colorizer } from '@iot-app-kit/core-util'; describe('useTrendCursorsEvents', () => { const { result } = renderHook(() => useECharts('dark')); - render(
    ); + render( +
    + ); it('when there are no user event, set state should not be called', () => { const mockSetGraphic = jest.fn(); @@ -50,7 +57,10 @@ describe('useTrendCursorsEvents', () => { }) ); - hook.result.current.onContextMenuClickHandler({ action: 'delete', posX: 100 }); + hook.result.current.onContextMenuClickHandler({ + action: 'delete', + posX: 100, + }); expect(mockSetGraphic).toBeCalled(); }); }); diff --git a/packages/react-components/src/components/chart/trendCursor/tests/useVisualizedDataStreams.spec.ts b/packages/react-components/src/components/chart/trendCursor/tests/useVisualizedDataStreams.spec.ts index 3af5617c3..d8f44bd3f 100644 --- a/packages/react-components/src/components/chart/trendCursor/tests/useVisualizedDataStreams.spec.ts +++ b/packages/react-components/src/components/chart/trendCursor/tests/useVisualizedDataStreams.spec.ts @@ -14,7 +14,12 @@ it('converts empty query to empty data stream', async () => { it('convert query to visualized data stream', async () => { const VIEWPORT = { duration: '5m' }; - const DATA_STREAM: DataStream = { id: 'abc-1', data: [], resolution: 0, name: 'my-name' }; + const DATA_STREAM: DataStream = { + id: 'abc-1', + data: [], + resolution: 0, + name: 'my-name', + }; const queries = mockTimeSeriesDataQuery([ { dataStreams: [DATA_STREAM], diff --git a/packages/react-components/src/components/chart/trendCursor/useTrendCursors.ts b/packages/react-components/src/components/chart/trendCursor/useTrendCursors.ts index d10b94c20..4c1360b35 100644 --- a/packages/react-components/src/components/chart/trendCursor/useTrendCursors.ts +++ b/packages/react-components/src/components/chart/trendCursor/useTrendCursors.ts @@ -14,7 +14,13 @@ import { useHandleSeries } from './series/useHandleSeries'; import { useHandleChartOptions } from './chartOptions/handleOptions'; import { KeyMap } from 'react-hotkeys'; -const TRENDCURSOR_COLOR_PALETTE = ['#7492e7', '#da7596', '#2ea597', '#a783e1', '#e07941']; +const TRENDCURSOR_COLOR_PALETTE = [ + '#7492e7', + '#da7596', + '#2ea597', + '#a783e1', + '#e07941', +]; const useTrendCursors = ({ chartRef, @@ -38,12 +44,16 @@ const useTrendCursors = ({ console.log(`useTrendCursors for chart id : ${chartId}`); } - const isInSyncMode = useDataStore((state) => (groupId ? !!state.trendCursorGroups[groupId] : false)); + const isInSyncMode = useDataStore((state) => + groupId ? !!state.trendCursorGroups[groupId] : false + ); const [graphic, setGraphic] = useState(initialGraphic ?? []); const [isInCursorAddMode, setIsInCursorAddMode] = useState(false); const colorer = Colorizer(TRENDCURSOR_COLOR_PALETTE); - const existingColors = graphic.map((g) => g.color).filter((color): color is string => color != null); + const existingColors = graphic + .map((g) => g.color) + .filter((color): color is string => color != null); colorer.remove(existingColors); // hook for handling all user events @@ -63,7 +73,15 @@ const useTrendCursors = ({ }); // for handling the resize of chart - useHandleResize({ series, size, graphic, setGraphic, chartRef, visualization, significantDigits }); + useHandleResize({ + series, + size, + graphic, + setGraphic, + chartRef, + visualization, + significantDigits, + }); // handling the trend cursor sync mode useHandleSync({ @@ -79,13 +97,44 @@ const useTrendCursors = ({ getColor: colorer.next, }); - useHandleViewport({ graphic, setGraphic, chartRef, series, visualization, significantDigits, viewportInMs, size }); + useHandleViewport({ + graphic, + setGraphic, + chartRef, + series, + visualization, + significantDigits, + viewportInMs, + size, + }); - useHandleYMinMax({ graphic, setGraphic, chartRef, series, visualization, significantDigits, yAxisOptions }); + useHandleYMinMax({ + graphic, + setGraphic, + chartRef, + series, + visualization, + significantDigits, + yAxisOptions, + }); - useHandleSeries({ graphic, setGraphic, chartRef, series, visualization, significantDigits }); + useHandleSeries({ + graphic, + setGraphic, + chartRef, + series, + visualization, + significantDigits, + }); - useHandleChartOptions({ graphic, setGraphic, chartRef, series, visualization, significantDigits }); + useHandleChartOptions({ + graphic, + setGraphic, + chartRef, + series, + visualization, + significantDigits, + }); const trendCursorKeyMap: KeyMap = { commandDown: { sequence: 't', action: 'keydown' }, @@ -97,9 +146,16 @@ const useTrendCursors = ({ commandUp: () => setIsInCursorAddMode(false), }; - const orderedTrendCursors = graphic.sort((a, b) => a.timestampInMs - b.timestampInMs); + const orderedTrendCursors = graphic.sort( + (a, b) => a.timestampInMs - b.timestampInMs + ); - return { onContextMenuClickHandler, trendCursorKeyMap, trendCursorHandlers, trendCursors: orderedTrendCursors }; + return { + onContextMenuClickHandler, + trendCursorKeyMap, + trendCursorHandlers, + trendCursors: orderedTrendCursors, + }; }; export default useTrendCursors; diff --git a/packages/react-components/src/components/chart/trendCursor/viewport/useHandleViewport.ts b/packages/react-components/src/components/chart/trendCursor/viewport/useHandleViewport.ts index 9d207684e..d323c5c68 100644 --- a/packages/react-components/src/components/chart/trendCursor/viewport/useHandleViewport.ts +++ b/packages/react-components/src/components/chart/trendCursor/viewport/useHandleViewport.ts @@ -41,7 +41,10 @@ export const useHandleViewport = ({ } else { g.ignore = false; - const { trendCursorsSeriesMakersValue, trendCursorsSeriesMakersInPixels } = calculateSeriesMakers( + const { + trendCursorsSeriesMakersValue, + trendCursorsSeriesMakersInPixels, + } = calculateSeriesMakers( series, g.timestampInMs, chartRef, @@ -49,7 +52,10 @@ export const useHandleViewport = ({ significantDigits ); g.yAxisMarkerValue = trendCursorsSeriesMakersValue; - g.children = updateTrendCursorLineMarkers(g.children, trendCursorsSeriesMakersInPixels); + g.children = updateTrendCursorLineMarkers( + g.children, + trendCursorsSeriesMakersInPixels + ); // update the X in any case g.x = x; diff --git a/packages/react-components/src/components/chart/trendCursor/yMinMax/useHandleYMinMax.ts b/packages/react-components/src/components/chart/trendCursor/yMinMax/useHandleYMinMax.ts index 543a7360f..88593bae8 100644 --- a/packages/react-components/src/components/chart/trendCursor/yMinMax/useHandleYMinMax.ts +++ b/packages/react-components/src/components/chart/trendCursor/yMinMax/useHandleYMinMax.ts @@ -34,7 +34,10 @@ export const useHandleYMinMax = ({ useEffect(() => { const update = () => { const newG = graphicRef.current.map((g) => { - const { trendCursorsSeriesMakersInPixels, trendCursorsSeriesMakersValue } = calculateSeriesMakers( + const { + trendCursorsSeriesMakersInPixels, + trendCursorsSeriesMakersValue, + } = calculateSeriesMakers( seriesRef.current, g.timestampInMs, chartRef, @@ -42,7 +45,10 @@ export const useHandleYMinMax = ({ significantDigitsRef.current ); g.yAxisMarkerValue = trendCursorsSeriesMakersValue; - g.children = updateTrendCursorLineMarkers(g.children, trendCursorsSeriesMakersInPixels); + g.children = updateTrendCursorLineMarkers( + g.children, + trendCursorsSeriesMakersInPixels + ); return g; }); diff --git a/packages/react-components/src/components/chart/types.ts b/packages/react-components/src/components/chart/types.ts index 4d785eb8d..d5f20e844 100644 --- a/packages/react-components/src/components/chart/types.ts +++ b/packages/react-components/src/components/chart/types.ts @@ -1,4 +1,11 @@ -import { DataStream, Primitive, Threshold, ThresholdSettings, TimeSeriesDataQuery, Viewport } from '@iot-app-kit/core'; +import { + DataStream, + Primitive, + Threshold, + ThresholdSettings, + TimeSeriesDataQuery, + Viewport, +} from '@iot-app-kit/core'; import { OptionId } from 'echarts/types/src/util/types'; import { InternalGraphicComponentGroupOption } from './trendCursor/types'; @@ -29,7 +36,13 @@ export type SimpleFontSettings = { export type SizeConfig = { width: number; height: number }; -export type Visualization = 'line' | 'bar' | 'scatter' | 'step-start' | 'step-middle' | 'step-end'; +export type Visualization = + | 'line' + | 'bar' + | 'scatter' + | 'step-start' + | 'step-middle' + | 'step-end'; export type ChartStyleSettingsOptions = { /** diff --git a/packages/react-components/src/components/chart/utils/getStyles.ts b/packages/react-components/src/components/chart/utils/getStyles.ts index 9e1aeee7d..469379285 100644 --- a/packages/react-components/src/components/chart/utils/getStyles.ts +++ b/packages/react-components/src/components/chart/utils/getStyles.ts @@ -5,9 +5,13 @@ export type Emphasis = 'none' | 'emphasize' | 'de-emphasize'; export const getStyles = ( refId?: string, styleSettings?: ChartOptions['styleSettings'] -): ChartStyleSettingsOptions | undefined => (refId && styleSettings ? styleSettings[refId] : undefined); +): ChartStyleSettingsOptions | undefined => + refId && styleSettings ? styleSettings[refId] : undefined; -type OptionalChartStyleSettingsOptions = Pick; +type OptionalChartStyleSettingsOptions = Pick< + ChartStyleSettingsOptions, + 'symbolColor' | 'yAxis' | 'color' +>; export type ChartStyleSettingsWithDefaults = Omit< Required, keyof OptionalChartStyleSettingsOptions diff --git a/packages/react-components/src/components/dial/dial.tsx b/packages/react-components/src/components/dial/dial.tsx index 8356b1f92..c42ce2453 100644 --- a/packages/react-components/src/components/dial/dial.tsx +++ b/packages/react-components/src/components/dial/dial.tsx @@ -45,14 +45,27 @@ export const Dial = ({ // if using echarts then echarts gesture overrides passed in viewport // else explicitly passed in viewport overrides viewport group const utilizedViewport = - (lastUpdatedBy === ECHARTS_GESTURE ? viewport : passedInViewport || viewport) ?? DEFAULT_VIEWPORT; + (lastUpdatedBy === ECHARTS_GESTURE + ? viewport + : passedInViewport || viewport) ?? DEFAULT_VIEWPORT; - const { propertyPoint, alarmPoint, alarmThreshold, propertyThreshold, alarmStream, propertyStream } = - widgetPropertiesFromInputs({ dataStreams, thresholds, viewport: utilizedViewport }); + const { + propertyPoint, + alarmPoint, + alarmThreshold, + propertyThreshold, + alarmStream, + propertyStream, + } = widgetPropertiesFromInputs({ + dataStreams, + thresholds, + viewport: utilizedViewport, + }); const name = propertyStream?.name || alarmStream?.name; const unit = propertyStream?.unit || alarmStream?.unit; - const color = alarmThreshold?.color || propertyThreshold?.color || propertyStream?.color; + const color = + alarmThreshold?.color || propertyThreshold?.color || propertyStream?.color; return ( { it('renders unit when showUnit is true and provided a property point', () => { const someUnit = 'some-Unit'; - render(); + render( + + ); expect(screen.queryByText(someUnit)).not.toBeNull(); }); it('renders unit when showUnit is true and provided a alarm point', () => { const someUnit = 'some-Unit'; - render(); + render( + + ); expect(screen.queryByText(someUnit)).not.toBeNull(); }); @@ -45,7 +57,13 @@ describe('unit', () => { it('does not render unit when showUnit is false', () => { const someUnit = 'some-Unit'; - render(); + render( + + ); expect(screen.queryByText(someUnit)).toBeNull(); }); @@ -54,13 +72,23 @@ describe('unit', () => { describe('property value', () => { it('renders property points y value', () => { const Y_VALUE = 123445; - render(); + render( + + ); expect(screen.queryByText(Y_VALUE)).not.toBeNull(); }); it('renders alarm points y value', () => { const Y_VALUE = 123445; - render(); + render( + + ); expect(screen.queryByText(Y_VALUE)).not.toBeNull(); }); @@ -119,7 +147,14 @@ describe('loading', () => { it('does not render alarm or property data point while isLoading is true', () => { const point = { x: 12341, y: 123213 }; const alarmPoint = { x: 12341, y: 'warning' }; - render(); + render( + + ); expect(screen.queryByText(point.y)).toBeNull(); expect(screen.queryByText(alarmPoint.y)).toBeNull(); diff --git a/packages/react-components/src/components/dial/dialBase.tsx b/packages/react-components/src/components/dial/dialBase.tsx index f79d0e55b..34fbf5896 100644 --- a/packages/react-components/src/components/dial/dialBase.tsx +++ b/packages/react-components/src/components/dial/dialBase.tsx @@ -24,9 +24,12 @@ export const DialBase: React.FC = ({ // Primary point to display. Dial Emphasizes the property point over the alarm point. const point = propertyPoint || alarmPoint; const value = point?.y; - const label = (propertyPoint != null && alarmPoint != null && alarmPoint.y) || undefined; + const label = + (propertyPoint != null && alarmPoint != null && alarmPoint.y) || undefined; const percent = - yMin != null && yMax != null && typeof value === 'number' ? (value - yMin) / (yMax / yMin) : undefined; + yMin != null && yMax != null && typeof value === 'number' + ? (value - yMin) / (yMax / yMin) + : undefined; return (
    {showName && name} diff --git a/packages/react-components/src/components/dial/dialSvg.tsx b/packages/react-components/src/components/dial/dialSvg.tsx index cb433bfb5..d3736b73f 100644 --- a/packages/react-components/src/components/dial/dialSvg.tsx +++ b/packages/react-components/src/components/dial/dialSvg.tsx @@ -25,17 +25,31 @@ export const DialSvg = ({ unit?: string; settings: Required; }) => { - const { defaultRing, colorRing } = useArcs({ percent, lineThickness: settings.dialThickness, radius: RADIUS }); + const { defaultRing, colorRing } = useArcs({ + percent, + lineThickness: settings.dialThickness, + radius: RADIUS, + }); const displayLabel = label != null && label != '' && !isLoading; const displayValue = !isLoading && value != null && value !== ''; - const valueLength = (value != null ? value.length : 0) + (unit != null ? unit.length : 0); + const valueLength = + (value != null ? value.length : 0) + (unit != null ? unit.length : 0); const valueFontSize = value != null - ? Math.min(settings.fontSize, Math.floor((2 * (RADIUS - settings.dialThickness)) / valueLength) * 1.6) + ? Math.min( + settings.fontSize, + Math.floor((2 * (RADIUS - settings.dialThickness)) / valueLength) * + 1.6 + ) : settings.fontSize; return ( - + - + {displayValue && ( - + {value} {unit && ( - + {unit} )} @@ -58,7 +86,14 @@ export const DialSvg = ({ )} {isLoading && ( - + Loading )} @@ -75,7 +110,14 @@ export const DialSvg = ({ )} {displayLabel && ( - + {label} )} diff --git a/packages/react-components/src/components/dial/useArcs.ts b/packages/react-components/src/components/dial/useArcs.ts index b94bf80f3..f13d6a607 100644 --- a/packages/react-components/src/components/dial/useArcs.ts +++ b/packages/react-components/src/components/dial/useArcs.ts @@ -14,9 +14,15 @@ const getArcs = (percent: number) => { const endAngle2 = endAngle1 + startAngle2; // Arc representing the value (i.e. if percent is 25%, this would be the arc going a 25% of the circle - const valueArc = arc().cornerRadius(CORNER_RADIUS).startAngle(0).endAngle(endAngle1); + const valueArc = arc() + .cornerRadius(CORNER_RADIUS) + .startAngle(0) + .endAngle(endAngle1); // The remainder of the arc not representing the value - const remainingArc = arc().cornerRadius(CORNER_RADIUS).startAngle(endAngle2).endAngle(endAngle1); + const remainingArc = arc() + .cornerRadius(CORNER_RADIUS) + .startAngle(endAngle2) + .endAngle(endAngle1); return { valueArc, diff --git a/packages/react-components/src/components/iot-app-kit-config/index.tsx b/packages/react-components/src/components/iot-app-kit-config/index.tsx index 0647a1a03..87fda3868 100644 --- a/packages/react-components/src/components/iot-app-kit-config/index.tsx +++ b/packages/react-components/src/components/iot-app-kit-config/index.tsx @@ -7,14 +7,26 @@ export interface IAppKitConfig { featureFlagConfig: FeatureFlagConfig; } -export const AppKitContext = createContext(DEFAULT_APP_KIT_CONFIG); +export const AppKitContext = createContext( + DEFAULT_APP_KIT_CONFIG +); export type AppKitConfigProps = { children: ReactNode; customFeatureConfig?: FeatureFlagConfig; }; -export const AppKitConfig: React.FC = ({ children, customFeatureConfig }) => { - const value = { featureFlagConfig: { ...DEFAULT_APP_KIT_CONFIG.featureFlagConfig, ...customFeatureConfig } }; - return {children}; +export const AppKitConfig: React.FC = ({ + children, + customFeatureConfig, +}) => { + const value = { + featureFlagConfig: { + ...DEFAULT_APP_KIT_CONFIG.featureFlagConfig, + ...customFeatureConfig, + }, + }; + return ( + {children} + ); }; diff --git a/packages/react-components/src/components/knowledge-graph/KnowledgeGraphPanel.tsx b/packages/react-components/src/components/knowledge-graph/KnowledgeGraphPanel.tsx index cba66140a..e99efd49c 100644 --- a/packages/react-components/src/components/knowledge-graph/KnowledgeGraphPanel.tsx +++ b/packages/react-components/src/components/knowledge-graph/KnowledgeGraphPanel.tsx @@ -1,9 +1,21 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable react-hooks/exhaustive-deps */ -import React, { HTMLAttributes, useEffect, useCallback, useMemo, useState, useRef } from 'react'; +import React, { + HTMLAttributes, + useEffect, + useCallback, + useMemo, + useState, + useRef, +} from 'react'; import { IntlProvider, FormattedMessage } from 'react-intl'; import type { Core, EventObjectNode, EventObjectEdge } from 'cytoscape'; -import { Button, Input, Grid, SpaceBetween } from '@cloudscape-design/components'; +import { + Button, + Input, + Grid, + SpaceBetween, +} from '@cloudscape-design/components'; import { TwinMakerKGQueryDataModule } from '@iot-app-kit/source-iottwinmaker'; import GraphView from './graph/graph-view'; import Toolbar from './graph/graph-toolbar'; @@ -18,7 +30,8 @@ import { NodeData, EdgeData } from './graph/types'; import { IQueryData } from './interfaces'; import { getElementsDefinition } from './utils'; -export interface KnowledgeGraphInterface extends HTMLAttributes { +export interface KnowledgeGraphInterface + extends HTMLAttributes { kgDataSource: TwinMakerKGQueryDataModule; onEntitySelected?: (e: NodeData) => void; onRelationshipSelected?: (e: EdgeData) => void; @@ -82,8 +95,13 @@ export const KnowledgeGraphContainer: React.FC = ({ const cy = useRef(null); const containerRef = useRef(null); const stylesheet = useStylesheet(containerRef); - const { selectedGraphNodeEntityId, setSelectedGraphNodeEntityId, setQueryResult, queryResult, clearGraphResults } = - useKnowledgeGraphState(); + const { + selectedGraphNodeEntityId, + setSelectedGraphNodeEntityId, + setQueryResult, + queryResult, + clearGraphResults, + } = useKnowledgeGraphState(); const [searchTerm, setSearchTerm] = useState(''); const { nodeData, edgeData } = ResponseParser.parse( queryResult ? queryResult['rows'] : null, @@ -101,14 +119,20 @@ export const KnowledgeGraphContainer: React.FC = ({ const zoomIn = useCallback(() => { cy.current?.zoom({ level: cy.current.zoom() + ZOOM_INTERVAL, - renderedPosition: { x: cy.current.width() / 2, y: cy.current.height() / 2 }, + renderedPosition: { + x: cy.current.width() / 2, + y: cy.current.height() / 2, + }, }); }, []); const zoomOut = useCallback(() => { cy.current?.zoom({ level: cy.current.zoom() - ZOOM_INTERVAL, - renderedPosition: { x: cy.current.width() / 2, y: cy.current.height() / 2 }, + renderedPosition: { + x: cy.current.width() / 2, + y: cy.current.height() / 2, + }, }); }, []); @@ -124,7 +148,10 @@ export const KnowledgeGraphContainer: React.FC = ({ onEntitySelected(data); } setSelectedGraphNodeEntityId(data.id); - console.log('selectedGraphNodeEntityId Inside = ', selectedGraphNodeEntityId); + console.log( + 'selectedGraphNodeEntityId Inside = ', + selectedGraphNodeEntityId + ); }, [onEntitySelected, setSelectedGraphNodeEntityId] ); @@ -171,7 +198,13 @@ export const KnowledgeGraphContainer: React.FC = ({ cy.current?.off('unselect', 'node'); cy.current?.off('unselect', 'edge'); }; - }, [cy.current, clickEntityHandler, clickRelationshipHandler, unClickEntityHandler, unClickRelationshipHandler]); + }, [ + cy.current, + clickEntityHandler, + clickRelationshipHandler, + unClickEntityHandler, + unClickRelationshipHandler, + ]); useEffect(() => { if (onGraphResultChange && nodeData) { @@ -201,7 +234,9 @@ export const KnowledgeGraphContainer: React.FC = ({ const onClearClicked = useCallback(() => { if (onClearGraph && nodeData) { - edgeData ? onClearGraph([...nodeData.values()], [...edgeData.values()]) : onClearGraph([...nodeData.values()]); + edgeData + ? onClearGraph([...nodeData.values()], [...edgeData.values()]) + : onClearGraph([...nodeData.values()]); } clearGraphResults(true); }, [clearGraphResults, onClearGraph, nodeData, edgeData]); @@ -234,13 +269,26 @@ export const KnowledgeGraphContainer: React.FC = ({
    -
    +
    0 ? breadthFirstlayout : nodeData.size > 1 ? gridlayout : presetlayout} + elements={getElementsDefinition( + [...nodeData.values()], + [...edgeData.values()] + )} + layout={ + edgeData.size > 0 + ? breadthFirstlayout + : nodeData.size > 1 + ? gridlayout + : presetlayout + } /> -
    } onError={() => {}}> - ; - - ); -}); +const GraphView = forwardRef( + ({ ...props }, ref) => { + const cy = useRef(); + // This fixes the type issue with the Cytoscape react library that doesn't actually except "refs". So we handle that here, + // and make it follow react conventions instead of doing it's own thing. + // istanbul ignore next (this is tested through other tests, but not the negative case) + const setRef = (core: Core) => { + (ref as MutableRefObject).current = core; + cy.current = core; + }; + return ( + Error
    } onError={() => {}}> + ; + + ); + } +); export default GraphView; diff --git a/packages/react-components/src/components/knowledge-graph/graph/lib/types.ts b/packages/react-components/src/components/knowledge-graph/graph/lib/types.ts index b3d3ac4be..6d8f2da3c 100644 --- a/packages/react-components/src/components/knowledge-graph/graph/lib/types.ts +++ b/packages/react-components/src/components/knowledge-graph/graph/lib/types.ts @@ -5,4 +5,11 @@ export type EntityData = { entityName?: string; }; -export type Health = 'ok' | 'critical' | 'high' | 'medium' | 'low' | 'offline' | 'unknown'; +export type Health = + | 'ok' + | 'critical' + | 'high' + | 'medium' + | 'low' + | 'offline' + | 'unknown'; diff --git a/packages/react-components/src/components/knowledge-graph/graph/types.ts b/packages/react-components/src/components/knowledge-graph/graph/types.ts index 11eaf4213..c54d713bd 100644 --- a/packages/react-components/src/components/knowledge-graph/graph/types.ts +++ b/packages/react-components/src/components/knowledge-graph/graph/types.ts @@ -14,7 +14,15 @@ import type { import { INodeResults } from '../interfaces/kgDataSourceInterfaces'; import type { ValueOf } from 'type-fest'; -export type layoutTypes = 'preset' | 'random' | 'grid' | 'circle' | 'concentric' | 'breadthfirst' | 'cose' | 'cise'; +export type layoutTypes = + | 'preset' + | 'random' + | 'grid' + | 'circle' + | 'concentric' + | 'breadthfirst' + | 'cose' + | 'cise'; export type EdgeStyleProps = { color: string; @@ -95,4 +103,8 @@ export type EventDetail = { eventName: EventName; data?: NodeData | EdgeData; }; -export type EventName = CollectionEventName | GraphEventName | UserInputDeviceEventName | UserInputDeviceEventNameExt; +export type EventName = + | CollectionEventName + | GraphEventName + | UserInputDeviceEventName + | UserInputDeviceEventNameExt; diff --git a/packages/react-components/src/components/knowledge-graph/responseParser.tsx b/packages/react-components/src/components/knowledge-graph/responseParser.tsx index 25cad9e66..9f626a070 100644 --- a/packages/react-components/src/components/knowledge-graph/responseParser.tsx +++ b/packages/react-components/src/components/knowledge-graph/responseParser.tsx @@ -1,4 +1,7 @@ -import { INodeResults, IRelationResults } from './interfaces/kgDataSourceInterfaces'; +import { + INodeResults, + IRelationResults, +} from './interfaces/kgDataSourceInterfaces'; import { ExecuteQueryCommandOutput } from '@aws-sdk/client-iottwinmaker'; import { NodeData, EdgeData } from './graph/types'; @@ -28,13 +31,20 @@ function parseEdge(item: IRelationResults, edgeData: Map) { export class ResponseParser { static parse( queryRows: ExecuteQueryCommandOutput['rows'] | null | undefined, - queryColumnDescriptions: ExecuteQueryCommandOutput['columnDescriptions'] | null | undefined + queryColumnDescriptions: + | ExecuteQueryCommandOutput['columnDescriptions'] + | null + | undefined ) { const nodeData = new Map(); const edgeData = new Map(); if (queryRows && queryColumnDescriptions) { for (const row of queryRows) { - for (let columnNumber = 0; columnNumber < queryColumnDescriptions.length; columnNumber++) { + for ( + let columnNumber = 0; + columnNumber < queryColumnDescriptions.length; + columnNumber++ + ) { const itemType = queryColumnDescriptions[columnNumber].type; const rowData = row.rowData; const item = rowData![columnNumber]; diff --git a/packages/react-components/src/components/knowledge-graph/tests/KnowledgeGraphPanel.spec.tsx b/packages/react-components/src/components/knowledge-graph/tests/KnowledgeGraphPanel.spec.tsx index 977c6ec35..138c6d184 100644 --- a/packages/react-components/src/components/knowledge-graph/tests/KnowledgeGraphPanel.spec.tsx +++ b/packages/react-components/src/components/knowledge-graph/tests/KnowledgeGraphPanel.spec.tsx @@ -5,9 +5,11 @@ import { renderWithProviders } from './utils/test-utils'; import { kgDataSource } from './__mocks__/dataSource'; import { KnowledgeGraph, ZOOM_INTERVAL } from '../KnowledgeGraphPanel'; -jest.mock('@awsui/components-react/container', () => (props: HTMLAttributes) => ( -
    -)); +jest.mock( + '@awsui/components-react/container', + () => (props: HTMLAttributes) => +
    +); jest.mock( 'react-cytoscapejs', () => @@ -15,7 +17,10 @@ jest.mock( elements, cy: _cy, // We don't want to propagate this ref arg ...props - }: HTMLAttributes & { elements: ElementDefinition[]; cy: (() => void) | undefined }) => + }: HTMLAttributes & { + elements: ElementDefinition[]; + cy: (() => void) | undefined; + }) => (
    {JSON.stringify(elements)} @@ -29,7 +34,9 @@ jest.mock('react', () => ({ describe('KnowledgeGraph', () => { it('should render correctly', () => { - const { container } = renderWithProviders(); + const { container } = renderWithProviders( + + ); expect(container).toMatchSnapshot(); }); it('should fit to screen on fit button clicked', async () => { @@ -48,7 +55,9 @@ describe('KnowledgeGraph', () => { useRefMock.mockReturnValueOnce(fakeCy); - const { findByTestId } = renderWithProviders(); + const { findByTestId } = renderWithProviders( + + ); const sut = await findByTestId('fit-button'); fireEvent.click(sut); @@ -72,7 +81,9 @@ describe('KnowledgeGraph', () => { useRefMock.mockReturnValueOnce(fakeCy); - const { findByTestId } = renderWithProviders(); + const { findByTestId } = renderWithProviders( + + ); const sut = await findByTestId('center-button'); fireEvent.click(sut); @@ -98,12 +109,17 @@ describe('KnowledgeGraph', () => { useRefMock.mockReturnValueOnce(fakeCy); - const { findByTestId } = renderWithProviders(); + const { findByTestId } = renderWithProviders( + + ); const sut = await findByTestId('zoom-in-button'); fireEvent.click(sut); - expect(fakeCy.current.zoom).toBeCalledWith({ level: 0.1 + ZOOM_INTERVAL, renderedPosition: { x: 250, y: 250 } }); + expect(fakeCy.current.zoom).toBeCalledWith({ + level: 0.1 + ZOOM_INTERVAL, + renderedPosition: { x: 250, y: 250 }, + }); }); it('should zoom out when zoom out button clicked', async () => { @@ -124,16 +140,24 @@ describe('KnowledgeGraph', () => { useRefMock.mockReturnValueOnce(fakeCy); - const { findByTestId } = renderWithProviders(); + const { findByTestId } = renderWithProviders( + + ); const sut = await findByTestId('zoom-out-button'); fireEvent.click(sut); - expect(fakeCy.current.zoom).toBeCalledWith({ level: 0.1 - ZOOM_INTERVAL, renderedPosition: { x: 250, y: 250 } }); + expect(fakeCy.current.zoom).toBeCalledWith({ + level: 0.1 - ZOOM_INTERVAL, + renderedPosition: { x: 250, y: 250 }, + }); }); it('should find Search, Explore and Clear button', async () => { - const { queryByText } = renderWithProviders(, {}); + const { queryByText } = renderWithProviders( + , + {} + ); const searchButton = queryByText('Search'); expect(searchButton).toBeVisible; const exploreButton = queryByText('explore-button'); diff --git a/packages/react-components/src/components/knowledge-graph/tests/utils/test-utils.tsx b/packages/react-components/src/components/knowledge-graph/tests/utils/test-utils.tsx index d07dba1ed..f17ae4b1c 100644 --- a/packages/react-components/src/components/knowledge-graph/tests/utils/test-utils.tsx +++ b/packages/react-components/src/components/knowledge-graph/tests/utils/test-utils.tsx @@ -21,5 +21,7 @@ const AllTheProviders = ({ children }: { children: React.ReactNode }) => { ); }; -export const renderWithProviders = (ui: ReactElement, options?: Omit) => - render(ui, { wrapper: AllTheProviders, ...options }); +export const renderWithProviders = ( + ui: ReactElement, + options?: Omit +) => render(ui, { wrapper: AllTheProviders, ...options }); diff --git a/packages/react-components/src/components/knowledge-graph/utils/cytoscapeParsingUtils.ts b/packages/react-components/src/components/knowledge-graph/utils/cytoscapeParsingUtils.ts index ae79aeafb..45b87f3b3 100644 --- a/packages/react-components/src/components/knowledge-graph/utils/cytoscapeParsingUtils.ts +++ b/packages/react-components/src/components/knowledge-graph/utils/cytoscapeParsingUtils.ts @@ -1,7 +1,10 @@ import { ElementDefinition, NodeDefinition, EdgeDefinition } from 'cytoscape'; import { NodeData, EdgeData } from '../graph/types'; -export function getElementsDefinition(nodeData: NodeData[], edgeData?: EdgeData[]): ElementDefinition[] { +export function getElementsDefinition( + nodeData: NodeData[], + edgeData?: EdgeData[] +): ElementDefinition[] { const elements: ElementDefinition[] = [ ...nodeData.map((data) => ({ data: { ...data }, diff --git a/packages/react-components/src/components/kpi/kpi.tsx b/packages/react-components/src/components/kpi/kpi.tsx index 892152394..a23640cdf 100644 --- a/packages/react-components/src/components/kpi/kpi.tsx +++ b/packages/react-components/src/components/kpi/kpi.tsx @@ -4,7 +4,12 @@ import { useTimeSeriesData } from '../../hooks/useTimeSeriesData'; import { useViewport } from '../../hooks/useViewport'; import { widgetPropertiesFromInputs } from '../../common/widgetPropertiesFromInputs'; import { DEFAULT_VIEWPORT } from '../../common/constants'; -import type { Threshold, StyleSettingsMap, Viewport, TimeSeriesDataQuery } from '@iot-app-kit/core'; +import type { + Threshold, + StyleSettingsMap, + Viewport, + TimeSeriesDataQuery, +} from '@iot-app-kit/core'; import type { KPISettings } from './types'; export const KPI = ({ @@ -52,8 +57,10 @@ export const KPI = ({ const name = propertyStream?.name || alarmStream?.name; const unit = propertyStream?.unit || alarmStream?.unit; - const color = alarmThreshold?.color || propertyThreshold?.color || settings?.color; - const isLoading = alarmStream?.isLoading || propertyStream?.isLoading || false; + const color = + alarmThreshold?.color || propertyThreshold?.color || settings?.color; + const isLoading = + alarmStream?.isLoading || propertyStream?.isLoading || false; const error = alarmStream?.error || propertyStream?.error; return ( diff --git a/packages/react-components/src/components/kpi/kpiBase.spec.tsx b/packages/react-components/src/components/kpi/kpiBase.spec.tsx index 836e7f086..d41e3dc0e 100644 --- a/packages/react-components/src/components/kpi/kpiBase.spec.tsx +++ b/packages/react-components/src/components/kpi/kpiBase.spec.tsx @@ -25,14 +25,26 @@ describe('unit', () => { it('renders unit when showUnit is true and provided a property point', () => { const someUnit = 'some-Unit'; - render(); + render( + + ); expect(screen.queryByText(someUnit)).not.toBeNull(); }); it('renders unit when showUnit is true and provided a alarm point', () => { const someUnit = 'some-Unit'; - render(); + render( + + ); expect(screen.queryByText(someUnit)).not.toBeNull(); }); @@ -46,7 +58,13 @@ describe('unit', () => { it('does not render unit when showUnit is false', () => { const someUnit = 'some-Unit'; - render(); + render( + + ); expect(screen.queryByText(someUnit)).toBeNull(); }); @@ -55,13 +73,23 @@ describe('unit', () => { describe('property value', () => { it('renders property points y value', () => { const Y_VALUE = 123445; - render(); + render( + + ); expect(screen.queryByText(Y_VALUE)).not.toBeNull(); }); it('renders alarm points y value', () => { const Y_VALUE = 123445; - render(); + render( + + ); expect(screen.queryByText(Y_VALUE)).not.toBeNull(); }); @@ -87,28 +115,48 @@ describe('timestamp', () => { it('renders property timestamp when showTimestamp is true', () => { render( - + ); - expect(screen.queryByText(PROPERTY_POINT_DATE.toLocaleString())).not.toBeNull(); + expect( + screen.queryByText(PROPERTY_POINT_DATE.toLocaleString()) + ).not.toBeNull(); }); it('does not render property timestamp when showTimestamp is false', () => { render( - + ); expect(screen.queryByText(PROPERTY_POINT_DATE.toLocaleString())).toBeNull(); }); it('renders alarm timestamp when showTimestamp is true', () => { - render(); + render( + + ); - expect(screen.queryByText(ALARM_POINT_DATE.toLocaleString())).not.toBeNull(); + expect( + screen.queryByText(ALARM_POINT_DATE.toLocaleString()) + ).not.toBeNull(); }); it('does not render alarm timestamp when showTimestamp is false', () => { - render(); + render( + + ); expect(screen.queryByText(ALARM_POINT_DATE.toLocaleString())).toBeNull(); }); @@ -123,7 +171,9 @@ describe('timestamp', () => { ); expect(screen.queryByText(ALARM_POINT_DATE.toLocaleString())).toBeNull(); - expect(screen.queryByText(PROPERTY_POINT_DATE.toLocaleString())).not.toBeNull(); + expect( + screen.queryByText(PROPERTY_POINT_DATE.toLocaleString()) + ).not.toBeNull(); }); }); @@ -157,7 +207,13 @@ describe('loading', () => { }); it('does not render icon while loading when showIcon is true', () => { - render(); + render( + + ); expect(screen.queryByTestId('status-icon-active')).toBeNull(); }); @@ -170,7 +226,13 @@ describe('loading', () => { it('does not render timestamp while loading and showTimestamp is true', () => { const DATE = new Date(2001, 0, 0); - render(); + render( + + ); expect(screen.queryByText(DATE.toLocaleString())).toBeNull(); }); @@ -178,12 +240,16 @@ describe('loading', () => { describe('icon', () => { it('renders icon when showIcon is true', () => { - render(); + render( + + ); expect(screen.queryByTestId('status-icon-active')).not.toBeNull(); }); it('does not render icon when showIcon is false', () => { - render(); + render( + + ); expect(screen.queryByTestId('status-icon-active')).toBeNull(); }); }); diff --git a/packages/react-components/src/components/kpi/kpiBase.tsx b/packages/react-components/src/components/kpi/kpiBase.tsx index ea4e12df9..6ec90ac80 100644 --- a/packages/react-components/src/components/kpi/kpiBase.tsx +++ b/packages/react-components/src/components/kpi/kpiBase.tsx @@ -4,7 +4,11 @@ import Spinner from '@cloudscape-design/components/spinner'; import Box from '@cloudscape-design/components/box'; import omitBy from 'lodash.omitby'; -import { DEFAULT_KPI_SETTINGS, DEFAULT_KPI_COLOR, KPI_ICON_SHRINK_FACTOR } from './constants'; +import { + DEFAULT_KPI_SETTINGS, + DEFAULT_KPI_COLOR, + KPI_ICON_SHRINK_FACTOR, +} from './constants'; import { StatusIcon, Value } from '../shared-components'; import type { KPIProperties, KPISettings } from './types'; import { getAggregationFrequency } from '../../utils/aggregationFrequency'; @@ -23,11 +27,18 @@ export const KpiBase: React.FC = ({ settings = {}, significantDigits, }) => { - const { showName, showUnit, showIcon, showTimestamp, fontSize, aggregationFontSize, secondaryFontSize }: KPISettings = - { - ...DEFAULT_KPI_SETTINGS, - ...omitBy(settings, (x) => x == null), - }; + const { + showName, + showUnit, + showIcon, + showTimestamp, + fontSize, + aggregationFontSize, + secondaryFontSize, + }: KPISettings = { + ...DEFAULT_KPI_SETTINGS, + ...omitBy(settings, (x) => x == null), + }; // Primary point to display. KPI Emphasizes the property point over the alarm point. const point = propertyPoint || alarmPoint; @@ -45,15 +56,26 @@ export const KpiBase: React.FC = ({
    {isLoading && } {!isLoading && showIcon && icon && ( - + )} {!isLoading && ( - + )}
    - {point && !isLoading && showTimestamp && new Date(point.x).toLocaleString()} + {point && + !isLoading && + showTimestamp && + new Date(point.x).toLocaleString()} {!isLoading && point?.y && (
    {getAggregationFrequency(resolution, aggregationType)} diff --git a/packages/react-components/src/components/line-chart/lineChart.spec.tsx b/packages/react-components/src/components/line-chart/lineChart.spec.tsx index 973579bef..3c41524d9 100644 --- a/packages/react-components/src/components/line-chart/lineChart.spec.tsx +++ b/packages/react-components/src/components/line-chart/lineChart.spec.tsx @@ -6,7 +6,13 @@ import { DataStream } from '@iot-app-kit/core'; const VIEWPORT = { duration: '5m' }; -const DATA_STREAM: DataStream = { id: 'abc-1', data: [], resolution: 0, name: 'my-name', color: 'black' }; +const DATA_STREAM: DataStream = { + id: 'abc-1', + data: [], + resolution: 0, + name: 'my-name', + color: 'black', +}; it('renders', async () => { const query = mockTimeSeriesDataQuery([ @@ -17,7 +23,9 @@ it('renders', async () => { }, ]); - const { container } = render(); + const { container } = render( + + ); const widget = container.querySelector('iot-app-kit-vis-line-chart'); expect(widget).not.toBeNull(); diff --git a/packages/react-components/src/components/line-chart/lineChart.tsx b/packages/react-components/src/components/line-chart/lineChart.tsx index fc7ebdbbf..f6d1a92ab 100644 --- a/packages/react-components/src/components/line-chart/lineChart.tsx +++ b/packages/react-components/src/components/line-chart/lineChart.tsx @@ -1,10 +1,23 @@ import React from 'react'; -import { StyleSettingsMap, Threshold, TimeSeriesDataQuery, Viewport, ThresholdSettings } from '@iot-app-kit/core'; +import { + StyleSettingsMap, + Threshold, + TimeSeriesDataQuery, + Viewport, + ThresholdSettings, +} from '@iot-app-kit/core'; import { LineChart as LineChartBase } from '@iot-app-kit/charts'; -import type { DataStream as DataStreamViz, YAnnotation } from '@iot-app-kit/charts-core'; +import type { + DataStream as DataStreamViz, + YAnnotation, +} from '@iot-app-kit/charts-core'; import { useTimeSeriesData } from '../../hooks/useTimeSeriesData'; import { useViewport } from '../../hooks/useViewport'; -import { DEFAULT_LEGEND, DEFAULT_VIEWPORT, ECHARTS_GESTURE } from '../../common/constants'; +import { + DEFAULT_LEGEND, + DEFAULT_VIEWPORT, + ECHARTS_GESTURE, +} from '../../common/constants'; import { AxisSettings, ChartSize } from '../../common/chartTypes'; export interface LineChartProps { @@ -51,7 +64,9 @@ export const LineChart = (props: LineChartProps) => { // if using echarts then echarts gesture overrides passed in viewport // else explicitly passed in viewport overrides viewport group const utilizedViewport = - (lastUpdatedBy === ECHARTS_GESTURE ? viewport : passedInViewport || viewport) ?? DEFAULT_VIEWPORT; + (lastUpdatedBy === ECHARTS_GESTURE + ? viewport + : passedInViewport || viewport) ?? DEFAULT_VIEWPORT; return ( { setViewport={setViewport} annotations={{ y: allThresholds as YAnnotation[], - thresholdOptions: { showColor: thresholdSettings?.colorBreachedData ?? true }, + thresholdOptions: { + showColor: thresholdSettings?.colorBreachedData ?? true, + }, }} aggregationType={aggregationType} legend={DEFAULT_LEGEND} diff --git a/packages/react-components/src/components/menu/option.spec.tsx b/packages/react-components/src/components/menu/option.spec.tsx index b4fbe3980..3885c1b82 100644 --- a/packages/react-components/src/components/menu/option.spec.tsx +++ b/packages/react-components/src/components/menu/option.spec.tsx @@ -4,7 +4,11 @@ import { MenuOption } from './option'; it('renders', () => { render( -
    icon-start
    } label='test option' iconEnd={() =>
    icon-end
    } /> +
    icon-start
    } + label='test option' + iconEnd={() =>
    icon-end
    } + /> ); expect(screen.queryByText('icon-start')).not.toBeNull(); diff --git a/packages/react-components/src/components/menu/option.tsx b/packages/react-components/src/components/menu/option.tsx index 2e98e904e..4f6bff540 100644 --- a/packages/react-components/src/components/menu/option.tsx +++ b/packages/react-components/src/components/menu/option.tsx @@ -14,9 +14,12 @@ import { import './option.css'; -const isPointerEvent = (e: React.MouseEvent | React.KeyboardEvent): e is React.MouseEvent => e.type === 'click'; -const isEnterEvent = (e: React.MouseEvent | React.KeyboardEvent): e is React.KeyboardEvent => - !isPointerEvent(e) && isHotkey('enter', e); +const isPointerEvent = ( + e: React.MouseEvent | React.KeyboardEvent +): e is React.MouseEvent => e.type === 'click'; +const isEnterEvent = ( + e: React.MouseEvent | React.KeyboardEvent +): e is React.KeyboardEvent => !isPointerEvent(e) && isHotkey('enter', e); export type MenuOptionProps = { id?: string; @@ -83,7 +86,10 @@ export const MenuOption: React.FC> = ({ const borderRadius = hover ? borderRadiusDropdown : 0; - const disabledStyle = { color: colorBackgroundControlDisabled, cursor: 'not-allowed' }; + const disabledStyle = { + color: colorBackgroundControlDisabled, + cursor: 'not-allowed', + }; return ( // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
  • > = ({ onPointerEnter={handlePointerEnter} onPointerLeave={handlePointerLeave} > - {iconStart &&
    {iconStart()}
    } + {iconStart && ( +
    + {iconStart()} +
    + )}
    {children} {label}
    - {iconEnd &&
    {iconEnd()}
    } + {iconEnd && ( +
    {iconEnd()}
    + )}
  • ); }; diff --git a/packages/react-components/src/components/menu/positionable.tsx b/packages/react-components/src/components/menu/positionable.tsx index 2ddedf44e..e4f572fc7 100644 --- a/packages/react-components/src/components/menu/positionable.tsx +++ b/packages/react-components/src/components/menu/positionable.tsx @@ -29,7 +29,10 @@ export type PositionableMenuProps = { } & MenuProps & (AbsoluteMenu | RelativeMenu); -const createReferenceElement = (setRef: RefCallback, position: Position) => { +const createReferenceElement = ( + setRef: RefCallback, + position: Position +) => { return (
    , position: P * and also position the menu relatively against another html element * or absolutely by a position. */ -export const PositionableMenu: React.FC> = ({ +export const PositionableMenu: React.FC< + PropsWithChildren +> = ({ position, referenceElement, open, @@ -89,7 +94,9 @@ export const PositionableMenu: React.FC return ( <> - {position ? createReferenceElement(setPlacementRef, position) : referenceElement(setPlacementRef)} + {position + ? createReferenceElement(setPlacementRef, position) + : referenceElement(setPlacementRef)}
    { const query = mockTimeSeriesDataQuery([ @@ -17,7 +23,9 @@ it('renders', async () => { }, ]); - const { container } = render(); + const { container } = render( + + ); const widget = container.querySelector('iot-app-kit-vis-scatter-chart'); expect(widget).not.toBeNull(); diff --git a/packages/react-components/src/components/scatter-chart/scatterChart.tsx b/packages/react-components/src/components/scatter-chart/scatterChart.tsx index f8f336421..ca3cd89c5 100644 --- a/packages/react-components/src/components/scatter-chart/scatterChart.tsx +++ b/packages/react-components/src/components/scatter-chart/scatterChart.tsx @@ -1,11 +1,21 @@ import React from 'react'; -import { StyleSettingsMap, Threshold, ThresholdSettings, TimeSeriesDataQuery, Viewport } from '@iot-app-kit/core'; +import { + StyleSettingsMap, + Threshold, + ThresholdSettings, + TimeSeriesDataQuery, + Viewport, +} from '@iot-app-kit/core'; import { ScatterChart as ScatterChartBase } from '@iot-app-kit/charts'; import type { DataStream as DataStreamViz } from '@iot-app-kit/charts-core'; import { YAnnotation } from '@iot-app-kit/charts-core'; import { useTimeSeriesData } from '../../hooks/useTimeSeriesData'; import { useViewport } from '../../hooks/useViewport'; -import { DEFAULT_LEGEND, DEFAULT_VIEWPORT, ECHARTS_GESTURE } from '../../common/constants'; +import { + DEFAULT_LEGEND, + DEFAULT_VIEWPORT, + ECHARTS_GESTURE, +} from '../../common/constants'; import { AxisSettings, ChartSize } from '../../common/chartTypes'; export interface ScatterChartProps { @@ -51,7 +61,9 @@ export const ScatterChart = (props: ScatterChartProps) => { // if using echarts then echarts gesture overrides passed in viewport // else explicitly passed in viewport overrides viewport group const utilizedViewport = - (lastUpdatedBy === ECHARTS_GESTURE ? viewport : passedInViewport || viewport) ?? DEFAULT_VIEWPORT; + (lastUpdatedBy === ECHARTS_GESTURE + ? viewport + : passedInViewport || viewport) ?? DEFAULT_VIEWPORT; return ( { viewport={{ ...utilizedViewport, group, lastUpdatedBy, yMin, yMax }} annotations={{ y: allThresholds as YAnnotation[], - thresholdOptions: { showColor: thresholdSettings?.colorBreachedData ?? true }, + thresholdOptions: { + showColor: thresholdSettings?.colorBreachedData ?? true, + }, }} setViewport={setViewport} legend={DEFAULT_LEGEND} diff --git a/packages/react-components/src/components/shared-components/Value/Value.tsx b/packages/react-components/src/components/shared-components/Value/Value.tsx index 25f65755d..7c3ec0efe 100644 --- a/packages/react-components/src/components/shared-components/Value/Value.tsx +++ b/packages/react-components/src/components/shared-components/Value/Value.tsx @@ -2,11 +2,11 @@ import React from 'react'; import type { Primitive } from '@iot-app-kit/core'; import { round } from '@iot-app-kit/core-util'; -export const Value: React.FC<{ value?: Primitive; unit?: string; precision?: number }> = ({ - value, - unit, - precision, -}) => { +export const Value: React.FC<{ + value?: Primitive; + unit?: string; + precision?: number; +}> = ({ value, unit, precision }) => { if (value == null) { return -; } diff --git a/packages/react-components/src/components/status-timeline/statusTimeline.spec.tsx b/packages/react-components/src/components/status-timeline/statusTimeline.spec.tsx index b95e0d876..66f9f0428 100644 --- a/packages/react-components/src/components/status-timeline/statusTimeline.spec.tsx +++ b/packages/react-components/src/components/status-timeline/statusTimeline.spec.tsx @@ -6,7 +6,13 @@ import { DataStream } from '@iot-app-kit/core'; const VIEWPORT = { duration: '5m' }; -const DATA_STREAM: DataStream = { id: 'abc-1', data: [], resolution: 0, name: 'my-name', color: 'black' }; +const DATA_STREAM: DataStream = { + id: 'abc-1', + data: [], + resolution: 0, + name: 'my-name', + color: 'black', +}; it('renders', async () => { const query = mockTimeSeriesDataQuery([ @@ -17,7 +23,9 @@ it('renders', async () => { }, ]); - const { container } = render(); + const { container } = render( + + ); const chart = container.querySelector('iot-app-kit-vis-status-timeline'); expect(chart).not.toBeNull(); diff --git a/packages/react-components/src/components/status-timeline/statusTimeline.tsx b/packages/react-components/src/components/status-timeline/statusTimeline.tsx index 833706c25..acb45d1f2 100644 --- a/packages/react-components/src/components/status-timeline/statusTimeline.tsx +++ b/packages/react-components/src/components/status-timeline/statusTimeline.tsx @@ -1,13 +1,29 @@ import React from 'react'; -import { StyleSettingsMap, Threshold, TimeSeriesDataQuery, Viewport } from '@iot-app-kit/core'; -import { StatusTimeline as StatusTimelineBaseWrongType, LineChart } from '@iot-app-kit/charts'; -import type { DataStream as DataStreamViz, Annotations } from '@iot-app-kit/charts-core'; +import { + StyleSettingsMap, + Threshold, + TimeSeriesDataQuery, + Viewport, +} from '@iot-app-kit/core'; +import { + StatusTimeline as StatusTimelineBaseWrongType, + LineChart, +} from '@iot-app-kit/charts'; +import type { + DataStream as DataStreamViz, + Annotations, +} from '@iot-app-kit/charts-core'; import { useTimeSeriesData } from '../../hooks/useTimeSeriesData'; import { useViewport } from '../../hooks/useViewport'; -import { DEFAULT_LEGEND, DEFAULT_VIEWPORT, ECHARTS_GESTURE } from '../../common/constants'; +import { + DEFAULT_LEGEND, + DEFAULT_VIEWPORT, + ECHARTS_GESTURE, +} from '../../common/constants'; // TODO: Remove this type assertion - iot-app-kit/charts has the wrong type for StatusTimeline -const StatusTimelineBase: typeof LineChart = StatusTimelineBaseWrongType as unknown as typeof LineChart; +const StatusTimelineBase: typeof LineChart = + StatusTimelineBaseWrongType as unknown as typeof LineChart; type StatusTimelineAxisSettings = { showX?: boolean }; @@ -44,7 +60,9 @@ export const StatusTimeline = ({ // if using echarts then echarts gesture overrides passed in viewport // else explicitly passed in viewport overrides viewport group const utilizedViewport = - (lastUpdatedBy === ECHARTS_GESTURE ? viewport : passedInViewport || viewport) ?? DEFAULT_VIEWPORT; + (lastUpdatedBy === ECHARTS_GESTURE + ? viewport + : passedInViewport || viewport) ?? DEFAULT_VIEWPORT; return ( { try { - return parseColor(backgroundColor).isLight() ? HIGH_LIGHTNESS_COLOR : LOW_LIGHTNESS_COLOR; + return parseColor(backgroundColor).isLight() + ? HIGH_LIGHTNESS_COLOR + : LOW_LIGHTNESS_COLOR; } catch (err) { // Invalid color string was likely passed into `color`. /* eslint-disable-next-line no-console */ diff --git a/packages/react-components/src/components/status/status.tsx b/packages/react-components/src/components/status/status.tsx index 0b63de4c2..3ef6653d8 100644 --- a/packages/react-components/src/components/status/status.tsx +++ b/packages/react-components/src/components/status/status.tsx @@ -58,9 +58,14 @@ export const Status = ({ }); const name = alarmStream?.name || propertyStream?.name; - const unit = alarmStream?.unit || (alarmStream == null && propertyStream?.unit) || undefined; - const color = alarmThreshold?.color || propertyThreshold?.color || settings?.color; - const isLoading = alarmStream?.isLoading || propertyStream?.isLoading || false; + const unit = + alarmStream?.unit || + (alarmStream == null && propertyStream?.unit) || + undefined; + const color = + alarmThreshold?.color || propertyThreshold?.color || settings?.color; + const isLoading = + alarmStream?.isLoading || propertyStream?.isLoading || false; const error = alarmStream?.error || propertyStream?.error; return ( diff --git a/packages/react-components/src/components/status/statusBase.spec.tsx b/packages/react-components/src/components/status/statusBase.spec.tsx index 4c33956b0..6e55276d0 100644 --- a/packages/react-components/src/components/status/statusBase.spec.tsx +++ b/packages/react-components/src/components/status/statusBase.spec.tsx @@ -25,14 +25,26 @@ describe('unit', () => { it('renders unit when showUnit is true and provided a property point', () => { const someUnit = 'some-Unit'; - render(); + render( + + ); expect(screen.queryByText(someUnit)).not.toBeNull(); }); it('does not render unit when showUnit is true and provided a alarm point', () => { const someUnit = 'some-Unit'; - render(); + render( + + ); expect(screen.queryByText(someUnit)).toBeNull(); }); @@ -46,7 +58,13 @@ describe('unit', () => { it('does not render unit when showUnit is false', () => { const someUnit = 'some-Unit'; - render(); + render( + + ); expect(screen.queryByText(someUnit)).toBeNull(); }); @@ -55,13 +73,23 @@ describe('unit', () => { describe('property value', () => { it('renders property points y value', () => { const Y_VALUE = 123445; - render(); + render( + + ); expect(screen.queryByText(Y_VALUE)).not.toBeNull(); }); it('renders alarm points y value', () => { const Y_VALUE = 123445; - render(); + render( + + ); expect(screen.queryByText(Y_VALUE)).not.toBeNull(); }); @@ -111,7 +139,13 @@ describe('loading', () => { }); it('does not render icon while loading when showIcon is true', () => { - render(); + render( + + ); expect(screen.queryByTestId('status-icon-active')).toBeNull(); }); diff --git a/packages/react-components/src/components/status/statusBase.tsx b/packages/react-components/src/components/status/statusBase.tsx index feb9c4d28..98a952b80 100644 --- a/packages/react-components/src/components/status/statusBase.tsx +++ b/packages/react-components/src/components/status/statusBase.tsx @@ -6,7 +6,11 @@ import * as cloudscape from '@cloudscape-design/design-tokens'; import { StatusIcon, Value } from '../shared-components'; import { highContrastColor } from './highContrastColor'; -import { DEFAULT_STATUS_SETTINGS, DEFAULT_STATUS_COLOR, STATUS_ICON_SHRINK_FACTOR } from './constants'; +import { + DEFAULT_STATUS_SETTINGS, + DEFAULT_STATUS_COLOR, + STATUS_ICON_SHRINK_FACTOR, +} from './constants'; import { DEFAULT_MESSAGE_OVERRIDES } from '../../common/dataTypes'; import omitBy from 'lodash.omitby'; import styled from 'styled-components'; @@ -38,11 +42,18 @@ export const StatusBase: React.FC = ({ settings = {}, significantDigits, }) => { - const { showName, showUnit, showValue, showIcon, fontSize, aggregationFontSize, secondaryFontSize }: StatusSettings = - { - ...DEFAULT_STATUS_SETTINGS, - ...omitBy(settings, (x) => x == null), - }; + const { + showName, + showUnit, + showValue, + showIcon, + fontSize, + aggregationFontSize, + secondaryFontSize, + }: StatusSettings = { + ...DEFAULT_STATUS_SETTINGS, + ...omitBy(settings, (x) => x == null), + }; // Primary point to display const point = alarmPoint || propertyPoint; @@ -66,7 +77,9 @@ export const StatusBase: React.FC = ({ padding: cloudscape.spaceStaticM, }} > - {showName && {name}} + {showName && ( + {name} + )} {error && ( @@ -99,7 +112,11 @@ export const StatusBase: React.FC = ({ color={highContrastColor(backgroundColor)} /> )} - + {!isLoading && (
    {getAggregationFrequency(resolution, aggregationType)} diff --git a/packages/react-components/src/components/table/createCellItem.spec.ts b/packages/react-components/src/components/table/createCellItem.spec.ts index 79f2bb271..34a852eb7 100644 --- a/packages/react-components/src/components/table/createCellItem.spec.ts +++ b/packages/react-components/src/components/table/createCellItem.spec.ts @@ -3,17 +3,29 @@ import { DEFAULT_TABLE_MESSAGES } from './messages'; import type { CellItem } from './types'; import type { TableMessages } from './messages'; -const messageOverride = { tableCell: { loading: 'Override loading text' } } as TableMessages; +const messageOverride = { + tableCell: { loading: 'Override loading text' }, +} as TableMessages; it('creates CellItem', () => { - const props = { value: 10, error: undefined, isLoading: false, threshold: undefined }; + const props = { + value: 10, + error: undefined, + isLoading: false, + threshold: undefined, + }; const item: CellItem = createCellItem(props, DEFAULT_TABLE_MESSAGES); expect(item).toMatchObject({ value: 10 }); expect(item.valueOf()).toEqual(10); }); it('creates CellItem that returns error message on error', () => { - const props = { value: 10, error: { msg: 'Some error' }, isLoading: false, threshold: undefined }; + const props = { + value: 10, + error: { msg: 'Some error' }, + isLoading: false, + threshold: undefined, + }; const item: CellItem = createCellItem(props, DEFAULT_TABLE_MESSAGES); expect(`${item}`).toBe('Some error'); diff --git a/packages/react-components/src/components/table/createCellItem.ts b/packages/react-components/src/components/table/createCellItem.ts index 8e815eec9..3b303ac80 100644 --- a/packages/react-components/src/components/table/createCellItem.ts +++ b/packages/react-components/src/components/table/createCellItem.ts @@ -1,7 +1,10 @@ import type { CellItem, CellProps } from './types'; import type { TableMessages } from './messages'; -export const createCellItem: (props: Partial, messageOverrides: TableMessages) => CellItem = ( +export const createCellItem: ( + props: Partial, + messageOverrides: TableMessages +) => CellItem = ( { value, error, isLoading, threshold } = {}, messageOverrides ) => { diff --git a/packages/react-components/src/components/table/createTableItems.spec.ts b/packages/react-components/src/components/table/createTableItems.spec.ts index e41c0e7f5..28b3f60da 100644 --- a/packages/react-components/src/components/table/createTableItems.spec.ts +++ b/packages/react-components/src/components/table/createTableItems.spec.ts @@ -83,7 +83,10 @@ const itemWithRef = [ ] as TableItem[]; it('creates table items', () => { - const items = createTableItems({ dataStreams, viewport, items: itemWithRef }, DEFAULT_TABLE_MESSAGES); + const items = createTableItems( + { dataStreams, viewport, items: itemWithRef }, + DEFAULT_TABLE_MESSAGES + ); expect(items).toMatchObject([ { value1: { value: 4 }, @@ -105,7 +108,10 @@ it('creates table items', () => { }); it('returns value as it is a primitive value', () => { - const items = createTableItems({ dataStreams, viewport, items: itemWithRef }, DEFAULT_TABLE_MESSAGES); + const items = createTableItems( + { dataStreams, viewport, items: itemWithRef }, + DEFAULT_TABLE_MESSAGES + ); const data = items[0].value1; expect((data as number) + 1).toBe(5); }); @@ -131,8 +137,14 @@ it('gets different data points on different viewports on the same data stream', }, }, ]; - const items1 = createTableItems({ dataStreams, viewport: viewport1, items: itemDef }, DEFAULT_TABLE_MESSAGES); - const items2 = createTableItems({ dataStreams, viewport: viewport2, items: itemDef }, DEFAULT_TABLE_MESSAGES); + const items1 = createTableItems( + { dataStreams, viewport: viewport1, items: itemDef }, + DEFAULT_TABLE_MESSAGES + ); + const items2 = createTableItems( + { dataStreams, viewport: viewport2, items: itemDef }, + DEFAULT_TABLE_MESSAGES + ); expect(items1).not.toEqual(items2); }); @@ -153,7 +165,10 @@ it('returns undefined value when no data points in data stream', () => { }, }, ]; - const items1 = createTableItems({ dataStreams, viewport: viewport1, items: itemDef }, DEFAULT_TABLE_MESSAGES); + const items1 = createTableItems( + { dataStreams, viewport: viewport1, items: itemDef }, + DEFAULT_TABLE_MESSAGES + ); expect(items1).toMatchObject([{ noDataPoints: { value: undefined } }]); }); diff --git a/packages/react-components/src/components/table/createTableItems.ts b/packages/react-components/src/components/table/createTableItems.ts index 67b291e8e..68ab51c71 100644 --- a/packages/react-components/src/components/table/createTableItems.ts +++ b/packages/react-components/src/components/table/createTableItems.ts @@ -1,4 +1,9 @@ -import type { DataStream, Primitive, Threshold, Viewport } from '@iot-app-kit/core'; +import type { + DataStream, + Primitive, + Threshold, + Viewport, +} from '@iot-app-kit/core'; import { getDataBeforeDate } from '@iot-app-kit/core'; import { breachedThreshold } from '../../utils/breachedThreshold'; import { createCellItem } from './createCellItem'; @@ -14,7 +19,10 @@ export const createTableItems: ( thresholds?: Threshold[]; }, messageOverrides: TableMessages -) => TableItemHydrated[] = ({ dataStreams, viewport, items, thresholds = [] }, messageOverrides) => { +) => TableItemHydrated[] = ( + { dataStreams, viewport, items, thresholds = [] }, + messageOverrides +) => { return items.map((item) => { const keys = Object.keys(item); const keyDataPairs = keys.map<{ key: string; data: CellItem }>((key) => { @@ -27,7 +35,10 @@ export const createTableItems: ( const { error, isLoading, data: dataPoints } = dataStream; if (dataStream.resolution !== $cellRef.resolution) { - return { key, data: createCellItem({ error, isLoading }, messageOverrides) }; + return { + key, + data: createCellItem({ error, isLoading }, messageOverrides), + }; } if ('end' in viewport && dataPoints) { @@ -40,7 +51,13 @@ export const createTableItems: ( thresholds, date: viewport.end, }); - return { key, data: createCellItem({ value, error, isLoading, threshold }, messageOverrides) }; + return { + key, + data: createCellItem( + { value, error, isLoading, threshold }, + messageOverrides + ), + }; } const value = dataPoints.slice(-1)[0]?.y; @@ -52,11 +69,23 @@ export const createTableItems: ( date: new Date(Date.now()), }); - return { key, data: createCellItem({ value, error, isLoading, threshold }, messageOverrides) }; + return { + key, + data: createCellItem( + { value, error, isLoading, threshold }, + messageOverrides + ), + }; } return { key, data: createCellItem({}, messageOverrides) }; } - return { key, data: createCellItem({ value: item[key] as Primitive }, messageOverrides) }; + return { + key, + data: createCellItem( + { value: item[key] as Primitive }, + messageOverrides + ), + }; }); return keyDataPairs.reduce( diff --git a/packages/react-components/src/components/table/spinner.tsx b/packages/react-components/src/components/table/spinner.tsx index 5ecf53b7d..d1bd70e84 100644 --- a/packages/react-components/src/components/table/spinner.tsx +++ b/packages/react-components/src/components/table/spinner.tsx @@ -1,8 +1,8 @@ import React from 'react'; -export const LoadingSpinner: React.FunctionComponent<{ size?: number; dark?: boolean } | Record> = ( - props -) => { +export const LoadingSpinner: React.FunctionComponent< + { size?: number; dark?: boolean } | Record +> = (props) => { const { size, dark = false } = props; return ( @@ -24,34 +24,138 @@ export const LoadingSpinner: React.FunctionComponent<{ size?: number; dark?: boo - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/react-components/src/components/table/table.spec.tsx b/packages/react-components/src/components/table/table.spec.tsx index cf3ba1a16..3ecb80013 100644 --- a/packages/react-components/src/components/table/table.spec.tsx +++ b/packages/react-components/src/components/table/table.spec.tsx @@ -6,7 +6,12 @@ import { Table } from './table'; const VIEWPORT = { duration: '5m' }; -const DATA_STREAM: DataStream = { id: 'abc-1', data: [], resolution: 0, name: 'my-name' }; +const DATA_STREAM: DataStream = { + id: 'abc-1', + data: [], + resolution: 0, + name: 'my-name', +}; it('renders', async () => { const query = mockTimeSeriesDataQuery([ @@ -18,6 +23,13 @@ it('renders', async () => { ]); expect(() => { - render(); + render( +
    + ); }).not.toThrowError(); }); diff --git a/packages/react-components/src/components/table/table.tsx b/packages/react-components/src/components/table/table.tsx index 031d8d297..90dd4069a 100644 --- a/packages/react-components/src/components/table/table.tsx +++ b/packages/react-components/src/components/table/table.tsx @@ -2,7 +2,12 @@ import React from 'react'; import { TableBase } from './tableBase'; import { useTimeSeriesData } from '../../hooks/useTimeSeriesData'; import { useViewport } from '../../hooks/useViewport'; -import type { StyleSettingsMap, Threshold, TimeSeriesDataQuery, Viewport } from '@iot-app-kit/core'; +import type { + StyleSettingsMap, + Threshold, + TimeSeriesDataQuery, + Viewport, +} from '@iot-app-kit/core'; import { UseCollectionOptions } from '@cloudscape-design/collection-hooks'; import { TableColumnDefinition, TableItem, TableItemHydrated } from './types'; import { createTableItems } from './createTableItems'; @@ -36,7 +41,14 @@ export const Table = ({ significantDigits?: number; paginationEnabled?: boolean; pageSize?: number; -} & Pick) => { +} & Pick< + TableBaseProps, + | 'resizableColumns' + | 'sortingDisabled' + | 'stickyHeader' + | 'empty' + | 'preferences' +>) => { const { dataStreams, thresholds: queryThresholds } = useTimeSeriesData({ viewport: passedInViewport, queries, diff --git a/packages/react-components/src/components/table/tableBase.spec.tsx b/packages/react-components/src/components/table/tableBase.spec.tsx index e73085efd..92000c380 100644 --- a/packages/react-components/src/components/table/tableBase.spec.tsx +++ b/packages/react-components/src/components/table/tableBase.spec.tsx @@ -48,10 +48,16 @@ it('renders correct data when viewport defined by duration', async () => { ); const { container } = render( - + ); - const cell = container.getElementsByClassName('iot-table-cell').item(0) as HTMLDivElement; + const cell = container + .getElementsByClassName('iot-table-cell') + .item(0) as HTMLDivElement; expect(cell.innerHTML).toMatch('200'); }); @@ -60,12 +66,21 @@ it('renders correct data when the viewport defined by start and end time', async start: new Date(2021, 0, 0, 0, 0, 0), end: new Date(2021, 12, 30, 0, 0, 0), }; - const tableItems = createTableItems({ dataStreams: [dataStream], items, viewport }, DEFAULT_TABLE_MESSAGES); + const tableItems = createTableItems( + { dataStreams: [dataStream], items, viewport }, + DEFAULT_TABLE_MESSAGES + ); const { container } = render( - + ); - const cell = container.getElementsByClassName('iot-table-cell').item(0) as HTMLDivElement; + const cell = container + .getElementsByClassName('iot-table-cell') + .item(0) as HTMLDivElement; expect(cell.innerHTML).toMatch('100'); }); @@ -82,7 +97,11 @@ it('renders loading circle when datastream is in loading state', async () => { ); const { container } = render( - + ); const svg = container.getElementsByTagName('svg').item(0) as SVGElement; @@ -109,10 +128,16 @@ it('renders icon and applies style when a datastream breaches threshold', async ); const { container } = render( - + ); - const cell = container.getElementsByClassName('iot-table-cell').item(0) as HTMLDivElement; + const cell = container + .getElementsByClassName('iot-table-cell') + .item(0) as HTMLDivElement; expect(cell.style.color).toEqual('red'); const iconContainer = cell.childNodes.item(0); expect(iconContainer).toBeTruthy(); @@ -140,10 +165,16 @@ it('renders icon and displays error message when datastream is in error state', ); const { container } = render( - + ); - const cell = container.getElementsByClassName('iot-table-cell').item(0) as HTMLDivElement; + const cell = container + .getElementsByClassName('iot-table-cell') + .item(0) as HTMLDivElement; const iconContainer = cell.childNodes.item(0); expect(iconContainer).toBeTruthy(); diff --git a/packages/react-components/src/components/table/tableBase.tsx b/packages/react-components/src/components/table/tableBase.tsx index b635a4686..69856fbf6 100644 --- a/packages/react-components/src/components/table/tableBase.tsx +++ b/packages/react-components/src/components/table/tableBase.tsx @@ -1,5 +1,9 @@ import React from 'react'; -import { Pagination, PropertyFilter, Table } from '@cloudscape-design/components'; +import { + Pagination, + PropertyFilter, + Table, +} from '@cloudscape-design/components'; import { useCollection } from '@cloudscape-design/collection-hooks'; import { getDefaultColumnDefinitions } from './tableHelpers'; import type { FunctionComponent } from 'react'; @@ -18,13 +22,21 @@ export const TableBase: FunctionComponent = (props) => { pageSize, empty, } = props; - const { items, collectionProps, propertyFilterProps, paginationProps } = useCollection(userItems, { - sorting, - propertyFiltering, - pagination: { pageSize: pageSize ?? DEFAULT_PAGE_SIZE }, - }); - const columnDefinitions = getDefaultColumnDefinitions(userColumnDefinitions, precision); - const pagination = { ...(paginationEnabled && { pagination: }) }; + const { items, collectionProps, propertyFilterProps, paginationProps } = + useCollection(userItems, { + sorting, + propertyFiltering, + pagination: { pageSize: pageSize ?? DEFAULT_PAGE_SIZE }, + }); + const columnDefinitions = getDefaultColumnDefinitions( + userColumnDefinitions, + precision + ); + const pagination = { + ...(paginationEnabled && { + pagination: , + }), + }; return (
    = (props) => { columnDefinitions={columnDefinitions} empty={empty} filter={ - propertyFiltering && + propertyFiltering && ( + + ) } /> ); diff --git a/packages/react-components/src/components/table/tableHelpers.spec.tsx b/packages/react-components/src/components/table/tableHelpers.spec.tsx index 7bdae1b04..278a1979f 100644 --- a/packages/react-components/src/components/table/tableHelpers.spec.tsx +++ b/packages/react-components/src/components/table/tableHelpers.spec.tsx @@ -13,7 +13,10 @@ describe('getDefaultColumnDefinitions', () => { ]; const columnDefs = getDefaultColumnDefinitions(userColumnDefinitions); - expect(columnDefs[0]).toMatchObject({ cell: expect.toBeFunction(), header: 'Header' }); + expect(columnDefs[0]).toMatchObject({ + cell: expect.toBeFunction(), + header: 'Header', + }); }); }); @@ -28,7 +31,9 @@ describe('default cell function', () => { header: 'Not Exist', }, ]; - const [firstColumnDef, secondColumnDef] = getDefaultColumnDefinitions(userColumnDefinitions); + const [firstColumnDef, secondColumnDef] = getDefaultColumnDefinitions( + userColumnDefinitions + ); it("returns item's value", () => { const item: TableItemHydrated = { diff --git a/packages/react-components/src/components/table/tableHelpers.tsx b/packages/react-components/src/components/table/tableHelpers.tsx index 1d8323569..152f5aabc 100644 --- a/packages/react-components/src/components/table/tableHelpers.tsx +++ b/packages/react-components/src/components/table/tableHelpers.tsx @@ -11,10 +11,8 @@ import type { TableColumnDefinition, TableItemHydrated } from './types'; export const getDefaultColumnDefinitions: ( columnDefinitions: TableColumnDefinition[], precision?: number -) => (CloudscapeTableProps.ColumnDefinition & TableColumnDefinition)[] = ( - columnDefinitions, - precision -) => { +) => (CloudscapeTableProps.ColumnDefinition & + TableColumnDefinition)[] = (columnDefinitions, precision) => { return columnDefinitions.map((colDef) => ({ cell: (item: TableItemHydrated) => { if (!(colDef.key in item)) { @@ -26,7 +24,8 @@ export const getDefaultColumnDefinitions: ( if (error) { return (
    -
    {getIcons(STATUS_ICON_TYPE.error)}
    {error.msg} +
    {getIcons(STATUS_ICON_TYPE.error)}
    {' '} + {error.msg}
    ); } @@ -38,15 +37,25 @@ export const getDefaultColumnDefinitions: ( if (colDef.formatter && value) { return (
    - {icon ?
    {getIcons(icon)}
    : null} {colDef.formatter(value)} + {icon ?
    {getIcons(icon)}
    : null}{' '} + {colDef.formatter(value)}
    ); } if (typeof value === 'number') { return ( -
    - {icon ?
    {getIcons(icon)}
    : null} {round(value, precision)} +
    + {icon ?
    {getIcons(icon)}
    : null}{' '} + {round(value, precision)}
    ); } diff --git a/packages/react-components/src/components/table/typePredicates.ts b/packages/react-components/src/components/table/typePredicates.ts index f4dbcf91c..cebb148c3 100644 --- a/packages/react-components/src/components/table/typePredicates.ts +++ b/packages/react-components/src/components/table/typePredicates.ts @@ -1,5 +1,7 @@ import type { TableItem, TableItemRef } from './types'; -export function isTableItemRef(value: TableItem[string]): value is TableItemRef { +export function isTableItemRef( + value: TableItem[string] +): value is TableItemRef { return typeof value === 'object' && value?.$cellRef !== undefined; } diff --git a/packages/react-components/src/components/table/types.ts b/packages/react-components/src/components/table/types.ts index b15698711..fbd671579 100644 --- a/packages/react-components/src/components/table/types.ts +++ b/packages/react-components/src/components/table/types.ts @@ -32,7 +32,11 @@ export type CellItem = { export type TableItemHydrated = { [k: string]: CellItem }; -export interface TableColumnDefinition extends Omit, 'cell'> { +export interface TableColumnDefinition + extends Omit< + CloudscapeTableProps.ColumnDefinition, + 'cell' + > { formatter?: (data: Primitive) => Primitive; key: string; } @@ -44,7 +48,8 @@ export type CellProps = { threshold: Threshold; }; -export interface TableProps extends Omit, 'columnDefinitions'> { +export interface TableProps + extends Omit, 'columnDefinitions'> { columnDefinitions: TableColumnDefinition[]; sorting?: UseCollectionOptions['sorting']; propertyFiltering?: UseCollectionOptions['propertyFiltering']; diff --git a/packages/react-components/src/components/time-sync/index.tsx b/packages/react-components/src/components/time-sync/index.tsx index 982bf1456..5fbf9cbfe 100644 --- a/packages/react-components/src/components/time-sync/index.tsx +++ b/packages/react-components/src/components/time-sync/index.tsx @@ -1,5 +1,11 @@ import { v4 as uuid } from 'uuid'; -import React, { createContext, useCallback, useEffect, useState, useRef } from 'react'; +import React, { + createContext, + useCallback, + useEffect, + useState, + useRef, +} from 'react'; import { Viewport, viewportManager } from '@iot-app-kit/core'; import type { ReactNode } from 'react'; @@ -25,8 +31,14 @@ export const DEFAULT_VIEWPORT: Viewport = { duration: '10m', // default viewport }; -export const TimeSync: React.FC = ({ group, initialViewport, children }) => { - const [viewport, setViewport] = useState(initialViewport || DEFAULT_VIEWPORT); +export const TimeSync: React.FC = ({ + group, + initialViewport, + children, +}) => { + const [viewport, setViewport] = useState( + initialViewport || DEFAULT_VIEWPORT + ); const [lastUpdatedBy, setLastUpdatedBy] = useState(); // Fall back unique viewport group, only used if `group` is not defined. @@ -34,15 +46,20 @@ export const TimeSync: React.FC = ({ group, initialViewport, chil const updateViewportGroup = useCallback( (v: Viewport, lastUpdatedBy?: string) => { - viewportManager.update(group ?? autoGeneratedGroup.current, v, lastUpdatedBy); + viewportManager.update( + group ?? autoGeneratedGroup.current, + v, + lastUpdatedBy + ); setLastUpdatedBy(lastUpdatedBy); }, [group] ); useEffect(() => { - const { viewport, unsubscribe } = viewportManager.subscribe(group ?? autoGeneratedGroup.current, (v) => - setViewport(v as Viewport) + const { viewport, unsubscribe } = viewportManager.subscribe( + group ?? autoGeneratedGroup.current, + (v) => setViewport(v as Viewport) ); // Set initial viewport diff --git a/packages/react-components/src/components/time-sync/rangeValidator.spec.tsx b/packages/react-components/src/components/time-sync/rangeValidator.spec.tsx index 7fa4e26c2..6d30d32df 100644 --- a/packages/react-components/src/components/time-sync/rangeValidator.spec.tsx +++ b/packages/react-components/src/components/time-sync/rangeValidator.spec.tsx @@ -14,15 +14,26 @@ describe('rangeValidator', () => { describe('relative ranges', () => { it('is valid for relative ranges', () => { - expect(rangeValidatorWithMessaging({ type: 'relative', amount: 10, unit: 'minute' }).valid).toBe(true); + expect( + rangeValidatorWithMessaging({ + type: 'relative', + amount: 10, + unit: 'minute', + }).valid + ).toBe(true); }); }); describe('absolute ranges', () => { it('is not valid if the start and or end date are not present', () => { - const missingStartAndEnd = rangeValidatorWithMessaging({ type: 'absolute', startDate: '', endDate: '' }); + const missingStartAndEnd = rangeValidatorWithMessaging({ + type: 'absolute', + startDate: '', + endDate: '', + }); - const missingStartAndEndErrorMessage = !missingStartAndEnd.valid && missingStartAndEnd.errorMessage; + const missingStartAndEndErrorMessage = + !missingStartAndEnd.valid && missingStartAndEnd.errorMessage; expect(missingStartAndEnd.valid).toBe(false); expect(missingStartAndEndErrorMessage).toBeString(); @@ -34,7 +45,8 @@ describe('rangeValidator', () => { endDate: '1899-12-31T06:01:34.689Z', }); - const missingStartErrorMessage = !missingStart.valid && missingStart.errorMessage; + const missingStartErrorMessage = + !missingStart.valid && missingStart.errorMessage; expect(missingStart.valid).toBe(false); expect(missingStartErrorMessage).toBeString(); @@ -46,7 +58,8 @@ describe('rangeValidator', () => { endDate: '', }); - const missingEndErrorMessage = !missingEnd.valid && missingEnd.errorMessage; + const missingEndErrorMessage = + !missingEnd.valid && missingEnd.errorMessage; expect(missingEnd.valid).toBe(false); expect(missingEndErrorMessage).toBeString(); diff --git a/packages/react-components/src/components/time-sync/timeSelection.test.tsx b/packages/react-components/src/components/time-sync/timeSelection.test.tsx index da5f960aa..6faab6d3f 100644 --- a/packages/react-components/src/components/time-sync/timeSelection.test.tsx +++ b/packages/react-components/src/components/time-sync/timeSelection.test.tsx @@ -35,7 +35,9 @@ describe('TimeSelection', () => { dateRangePicker?.openDropdown(); }); - const dateRangePickerDropdown = dateRangePicker?.findDropdown({ expandToViewport: true }); + const dateRangePickerDropdown = dateRangePicker?.findDropdown({ + expandToViewport: true, + }); act(() => { const group = dateRangePickerDropdown?.findRelativeRangeRadioGroup(); @@ -59,7 +61,9 @@ describe('TimeSelection', () => { dateRangePicker?.openDropdown(); }); - const dateRangePickerDropdown = dateRangePicker?.findDropdown({ expandToViewport: true }); + const dateRangePickerDropdown = dateRangePicker?.findDropdown({ + expandToViewport: true, + }); act(() => { const group = dateRangePickerDropdown?.findRelativeRangeRadioGroup(); @@ -68,7 +72,8 @@ describe('TimeSelection', () => { }); act(() => { - const rangeInput = dateRangePickerDropdown?.findCustomRelativeRangeDuration(); + const rangeInput = + dateRangePickerDropdown?.findCustomRelativeRangeDuration(); rangeInput?.setInputValue('15'); }); diff --git a/packages/react-components/src/components/time-sync/timeSelection.tsx b/packages/react-components/src/components/time-sync/timeSelection.tsx index 2d34de867..02ae410c6 100644 --- a/packages/react-components/src/components/time-sync/timeSelection.tsx +++ b/packages/react-components/src/components/time-sync/timeSelection.tsx @@ -35,7 +35,8 @@ const messages: ViewportMessages = { customRelativeRangeOptionLabel: 'Custom range', customRelativeRangeOptionDescription: 'Set a custom range in the past', customRelativeRangeUnitLabel: 'Unit of time', - dateTimeConstraintText: 'For date, use YYYY/MM/DD. For time, use 24 hr format.', + dateTimeConstraintText: + 'For date, use YYYY/MM/DD. For time, use 24 hr format.', relativeModeTitle: 'Relative range', absoluteModeTitle: 'Absolute range', relativeRangeSelectionHeading: 'Choose a range', @@ -46,10 +47,13 @@ const messages: ViewportMessages = { clearButtonLabel: 'Clear and dismiss', cancelButtonLabel: 'Cancel', applyButtonLabel: 'Apply', - formatRelativeRange: (e) => (e.amount === 1 ? `Last ${e.unit}` : `Last ${e.amount} ${e.unit}s`), + formatRelativeRange: (e) => + e.amount === 1 ? `Last ${e.unit}` : `Last ${e.amount} ${e.unit}s`, formatUnit: (e, n) => (1 === n ? e : `${e}s`), - dateRangeIncompleteError: 'The selected date range is incomplete. Select a start and end date for the date range.', - dateRangeInvalidError: 'The selected date range is invalid. The start date must be before the end date.', + dateRangeIncompleteError: + 'The selected date range is incomplete. Select a start and end date for the date range.', + dateRangeInvalidError: + 'The selected date range is invalid. The start date must be before the end date.', }; /** @@ -70,7 +74,9 @@ export const TimeSelection = ({ }) => { const { viewport, setViewport } = useViewport(); - const handleChangeDateRange: NonCancelableEventHandler = (event) => { + const handleChangeDateRange: NonCancelableEventHandler< + DateRangePickerProps.ChangeDetail + > = (event) => { const { value } = event.detail; if (!value) return; setViewport(dateRangeToViewport(value), 'date-picker'); @@ -80,10 +86,15 @@ export const TimeSelection = ({ const value = viewportToDateRange(viewport); if (!value) return; if (value.type === 'absolute') { - const duration = new Date(value.endDate).getTime() - new Date(value.startDate).getTime(); + const duration = + new Date(value.endDate).getTime() - new Date(value.startDate).getTime(); const newEnd = new Date(new Date(value.endDate).getTime() + duration); setViewport( - dateRangeToViewport({ startDate: value.endDate, endDate: newEnd.toISOString(), type: value.type }), + dateRangeToViewport({ + startDate: value.endDate, + endDate: newEnd.toISOString(), + type: value.type, + }), 'date-picker' ); } @@ -91,7 +102,11 @@ export const TimeSelection = ({ const newStart = new Date(); const newEnd = getViewportDateRelativeToAbsolute(value, false, true); setViewport( - dateRangeToViewport({ startDate: newStart.toISOString(), endDate: newEnd.toISOString(), type: 'absolute' }), + dateRangeToViewport({ + startDate: newStart.toISOString(), + endDate: newEnd.toISOString(), + type: 'absolute', + }), 'date-picker' ); } @@ -101,33 +116,56 @@ export const TimeSelection = ({ const value = viewportToDateRange(viewport); if (!value) return; if (value.type === 'absolute') { - const duration = new Date(value.endDate).getTime() - new Date(value.startDate).getTime(); + const duration = + new Date(value.endDate).getTime() - new Date(value.startDate).getTime(); const newStart = new Date(new Date(value.startDate).getTime() - duration); setViewport( - dateRangeToViewport({ startDate: newStart.toISOString(), endDate: value.startDate, type: value.type }), + dateRangeToViewport({ + startDate: newStart.toISOString(), + endDate: value.startDate, + type: value.type, + }), 'date-picker' ); } else if (value.type === 'relative') { const newEnd = getViewportDateRelativeToAbsolute(value); const newStart = getViewportDateRelativeToAbsolute(value, true); setViewport( - dateRangeToViewport({ startDate: newStart.toISOString(), endDate: newEnd.toISOString(), type: 'absolute' }), + dateRangeToViewport({ + startDate: newStart.toISOString(), + endDate: newEnd.toISOString(), + type: 'absolute', + }), 'date-picker' ); } }; - const { title, placeholder, dateRangeIncompleteError, dateRangeInvalidError, ...i18nStrings } = messages; + const { + title, + placeholder, + dateRangeIncompleteError, + dateRangeInvalidError, + ...i18nStrings + } = messages; return ( {isPaginationEnabled && ( +
    `; + )}${getDisplayForVideoOnEdge( + timerangesForVideoOnEdge, + startTimestamp, + endTimestamp + )}${getVideoProgressHolder(playProgressId)}${getStartTimeIndicator( + startTimestamp + )}${getEndTimeIndicator(endTimestamp)}`; }; export const getStartTimeIndicator = (startTimestamp: number) => { @@ -41,7 +48,9 @@ export const getStartTimeIndicator = (startTimestamp: number) => { }; export const getEndTimeIndicator = (endTimestamp: number) => { - return `
    ${getFormattedDateTime(new Date(endTimestamp))}
    `; + return `
    ${getFormattedDateTime( + new Date(endTimestamp) + )}
    `; }; export const getVideoProgressHolder = (playProgressId: string) => { diff --git a/packages/react-components/src/components/video-player/utils/videoProgressUtils.spec.tsx b/packages/react-components/src/components/video-player/utils/videoProgressUtils.spec.tsx index f5c910b0b..defc0371d 100644 --- a/packages/react-components/src/components/video-player/utils/videoProgressUtils.spec.tsx +++ b/packages/react-components/src/components/video-player/utils/videoProgressUtils.spec.tsx @@ -1,18 +1,28 @@ -import { getVideoProgressPercentage, getVideoProgressSeekTime, getVideoProgressTooltip } from './videoProgressUtils'; +import { + getVideoProgressPercentage, + getVideoProgressSeekTime, + getVideoProgressTooltip, +} from './videoProgressUtils'; it('should return video progress percentage', () => { - expect(getVideoProgressPercentage(1665583620000, 20, 1665583520000, 1665583720000)).toEqual(60); + expect( + getVideoProgressPercentage(1665583620000, 20, 1665583520000, 1665583720000) + ).toEqual(60); }); it('should return video seek time according to percentage', () => { - expect(getVideoProgressSeekTime(60, 1665583520000, 1665583720000)).toEqual(1665583640000); + expect(getVideoProgressSeekTime(60, 1665583520000, 1665583720000)).toEqual( + 1665583640000 + ); }); // TimezoneOffset is included to make sure that output is calucalted as expected result without timezone issue during test it('should set valid tooltip on video progress', () => { const seekTime = 1665583620000 + new Date().getTimezoneOffset() * 60000; const startTime = 1665583520000 + new Date().getTimezoneOffset() * 60000; - expect(getVideoProgressTooltip(seekTime, startTime)).toEqual(`10/12\n14:07:00`); + expect(getVideoProgressTooltip(seekTime, startTime)).toEqual( + `10/12\n14:07:00` + ); }); it('should set tooltip to empty text if cursor before start time', () => { diff --git a/packages/react-components/src/components/video-player/utils/videoProgressUtils.tsx b/packages/react-components/src/components/video-player/utils/videoProgressUtils.tsx index 63945425c..8d454aa17 100644 --- a/packages/react-components/src/components/video-player/utils/videoProgressUtils.tsx +++ b/packages/react-components/src/components/video-player/utils/videoProgressUtils.tsx @@ -6,14 +6,25 @@ export const getVideoProgressPercentage = ( startTimeStamp: number, endTimeStamp: number ) => { - return ((currentStart + currentTime * 1000 - startTimeStamp) / (endTimeStamp - startTimeStamp)) * 100; + return ( + ((currentStart + currentTime * 1000 - startTimeStamp) / + (endTimeStamp - startTimeStamp)) * + 100 + ); }; -export const getVideoProgressSeekTime = (percentage: number, startTimeStamp: number, endTimeStamp: number) => { +export const getVideoProgressSeekTime = ( + percentage: number, + startTimeStamp: number, + endTimeStamp: number +) => { return startTimeStamp + (percentage * (endTimeStamp - startTimeStamp)) / 100; }; -export const getVideoProgressTooltip = (seekTime: number, startTime: number) => { +export const getVideoProgressTooltip = ( + seekTime: number, + startTime: number +) => { let tooltipText = ''; if (seekTime >= startTime) { tooltipText = getFormattedDateTime(new Date(seekTime)); diff --git a/packages/react-components/src/components/video-player/videoPlayer.tsx b/packages/react-components/src/components/video-player/videoPlayer.tsx index bb3283837..a1dd4aee5 100644 --- a/packages/react-components/src/components/video-player/videoPlayer.tsx +++ b/packages/react-components/src/components/video-player/videoPlayer.tsx @@ -13,8 +13,16 @@ import { videoJsOptions, videoOnEdgeMessage, } from './constants'; -import { liveButtonBackground, noVideoAvailableStyle, ondemandButtonBackground } from './styles'; -import { getFormattedDateTime, getNewSeekTime, getStartAndEndTimeForVideo } from './utils/dateTimeUtils'; +import { + liveButtonBackground, + noVideoAvailableStyle, + ondemandButtonBackground, +} from './styles'; +import { + getFormattedDateTime, + getNewSeekTime, + getStartAndEndTimeForVideo, +} from './utils/dateTimeUtils'; import { filterTimerangesForVideoOnEdge } from './utils/filterTimeRanges'; import { customVideoProgressBar } from './customVideoProgressBar'; import { getLiveToggleButton } from './utils/getLiveToggleButton'; @@ -23,7 +31,11 @@ import { getVideoProgressSeekTime, getVideoProgressTooltip, } from './utils/videoProgressUtils'; -import type { IVideoPlayerProps, VideoTimeRanges, VideoTimeRangesWithSource } from './types'; +import type { + IVideoPlayerProps, + VideoTimeRanges, + VideoTimeRangesWithSource, +} from './types'; import type { Viewport, HistoricalViewport } from '@iot-app-kit/core'; import type VideoJsPlayer from 'video.js/dist/types/player'; import type ControlBar from 'video.js/dist/types/component'; @@ -52,20 +64,28 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { const liveToggleButtonIdRef = useRef(''); const progressControlIdRef = useRef(''); const liveToggleButtonRef = useRef(); - const [timerangesWithSource, setTimerangesWithSource] = useState([]); - const [timerangesForVideoOnEdgeRaw, setTimerangesForVideoOnEdgeRaw] = useState([]); - const [timerangesForVideoOnEdge, setTimerangesForVideoOnEdge] = useState([]); + const [timerangesWithSource, setTimerangesWithSource] = + useState([]); + const [timerangesForVideoOnEdgeRaw, setTimerangesForVideoOnEdgeRaw] = + useState([]); + const [timerangesForVideoOnEdge, setTimerangesForVideoOnEdge] = + useState([]); const videoPlayerIdRef = useRef('iot-' + uuid()); - const initialPlaybackMode = 'start' in viewport && 'end' in viewport ? PLAYBACKMODE_ON_DEMAND : PLAYBACKMODE_LIVE; + const initialPlaybackMode = + 'start' in viewport && 'end' in viewport + ? PLAYBACKMODE_ON_DEMAND + : PLAYBACKMODE_LIVE; const playbackModeRef = useRef({}); playbackModeRef.current = initialPlaybackMode; const videoPlayerRef = useRef>(); - const videoErrorDialogRef = useRef['createModal']>>(); + const videoErrorDialogRef = + useRef['createModal']>>(); // Boolean flag to keep track if video is seeked by the user explicitly const [isVideoSeeking, setIsVideoSeeking] = useState(false); let triggerLiveVideoRequesttimeout: ReturnType | undefined; const uploadLiveVideoTimer = 120000; // 2 minutes timer in milli seconds to trigger live video upload every 2 minutes - const [currentOnDemandSource, setCurrentOnDemandSource] = useState(); + const [currentOnDemandSource, setCurrentOnDemandSource] = + useState(); let waitForLiveTimeout: ReturnType | undefined; const progressBarRef = useRef(); const seekBarRef = useRef(); @@ -90,7 +110,10 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { useEffect(() => { clearAllTimeouts(); - const updatedPlaybackMode = 'start' in viewport && 'end' in viewport ? PLAYBACKMODE_ON_DEMAND : PLAYBACKMODE_LIVE; + const updatedPlaybackMode = + 'start' in viewport && 'end' in viewport + ? PLAYBACKMODE_ON_DEMAND + : PLAYBACKMODE_LIVE; playbackModeRef.current = updatedPlaybackMode; setVideoPlayerStartAndEndTime(viewport, updatedPlaybackMode); }, [viewport]); @@ -101,18 +124,27 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { }, [startTime, endTime]); const createPlayer = () => { - return videojs.getPlayer(videoPlayerIdRef.current) as unknown as VideoJsPlayer & { controlBar: ControlBar }; + return videojs.getPlayer( + videoPlayerIdRef.current + ) as unknown as VideoJsPlayer & { controlBar: ControlBar }; }; const setPlayer = () => { if (!domRef.current) return; const videoElement = document.createElement('video-js'); domRef.current.appendChild(videoElement); - videoPlayerRef.current = videojs(videoElement, videoJsOptions, videoPlayerReady); + videoPlayerRef.current = videojs( + videoElement, + videoJsOptions, + videoPlayerReady + ); videoPlayerIdRef.current = videoPlayerRef.current.id(); }; - const setVideoPlayerStartAndEndTime = (viewport: Viewport, playbackMode: string) => { + const setVideoPlayerStartAndEndTime = ( + viewport: Viewport, + playbackMode: string + ) => { const { start, end } = getStartAndEndTimeForVideo(viewport, playbackMode); setStartTime(start); setEndTime(end); @@ -130,9 +162,13 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { if (liveButtonDom && playToggle) { playToggle?.el().after(liveButtonDom); const toggleButtonId = `tb-${videoPlayerIdRef.current}`; - liveButtonDom.innerHTML = DOMPurify.sanitize(getLiveToggleButton(toggleButtonId)); + liveButtonDom.innerHTML = DOMPurify.sanitize( + getLiveToggleButton(toggleButtonId) + ); liveToggleButtonIdRef.current = liveButton.id(); - liveToggleButtonRef.current = document.getElementById(toggleButtonId) as HTMLButtonElement; + liveToggleButtonRef.current = document.getElementById( + toggleButtonId + ) as HTMLButtonElement; liveButtonDom.onclick = () => { togglePlaybackMode(); }; @@ -152,14 +188,17 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { }; const clearAllTimeouts = () => { - if (triggerLiveVideoRequesttimeout != null) clearTimeout(triggerLiveVideoRequesttimeout); + if (triggerLiveVideoRequesttimeout != null) + clearTimeout(triggerLiveVideoRequesttimeout); if (waitForLiveTimeout != null) clearTimeout(waitForLiveTimeout); }; const togglePlaybackMode = () => { clearAllTimeouts(); const newPlaybackMode = - playbackModeRef.current === PLAYBACKMODE_ON_DEMAND ? PLAYBACKMODE_LIVE : PLAYBACKMODE_ON_DEMAND; + playbackModeRef.current === PLAYBACKMODE_ON_DEMAND + ? PLAYBACKMODE_LIVE + : PLAYBACKMODE_ON_DEMAND; // In case of video player initialized with live mode, set end time to now // This will play the video from the time it started till now when toggling to on-demand mode if ('duration' in viewport && newPlaybackMode === PLAYBACKMODE_ON_DEMAND) { @@ -172,7 +211,9 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { const setLiveToggleButtonStyle = () => { if (liveToggleButtonRef.current) { liveToggleButtonRef.current.style.backgroundColor = - playbackModeRef.current === PLAYBACKMODE_ON_DEMAND ? ondemandButtonBackground : liveButtonBackground; + playbackModeRef.current === PLAYBACKMODE_ON_DEMAND + ? ondemandButtonBackground + : liveButtonBackground; } }; @@ -180,19 +221,25 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { const currentPlayer = createPlayer(); if (currentPlayer && startTime && endTime) { // Remove the default progress bar if exists - const progressControlDefault = currentPlayer.controlBar.getChild('progressControl'); + const progressControlDefault = + currentPlayer.controlBar.getChild('progressControl'); if (progressControlDefault) { currentPlayer.controlBar.removeChild(progressControlDefault); } // Remove the current progress bar if exists - const progressControlPrev = currentPlayer.controlBar.getChildById(progressControlIdRef.current); + const progressControlPrev = currentPlayer.controlBar.getChildById( + progressControlIdRef.current + ); if (progressControlPrev) { currentPlayer.controlBar.removeChild(progressControlPrev); } // Add a new progress bar - const progressControl = currentPlayer.controlBar.addChild('progressControl'); + const progressControl = + currentPlayer.controlBar.addChild('progressControl'); const progressControlDom = progressControl.el() as HTMLElement; - const playbackModeToggleBtn = currentPlayer.controlBar.getChildById(liveToggleButtonIdRef.current); + const playbackModeToggleBtn = currentPlayer.controlBar.getChildById( + liveToggleButtonIdRef.current + ); const startTimestamp = startTime.getTime(); const endTimestamp = endTime.getTime(); if (progressControlDom && playbackModeToggleBtn) { @@ -212,10 +259,16 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { endTimestamp, }) ); - progressBarRef.current = document.getElementById(timelineId) as HTMLDivElement; + progressBarRef.current = document.getElementById( + timelineId + ) as HTMLDivElement; subscribeToSeekEvent(); - seekBarRef.current = document.getElementById(playProgressId) as HTMLElement; - currentTimeIndicatorRef.current = document.getElementById(currentTimeIndicatorId) as HTMLElement; + seekBarRef.current = document.getElementById( + playProgressId + ) as HTMLElement; + currentTimeIndicatorRef.current = document.getElementById( + currentTimeIndicatorId + ) as HTMLElement; displayCurrentTimeOnProgressIndicator(); displayTimeOnProgressBar(); } @@ -226,16 +279,22 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { const currentPlayer = createPlayer(); if (currentPlayer) { // Remove the custom progress bar if exists - const progressControlPrev = currentPlayer.controlBar.getChildById(progressControlIdRef.current); + const progressControlPrev = currentPlayer.controlBar.getChildById( + progressControlIdRef.current + ); if (progressControlPrev) { currentPlayer.controlBar.removeChild(progressControlPrev); } // Add the default progress bar if not exists - const progressControlDefault = currentPlayer.controlBar.getChild('progressControl'); + const progressControlDefault = + currentPlayer.controlBar.getChild('progressControl'); if (progressControlDefault === undefined) { - const progressControl = currentPlayer.controlBar.addChild('progressControl'); + const progressControl = + currentPlayer.controlBar.addChild('progressControl'); const progressControlDom = progressControl.el(); - const playbackModeToggleBtn = currentPlayer.controlBar.getChildById(liveToggleButtonIdRef.current); + const playbackModeToggleBtn = currentPlayer.controlBar.getChildById( + liveToggleButtonIdRef.current + ); if (playbackModeToggleBtn) { playbackModeToggleBtn.el().after(progressControlDom); } @@ -276,8 +335,16 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { const startTimeMs = startTime.getTime(); const endTimeMs = endTime.getTime(); const rect = progressBarRef.current.getBoundingClientRect(); - const seekTime = getNewSeekTime(ev.clientX, rect, startTimeMs, endTimeMs); - progressBarRef.current.title = getVideoProgressTooltip(seekTime, startTimeMs); + const seekTime = getNewSeekTime( + ev.clientX, + rect, + startTimeMs, + endTimeMs + ); + progressBarRef.current.title = getVideoProgressTooltip( + seekTime, + startTimeMs + ); } }; } @@ -300,14 +367,28 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { const endTimeMs = endTime.getTime(); if (progressBarRef.current) { const rect = progressBarRef.current.getBoundingClientRect(); - const seekTime = getNewSeekTime(newXPosition, rect, startTimeMs, endTimeMs); - seekBarRef.current.title = getVideoProgressTooltip(seekTime, startTimeMs); + const seekTime = getNewSeekTime( + newXPosition, + rect, + startTimeMs, + endTimeMs + ); + seekBarRef.current.title = getVideoProgressTooltip( + seekTime, + startTimeMs + ); } } }; const displayCurrentTime = () => { - if (currentOnDemandSource && videoPlayerRef.current && currentTimeIndicatorRef.current && startTime && endTime) { + if ( + currentOnDemandSource && + videoPlayerRef.current && + currentTimeIndicatorRef.current && + startTime && + endTime + ) { const startTimestamp = startTime.getTime(); const endTimestamp = endTime.getTime(); const percentage = getVideoProgressPercentage( @@ -316,8 +397,14 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { startTimestamp, endTimestamp ); - const seekTime = getVideoProgressSeekTime(percentage, startTimestamp, endTimestamp); - currentTimeIndicatorRef.current.textContent = getFormattedDateTime(new Date(seekTime)); + const seekTime = getVideoProgressSeekTime( + percentage, + startTimestamp, + endTimestamp + ); + currentTimeIndicatorRef.current.textContent = getFormattedDateTime( + new Date(seekTime) + ); } }; @@ -350,12 +437,19 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { }; const playNextVideo = () => { - if (playbackModeRef.current === PLAYBACKMODE_ON_DEMAND && timerangesWithSource.length > 0) { + if ( + playbackModeRef.current === PLAYBACKMODE_ON_DEMAND && + timerangesWithSource.length > 0 + ) { if (currentOnDemandSource) { // Get the next video information - const currentSourceIndex = timerangesWithSource.indexOf(currentOnDemandSource); + const currentSourceIndex = timerangesWithSource.indexOf( + currentOnDemandSource + ); if (currentSourceIndex < timerangesWithSource.length - 1) { - setCurrentSourceForOnDemand(timerangesWithSource[currentSourceIndex + 1]); + setCurrentSourceForOnDemand( + timerangesWithSource[currentSourceIndex + 1] + ); } else { // Reset the video source if all the videos are done playing setCurrentOnDemandSource(undefined); @@ -375,8 +469,14 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { const seekVideo = (newPositionPercentage: number) => { if (videoPlayerRef.current && seekBarRef.current && startTime && endTime) { - const seekTime = getVideoProgressSeekTime(newPositionPercentage, startTime.getTime(), endTime.getTime()); - const newSource = timerangesWithSource.find(({ start, end }) => seekTime >= start && seekTime < end); + const seekTime = getVideoProgressSeekTime( + newPositionPercentage, + startTime.getTime(), + endTime.getTime() + ); + const newSource = timerangesWithSource.find( + ({ start, end }) => seekTime >= start && seekTime < end + ); if (newSource) { setCurrentSourceForOnDemand(newSource); updateSeekbarPosition(newPositionPercentage); @@ -385,17 +485,23 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { } else { // No video available videoPlayerRef.current.pause(); - const nextSource = timerangesWithSource.find(({ start }) => seekTime < start); + const nextSource = timerangesWithSource.find( + ({ start }) => seekTime < start + ); setIsVideoSeeking(false); updateSeekbarPosition(newPositionPercentage); displayCurrentTime(); let noVideoMessage = noVideoAvailableMessage; - const videoOnEdge = timerangesForVideoOnEdge?.find(({ start, end }) => seekTime >= start && seekTime < end); + const videoOnEdge = timerangesForVideoOnEdge?.find( + ({ start, end }) => seekTime >= start && seekTime < end + ); if (videoOnEdge) { noVideoMessage = videoOnEdgeMessage; } const noVideo = document.createElement('div'); - noVideo.innerHTML = DOMPurify.sanitize(`

    ${noVideoMessage}

    `); + noVideo.innerHTML = DOMPurify.sanitize( + `

    ${noVideoMessage}

    ` + ); const noVideoModal = videoPlayerRef.current.createModal(noVideo, null); // When the modal closes, resume playback. noVideoModal.on('modalclose', () => { @@ -412,7 +518,11 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { } }; - const setCurrentSourceForOnDemand = (newSource: { start: number; end: number; src: string }) => { + const setCurrentSourceForOnDemand = (newSource: { + start: number; + end: number; + src: string; + }) => { if (videoPlayerRef.current) { if (currentOnDemandSource !== newSource) { setCurrentOnDemandSource(newSource); @@ -436,7 +546,10 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { if (triggerLiveVideoRequesttimeout != null) { clearTimeout(triggerLiveVideoRequesttimeout); } - triggerLiveVideoRequesttimeout = setTimeout(triggerLiveVideoUpload, uploadLiveVideoTimer); + triggerLiveVideoRequesttimeout = setTimeout( + triggerLiveVideoUpload, + uploadLiveVideoTimer + ); }; const updateVideoSource = () => { @@ -456,8 +569,13 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { videoErrorDialogRef.current.close(); } const videoErrorEl = document.createElement('div') as HTMLElement; - videoErrorEl.innerHTML = DOMPurify.sanitize(`

    ${errorMessage}

    `); - videoErrorDialogRef.current = videoPlayerRef.current.createModal(videoErrorEl, null); + videoErrorEl.innerHTML = DOMPurify.sanitize( + `

    ${errorMessage}

    ` + ); + videoErrorDialogRef.current = videoPlayerRef.current.createModal( + videoErrorEl, + null + ); } }; @@ -495,14 +613,29 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { const setVideoPlayerForOnDemandMode = async () => { try { - const kvsStreamSrc = await videoData.getKvsStreamSrc(PLAYBACKMODE_ON_DEMAND, startTime, endTime); + const kvsStreamSrc = await videoData.getKvsStreamSrc( + PLAYBACKMODE_ON_DEMAND, + startTime, + endTime + ); if (kvsStreamSrc && videoPlayerRef.current) { await getAvailableTimeRangesAndSetVideoSource(); if (currentOnDemandSource) { // EdgeVideo Component Type - closeErrorDialogAndSetVideoSource((currentOnDemandSource as { start: number; end: number; src: string }).src); + closeErrorDialogAndSetVideoSource( + ( + currentOnDemandSource as { + start: number; + end: number; + src: string; + } + ).src + ); setTimerangesForVideoOnEdge( - filterTimerangesForVideoOnEdge(timerangesForVideoOnEdgeRaw, timerangesWithSource) + filterTimerangesForVideoOnEdge( + timerangesForVideoOnEdgeRaw, + timerangesWithSource + ) ); setVideoPlayerCustomeProgressBar(); } else { @@ -518,7 +651,10 @@ export const VideoPlayer = (props: IVideoPlayerProps) => { const getAvailableTimeRangesAndSetVideoSource = async () => { if (endTime) { - const timeRanges = await videoData.getAvailableTimeRanges(startTime, endTime); + const timeRanges = await videoData.getAvailableTimeRanges( + startTime, + endTime + ); if (timeRanges) { setTimerangesWithSource(timeRanges[0]); setTimerangesForVideoOnEdgeRaw(timeRanges[1]); diff --git a/packages/react-components/src/echarts/extensions/yAxisSync/updateYAxis.ts b/packages/react-components/src/echarts/extensions/yAxisSync/updateYAxis.ts index accf27e65..5c1cf8841 100644 --- a/packages/react-components/src/echarts/extensions/yAxisSync/updateYAxis.ts +++ b/packages/react-components/src/echarts/extensions/yAxisSync/updateYAxis.ts @@ -13,7 +13,12 @@ export const handleSetYAxis = (model: LineSeriesModel) => { const id = `${option.id}`; // echart option id can also be a number // Cannot determine yAxis unless the coordinateSystem is cartesion2d - if (coordinateSystem.type !== 'cartesian2d' || id == null || appKitChartId == null) return; + if ( + coordinateSystem.type !== 'cartesian2d' || + id == null || + appKitChartId == null + ) + return; const storeState = useDataStore.getState(); const stores = storeState.chartStores; @@ -51,7 +56,10 @@ export const handleSetYAxis = (model: LineSeriesModel) => { state.setYMin(id, newMin); } } else { - if (!isEqual(undefined, state.yMaxes[id]) || !isEqual(undefined, state.yMins[id])) { + if ( + !isEqual(undefined, state.yMaxes[id]) || + !isEqual(undefined, state.yMins[id]) + ) { store.getState().clearYAxis(id); } } diff --git a/packages/react-components/src/echarts/extensions/yAxisSync/yAxisPredicates.ts b/packages/react-components/src/echarts/extensions/yAxisSync/yAxisPredicates.ts index 00ddb17ea..709377805 100644 --- a/packages/react-components/src/echarts/extensions/yAxisSync/yAxisPredicates.ts +++ b/packages/react-components/src/echarts/extensions/yAxisSync/yAxisPredicates.ts @@ -1,6 +1,8 @@ import { LineSeriesOption } from 'echarts/types/src/chart/line/LineSeries'; // the first y-axis is the default chart y-axis -export const hasCustomYAxis = (option: LineSeriesOption) => option.yAxisIndex && option.yAxisIndex > 0; +export const hasCustomYAxis = (option: LineSeriesOption) => + option.yAxisIndex && option.yAxisIndex > 0; -export const handlesYAxis = (type: string) => ['series.line', 'series.scatter'].includes(type); +export const handlesYAxis = (type: string) => + ['series.line', 'series.scatter'].includes(type); diff --git a/packages/react-components/src/echarts/extensions/yAxisSync/yAxisSync.ts b/packages/react-components/src/echarts/extensions/yAxisSync/yAxisSync.ts index bdc10fe48..453148959 100644 --- a/packages/react-components/src/echarts/extensions/yAxisSync/yAxisSync.ts +++ b/packages/react-components/src/echarts/extensions/yAxisSync/yAxisSync.ts @@ -6,16 +6,21 @@ import { handleSetYAxis } from './updateYAxis'; // Echarts core use type does not map correctly to the echarts extension type so exporting as any // eslint-disable-next-line export const yAxisSyncExtension: any = (registers: EChartsExtensionInstallRegisters) => { - registers.registerUpdateLifecycle('series:afterupdate', (_ecModel, _api, params) => { - (params.updatedSeries ?? []) - .filter((series) => handlesYAxis(series.type)) - /** - * Issue with the exposed types in echarts. - * SeriesModel> which is used in EChartsExtensionInstaller - * is incompatible with LineSeriesModel | ScatterSeriesModel which is exported by echarts - * The type used by the extension is not accessible, so casting to LineSeriesModel - * as it uses the correct type for the coordinateSystem. - */ - .forEach((series) => handleSetYAxis(series as unknown as LineSeriesModel)); - }); + registers.registerUpdateLifecycle( + 'series:afterupdate', + (_ecModel, _api, params) => { + (params.updatedSeries ?? []) + .filter((series) => handlesYAxis(series.type)) + /** + * Issue with the exposed types in echarts. + * SeriesModel> which is used in EChartsExtensionInstaller + * is incompatible with LineSeriesModel | ScatterSeriesModel which is exported by echarts + * The type used by the extension is not accessible, so casting to LineSeriesModel + * as it uses the correct type for the coordinateSystem. + */ + .forEach((series) => + handleSetYAxis(series as unknown as LineSeriesModel) + ); + } + ); }; diff --git a/packages/react-components/src/hooks/useColoredDataStreams/useColoredDataStreams.spec.ts b/packages/react-components/src/hooks/useColoredDataStreams/useColoredDataStreams.spec.ts index 3f95a7d22..158ebef73 100644 --- a/packages/react-components/src/hooks/useColoredDataStreams/useColoredDataStreams.spec.ts +++ b/packages/react-components/src/hooks/useColoredDataStreams/useColoredDataStreams.spec.ts @@ -3,12 +3,37 @@ import { useColoredDataStreams } from './useColoredDataStreams'; import { DataStream } from '@iot-app-kit/core'; import { Colorizer } from '@iot-app-kit/core-util'; -const PALETTE = ['red', 'white', 'blue', 'cyan', 'mauve', 'orange', 'lime', 'pink', 'gold']; +const PALETTE = [ + 'red', + 'white', + 'blue', + 'cyan', + 'mauve', + 'orange', + 'lime', + 'pink', + 'gold', +]; it('Applies a default color to each stream', () => { - const DATA_STREAM_1: DataStream = { id: 'abc-1', data: [], resolution: 0, name: 'my-name' }; - const DATA_STREAM_2: DataStream = { id: 'abc-2', data: [], resolution: 0, name: 'my-name' }; - const DATA_STREAM_3: DataStream = { id: 'abc-3', data: [], resolution: 0, name: 'my-name' }; + const DATA_STREAM_1: DataStream = { + id: 'abc-1', + data: [], + resolution: 0, + name: 'my-name', + }; + const DATA_STREAM_2: DataStream = { + id: 'abc-2', + data: [], + resolution: 0, + name: 'my-name', + }; + const DATA_STREAM_3: DataStream = { + id: 'abc-3', + data: [], + resolution: 0, + name: 'my-name', + }; const colorer = Colorizer(PALETTE); const { result: { current: dataStreams }, @@ -29,10 +54,30 @@ it('Applies a default color to each stream', () => { }); it('preserves the same default colors between renders', () => { - const DATA_STREAM_1: DataStream = { id: 'abc-1', data: [], resolution: 0, name: 'my-name' }; - const DATA_STREAM_2: DataStream = { id: 'abc-2', data: [], resolution: 0, name: 'my-name' }; - const DATA_STREAM_3: DataStream = { id: 'abc-3', data: [], resolution: 0, name: 'my-name' }; - const DATA_STREAM_4: DataStream = { id: 'abc-4', data: [], resolution: 0, name: 'my-name' }; + const DATA_STREAM_1: DataStream = { + id: 'abc-1', + data: [], + resolution: 0, + name: 'my-name', + }; + const DATA_STREAM_2: DataStream = { + id: 'abc-2', + data: [], + resolution: 0, + name: 'my-name', + }; + const DATA_STREAM_3: DataStream = { + id: 'abc-3', + data: [], + resolution: 0, + name: 'my-name', + }; + const DATA_STREAM_4: DataStream = { + id: 'abc-4', + data: [], + resolution: 0, + name: 'my-name', + }; const colorer = Colorizer(PALETTE); const { result, rerender } = renderHook( (dataStreams) => diff --git a/packages/react-components/src/hooks/useColoredDataStreams/useColoredDataStreams.ts b/packages/react-components/src/hooks/useColoredDataStreams/useColoredDataStreams.ts index c7ff8ac7e..9295147f6 100644 --- a/packages/react-components/src/hooks/useColoredDataStreams/useColoredDataStreams.ts +++ b/packages/react-components/src/hooks/useColoredDataStreams/useColoredDataStreams.ts @@ -6,7 +6,8 @@ import difference from 'lodash.difference'; const hasColor = (stream: DataStream, styles: StyleSettingsMap): boolean => { const streamHasColor = stream.color != null; - const associatedStyles = stream.refId != null ? styles[stream.refId] ?? {} : {}; + const associatedStyles = + stream.refId != null ? styles[stream.refId] ?? {} : {}; const hasAssociatedColor = associatedStyles.color != null; return streamHasColor || hasAssociatedColor; diff --git a/packages/react-components/src/hooks/useECharts/useGroupableEchart.ts b/packages/react-components/src/hooks/useECharts/useGroupableEchart.ts index 9227f68ef..da4780193 100644 --- a/packages/react-components/src/hooks/useECharts/useGroupableEchart.ts +++ b/packages/react-components/src/hooks/useECharts/useGroupableEchart.ts @@ -8,7 +8,10 @@ import { connect, disconnect, type ECharts } from 'echarts'; * @param groupId - set a group on an echarts instance * @returns void */ -export const useGroupableEChart = (chartRef: React.MutableRefObject, groupId?: string) => { +export const useGroupableEChart = ( + chartRef: React.MutableRefObject, + groupId?: string +) => { useEffect(() => { if (groupId && chartRef.current) { chartRef.current.group = groupId; diff --git a/packages/react-components/src/hooks/useECharts/useLoadableEChart.ts b/packages/react-components/src/hooks/useECharts/useLoadableEChart.ts index 8c9de6201..60e574057 100644 --- a/packages/react-components/src/hooks/useECharts/useLoadableEChart.ts +++ b/packages/react-components/src/hooks/useECharts/useLoadableEChart.ts @@ -8,7 +8,10 @@ import type { ECharts } from 'echarts'; * @param loading - whether or not to set the loading animation on an echart * @returns void */ -export const useLoadableEChart = (chartRef: React.MutableRefObject, loading?: boolean) => { +export const useLoadableEChart = ( + chartRef: React.MutableRefObject, + loading?: boolean +) => { useEffect(() => { const chart = chartRef.current; loading === true ? chart?.showLoading() : chart?.hideLoading(); diff --git a/packages/react-components/src/hooks/useECharts/useResizeableEChart.ts b/packages/react-components/src/hooks/useECharts/useResizeableEChart.ts index a518dbea2..e262e7cad 100644 --- a/packages/react-components/src/hooks/useECharts/useResizeableEChart.ts +++ b/packages/react-components/src/hooks/useECharts/useResizeableEChart.ts @@ -1,4 +1,10 @@ -import { useEffect, useState, SyntheticEvent, useMemo, MutableRefObject } from 'react'; +import { + useEffect, + useState, + SyntheticEvent, + useMemo, + MutableRefObject, +} from 'react'; import type { ECharts } from 'echarts'; import { CHART_RESIZE_INITIAL_FACTOR, @@ -8,7 +14,11 @@ import { import { ResizeCallbackData } from 'react-resizable'; import { useMeasure } from 'react-use'; -const getChartWidth = (width: number, staticWidth: number, rightLegend?: boolean) => { +const getChartWidth = ( + width: number, + staticWidth: number, + rightLegend?: boolean +) => { if (!rightLegend) { return width - staticWidth; } @@ -44,19 +54,28 @@ export const useResizeableEChart = ( isBottomAligned?: boolean ) => { const { width, height } = size; - const [leftLegendRef, { width: leftLegendWidth }] = useMeasure(); - const [chartWidth, setChartWidth] = useState(getChartWidth(width, leftLegendWidth)); + const [leftLegendRef, { width: leftLegendWidth }] = + useMeasure(); + const [chartWidth, setChartWidth] = useState( + getChartWidth(width, leftLegendWidth) + ); const [chartHeight, setChartHeight] = useState(getChartHeight(height)); - const rightLegendWidth = rightLegend ? width - leftLegendWidth - chartWidth : 0; + const rightLegendWidth = rightLegend + ? width - leftLegendWidth - chartWidth + : 0; const rightLegendHeight = rightLegend ? height - chartHeight : 0; const onResize = (_event: SyntheticEvent, data: ResizeCallbackData) => { _event.stopPropagation(); if (!rightLegend) { - isBottomAligned ? setChartHeight(height) : setChartWidth(width - leftLegendWidth); + isBottomAligned + ? setChartHeight(height) + : setChartWidth(width - leftLegendWidth); } else { - isBottomAligned ? setChartHeight(data.size.height) : setChartWidth(data.size.width); + isBottomAligned + ? setChartHeight(data.size.height) + : setChartWidth(data.size.width); } }; @@ -70,7 +89,10 @@ export const useResizeableEChart = ( useEffect(() => { const chart = chartRef.current; - chart?.resize({ width: isBottomAligned ? width : chartWidth, height: isBottomAligned ? chartHeight : height }); + chart?.resize({ + width: isBottomAligned ? width : chartWidth, + height: isBottomAligned ? chartHeight : height, + }); }, [chartRef, chartHeight, chartWidth, isBottomAligned, height, width]); const minConstraints: [number, number] = useMemo(() => { diff --git a/packages/react-components/src/hooks/useTimeSeriesData/providerStore.ts b/packages/react-components/src/hooks/useTimeSeriesData/providerStore.ts index 8079a9ffe..825b059cd 100644 --- a/packages/react-components/src/hooks/useTimeSeriesData/providerStore.ts +++ b/packages/react-components/src/hooks/useTimeSeriesData/providerStore.ts @@ -4,12 +4,17 @@ type ProviderMap = { [key in string]: ProviderWithViewport }; const providerStore = () => { const providerMap: ProviderMap = {}; - const set = (id: keyof ProviderMap, provider: ProviderMap[keyof ProviderMap]) => { + const set = ( + id: keyof ProviderMap, + provider: ProviderMap[keyof ProviderMap] + ) => { providerMap[id] = provider; return provider; }; - const get = (id: keyof ProviderMap): ProviderMap[keyof ProviderMap] | undefined => providerMap[id]; + const get = ( + id: keyof ProviderMap + ): ProviderMap[keyof ProviderMap] | undefined => providerMap[id]; const remove = (id: keyof ProviderMap) => { delete providerMap[id]; diff --git a/packages/react-components/src/hooks/useTimeSeriesData/useTimeSeriesData.spec.ts b/packages/react-components/src/hooks/useTimeSeriesData/useTimeSeriesData.spec.ts index 1c79469a1..0a91afcd2 100644 --- a/packages/react-components/src/hooks/useTimeSeriesData/useTimeSeriesData.spec.ts +++ b/packages/react-components/src/hooks/useTimeSeriesData/useTimeSeriesData.spec.ts @@ -18,7 +18,9 @@ it('returns no time series data when query returns no time series data', () => { }); it('provides time series data returned from query', () => { - const QUERY_RESPONSE: TimeSeriesData[] = [{ dataStreams: [], viewport: { duration: '5m' }, thresholds: [] }]; + const QUERY_RESPONSE: TimeSeriesData[] = [ + { dataStreams: [], viewport: { duration: '5m' }, thresholds: [] }, + ]; const queries = [mockTimeSeriesDataQuery(QUERY_RESPONSE)]; const viewport = { duration: '5m' }; @@ -35,8 +37,16 @@ it('provides time series data returned from query', () => { }); it('returns time series data when the number of time series queries changes', () => { - const THRESHOLD_1: Threshold = { comparisonOperator: 'GT', value: 10, color: 'black' }; - const THRESHOLD_2: Threshold = { comparisonOperator: 'GT', value: 100, color: 'black' }; + const THRESHOLD_1: Threshold = { + comparisonOperator: 'GT', + value: 10, + color: 'black', + }; + const THRESHOLD_2: Threshold = { + comparisonOperator: 'GT', + value: 100, + color: 'black', + }; const DATA_STREAM_1: DataStream = { refId: 'red', @@ -55,17 +65,31 @@ it('returns time series data when the number of time series queries changes', () color: 'black', }; - const QUERY_1 = { dataStreams: [DATA_STREAM_1], viewport: { duration: '5m' }, thresholds: [THRESHOLD_1] }; - const QUERY_2 = { dataStreams: [DATA_STREAM_2], viewport: { duration: '5m' }, thresholds: [THRESHOLD_2] }; + const QUERY_1 = { + dataStreams: [DATA_STREAM_1], + viewport: { duration: '5m' }, + thresholds: [THRESHOLD_1], + }; + const QUERY_2 = { + dataStreams: [DATA_STREAM_2], + viewport: { duration: '5m' }, + thresholds: [THRESHOLD_2], + }; const INITIAL_QUERIES = [mockTimeSeriesDataQuery([QUERY_1])]; - const UPDATED_QUERIES = [mockTimeSeriesDataQuery([QUERY_1]), mockTimeSeriesDataQuery([QUERY_2])]; + const UPDATED_QUERIES = [ + mockTimeSeriesDataQuery([QUERY_1]), + mockTimeSeriesDataQuery([QUERY_2]), + ]; const VIEWPORT = { duration: '5m' }; - const { result, rerender } = renderHook((queries) => useTimeSeriesData({ queries, viewport: VIEWPORT }), { - initialProps: INITIAL_QUERIES, - }); + const { result, rerender } = renderHook( + (queries) => useTimeSeriesData({ queries, viewport: VIEWPORT }), + { + initialProps: INITIAL_QUERIES, + } + ); expect(result.current).toEqual({ dataStreams: [DATA_STREAM_1], @@ -82,8 +106,16 @@ it('returns time series data when the number of time series queries changes', () // TODO: Fix this test it.skip('returns time series data when the number of queries within one time series query changes', () => { - const THRESHOLD_1: Threshold = { comparisonOperator: 'GT', value: 10, color: 'black' }; - const THRESHOLD_2: Threshold = { comparisonOperator: 'GT', value: 100, color: 'black' }; + const THRESHOLD_1: Threshold = { + comparisonOperator: 'GT', + value: 10, + color: 'black', + }; + const THRESHOLD_2: Threshold = { + comparisonOperator: 'GT', + value: 100, + color: 'black', + }; const DATA_STREAM_1: DataStream = { refId: 'red', @@ -102,17 +134,28 @@ it.skip('returns time series data when the number of queries within one time ser color: 'black', }; - const QUERY_1 = { dataStreams: [DATA_STREAM_1], viewport: { duration: '5m' }, thresholds: [THRESHOLD_1] }; - const QUERY_2 = { dataStreams: [DATA_STREAM_2], viewport: { duration: '5m' }, thresholds: [THRESHOLD_2] }; + const QUERY_1 = { + dataStreams: [DATA_STREAM_1], + viewport: { duration: '5m' }, + thresholds: [THRESHOLD_1], + }; + const QUERY_2 = { + dataStreams: [DATA_STREAM_2], + viewport: { duration: '5m' }, + thresholds: [THRESHOLD_2], + }; const INITIAL_QUERIES = [mockTimeSeriesDataQuery([QUERY_1])]; const UPDATED_QUERIES = [mockTimeSeriesDataQuery([QUERY_1, QUERY_2])]; const VIEWPORT = { duration: '5m' }; - const { result, rerender } = renderHook((queries) => useTimeSeriesData({ queries, viewport: VIEWPORT }), { - initialProps: INITIAL_QUERIES, - }); + const { result, rerender } = renderHook( + (queries) => useTimeSeriesData({ queries, viewport: VIEWPORT }), + { + initialProps: INITIAL_QUERIES, + } + ); expect(result.current).toEqual({ dataStreams: [DATA_STREAM_1], @@ -128,7 +171,13 @@ it.skip('returns time series data when the number of queries within one time ser }); it('binds style settings color to the data stream color', () => { - const DATA_STREAM: DataStream = { refId: 'red', id: 'abc', data: [], resolution: 0, name: 'my-name' }; + const DATA_STREAM: DataStream = { + refId: 'red', + id: 'abc', + data: [], + resolution: 0, + name: 'my-name', + }; const TIME_SERIES_DATA: TimeSeriesData = { dataStreams: [DATA_STREAM], viewport: { duration: '5m' }, @@ -151,8 +200,16 @@ it('binds style settings color to the data stream color', () => { }); it('combines multiple time series data results into a single time series data', () => { - const THRESHOLD_1: Threshold = { comparisonOperator: 'GT', value: 10, color: 'black' }; - const THRESHOLD_2: Threshold = { comparisonOperator: 'GT', value: 100, color: 'black' }; + const THRESHOLD_1: Threshold = { + comparisonOperator: 'GT', + value: 10, + color: 'black', + }; + const THRESHOLD_2: Threshold = { + comparisonOperator: 'GT', + value: 100, + color: 'black', + }; const DATA_STREAM_1: DataStream = { refId: 'red', id: 'abc-1', @@ -171,8 +228,16 @@ it('combines multiple time series data results into a single time series data', }; const QUERY_RESPONSE: TimeSeriesData[] = [ - { dataStreams: [DATA_STREAM_1], viewport: { duration: '5m' }, thresholds: [THRESHOLD_1] }, - { dataStreams: [DATA_STREAM_2], viewport: { duration: '5m' }, thresholds: [THRESHOLD_2] }, + { + dataStreams: [DATA_STREAM_1], + viewport: { duration: '5m' }, + thresholds: [THRESHOLD_1], + }, + { + dataStreams: [DATA_STREAM_2], + viewport: { duration: '5m' }, + thresholds: [THRESHOLD_2], + }, ]; const queries = [mockTimeSeriesDataQuery(QUERY_RESPONSE)]; const viewport = { duration: '5m' }; @@ -193,17 +258,40 @@ it('combines multiple time series data results into a single time series data', }); it('returns data streams from multiple queries', () => { - const DATA_STREAM_1: DataStream = { id: 'abc-1', data: [], resolution: 0, name: 'my-name', color: 'black' }; - const DATA_STREAM_2: DataStream = { id: 'abc-2', data: [], resolution: 0, name: 'my-name-2', color: 'black' }; + const DATA_STREAM_1: DataStream = { + id: 'abc-1', + data: [], + resolution: 0, + name: 'my-name', + color: 'black', + }; + const DATA_STREAM_2: DataStream = { + id: 'abc-2', + data: [], + resolution: 0, + name: 'my-name-2', + color: 'black', + }; const QUERY_RESPONSE_1: TimeSeriesData[] = [ - { dataStreams: [DATA_STREAM_1], viewport: { duration: '5m' }, thresholds: [] }, + { + dataStreams: [DATA_STREAM_1], + viewport: { duration: '5m' }, + thresholds: [], + }, ]; const QUERY_RESPONSE_2: TimeSeriesData[] = [ - { dataStreams: [DATA_STREAM_2], viewport: { duration: '5m' }, thresholds: [] }, + { + dataStreams: [DATA_STREAM_2], + viewport: { duration: '5m' }, + thresholds: [], + }, ]; - const queries = [mockTimeSeriesDataQuery(QUERY_RESPONSE_1), mockTimeSeriesDataQuery(QUERY_RESPONSE_2)]; + const queries = [ + mockTimeSeriesDataQuery(QUERY_RESPONSE_1), + mockTimeSeriesDataQuery(QUERY_RESPONSE_2), + ]; const { result: { @@ -222,14 +310,22 @@ it('returns data streams from multiple queries', () => { it('providers updated viewport to query', () => { let viewport = { duration: '5m' }; const updateViewport = jest.fn(); - const DATA_STREAM: DataStream = { refId: 'red', id: 'abc', data: [], resolution: 0, name: 'my-name' }; + const DATA_STREAM: DataStream = { + refId: 'red', + id: 'abc', + data: [], + resolution: 0, + name: 'my-name', + }; const TIME_SERIES_DATA: TimeSeriesData = { dataStreams: [DATA_STREAM], viewport: { duration: '5m' }, thresholds: [], }; - const queries = [mockTimeSeriesDataQuery([TIME_SERIES_DATA], { updateViewport })]; + const queries = [ + mockTimeSeriesDataQuery([TIME_SERIES_DATA], { updateViewport }), + ]; const color = 'red'; const { rerender } = renderHook(() => @@ -264,7 +360,11 @@ it('does not attempt to re-create the subscription when provided a new reference }); it('returns thresholds from time series data provider', () => { - const THRESHOLD: Threshold = { comparisonOperator: 'GT', value: 10, color: 'black' }; + const THRESHOLD: Threshold = { + comparisonOperator: 'GT', + value: 10, + color: 'black', + }; const TIME_SERIES_DATA: TimeSeriesData = { dataStreams: [], viewport: { duration: '5m' }, @@ -284,7 +384,13 @@ it('returns thresholds from time series data provider', () => { }); it('update datastream styles when styles change', () => { - const DATA_STREAM: DataStream = { refId: 'ref-id', id: 'abc', data: [], resolution: 0, name: 'my-name' }; + const DATA_STREAM: DataStream = { + refId: 'ref-id', + id: 'abc', + data: [], + resolution: 0, + name: 'my-name', + }; const TIME_SERIES_DATA: TimeSeriesData = { dataStreams: [DATA_STREAM], viewport: { duration: '5m' }, @@ -303,5 +409,7 @@ it('update datastream styles when styles change', () => { styles = { 'ref-id': { color: 'green' } }; rerender(); - expect(result.current.dataStreams[0]).toEqual(expect.objectContaining({ color: 'green' })); + expect(result.current.dataStreams[0]).toEqual( + expect.objectContaining({ color: 'green' }) + ); }); diff --git a/packages/react-components/src/hooks/useTimeSeriesData/useTimeSeriesData.ts b/packages/react-components/src/hooks/useTimeSeriesData/useTimeSeriesData.ts index 559a0cbee..68bd637c8 100644 --- a/packages/react-components/src/hooks/useTimeSeriesData/useTimeSeriesData.ts +++ b/packages/react-components/src/hooks/useTimeSeriesData/useTimeSeriesData.ts @@ -17,7 +17,10 @@ import type { } from '@iot-app-kit/core'; import { ProviderStore } from './providerStore'; import { useColoredDataStreams } from '../useColoredDataStreams'; -import { SiteWiseAssetQuery, SiteWisePropertyAliasQuery } from '@iot-app-kit/source-iotsitewise'; +import { + SiteWiseAssetQuery, + SiteWisePropertyAliasQuery, +} from '@iot-app-kit/source-iotsitewise'; const DEFAULT_SETTINGS: TimeSeriesDataRequestSettings = { resolution: '0', @@ -48,7 +51,9 @@ export const useTimeSeriesData = ({ settings?: TimeSeriesDataRequestSettings; styles?: StyleSettingsMap; }): { dataStreams: DataStream[]; thresholds: Threshold[] } => { - const [timeSeriesData, setTimeSeriesData] = useState(undefined); + const [timeSeriesData, setTimeSeriesData] = useState< + TimeSeriesData | undefined + >(undefined); const { viewport: injectedViewport } = useViewport(); const viewport = passedInViewport || injectedViewport || DEFAULT_VIEWPORT; @@ -72,17 +77,21 @@ export const useTimeSeriesData = ({ query: { assets: query.assets?.map(({ assetId, properties }) => ({ assetId, - properties: properties.map(({ propertyId, aggregationType, resolution }) => ({ - propertyId, + properties: properties.map( + ({ propertyId, aggregationType, resolution }) => ({ + propertyId, + aggregationType, + resolution, + }) + ), + })), + properties: query.properties?.map( + ({ propertyAlias, resolution, aggregationType }) => ({ + propertyAlias, aggregationType, resolution, - })), - })), - properties: query.properties?.map(({ propertyAlias, resolution, aggregationType }) => ({ - propertyAlias, - aggregationType, - resolution, - })), + }) + ), }, })); @@ -105,7 +114,10 @@ export const useTimeSeriesData = ({ provider.subscribe({ next: (timeSeriesDataCollection: TimeSeriesData[]) => { - const timeSeriesData = combineTimeSeriesData(timeSeriesDataCollection, viewport); + const timeSeriesData = combineTimeSeriesData( + timeSeriesDataCollection, + viewport + ); setTimeSeriesData({ ...timeSeriesData, @@ -122,7 +134,8 @@ export const useTimeSeriesData = ({ useEffect(() => { if (prevViewportRef.current != null) { - const provider = providerIdRef.current && ProviderStore.get(providerIdRef.current); + const provider = + providerIdRef.current && ProviderStore.get(providerIdRef.current); if (provider) { provider.updateViewport(viewport); } @@ -140,5 +153,8 @@ export const useTimeSeriesData = ({ styleSettings: styles, }); - return { dataStreams: styledDataStreams, thresholds: timeSeriesData?.thresholds || [] }; + return { + dataStreams: styledDataStreams, + thresholds: timeSeriesData?.thresholds || [], + }; }; diff --git a/packages/react-components/src/hooks/utils/bindStylesToDataStreams.spec.ts b/packages/react-components/src/hooks/utils/bindStylesToDataStreams.spec.ts index 605dbd1c0..53a765236 100644 --- a/packages/react-components/src/hooks/utils/bindStylesToDataStreams.spec.ts +++ b/packages/react-components/src/hooks/utils/bindStylesToDataStreams.spec.ts @@ -55,7 +55,10 @@ it('associates styles to corresponding data stream for multiple data streams', ( { ...DATA_STREAM, refId: 'someStyle' }, { ...DATA_STREAM_2, refId: 'someStyle2' }, ], - styleSettings: { someStyle: { color: 'red' }, someStyle2: { color: 'blue' } }, + styleSettings: { + someStyle: { color: 'red' }, + someStyle2: { color: 'blue' }, + }, }) ).toEqual([ { ...DATA_STREAM, refId: 'someStyle', color: 'red' }, diff --git a/packages/react-components/src/hooks/utils/combineTimeSeriesData.ts b/packages/react-components/src/hooks/utils/combineTimeSeriesData.ts index ad7f7db9a..6b7dc9936 100644 --- a/packages/react-components/src/hooks/utils/combineTimeSeriesData.ts +++ b/packages/react-components/src/hooks/utils/combineTimeSeriesData.ts @@ -1,6 +1,9 @@ import type { TimeSeriesData, Viewport } from '@iot-app-kit/core'; -export const combineTimeSeriesData = (timeSeresDataResults: TimeSeriesData[], viewport: Viewport): TimeSeriesData => +export const combineTimeSeriesData = ( + timeSeresDataResults: TimeSeriesData[], + viewport: Viewport +): TimeSeriesData => timeSeresDataResults.reduce( (timeSeriesData, newTimeSeriesData) => { const { dataStreams, viewport, thresholds } = newTimeSeriesData; diff --git a/packages/react-components/src/hooks/utils/getAlarmStreamThresholds.spec.ts b/packages/react-components/src/hooks/utils/getAlarmStreamThresholds.spec.ts index c1244120d..fd131ec49 100644 --- a/packages/react-components/src/hooks/utils/getAlarmStreamThresholds.spec.ts +++ b/packages/react-components/src/hooks/utils/getAlarmStreamThresholds.spec.ts @@ -100,8 +100,12 @@ it('should return alarm stream annotations', () => { const { thresholds, dataStreams } = TIME_SERIES_DATA_WITH_ALARMS; const ALARM_STREAM_ANNOTATIONS = thresholds.filter((yAnnotation) => { - return (yAnnotation as unknown as Threshold)!.dataStreamIds!.includes('alarm-asset-id---alarm-state-property-id'); + return (yAnnotation as unknown as Threshold)!.dataStreamIds!.includes( + 'alarm-asset-id---alarm-state-property-id' + ); }); - expect(getAlarmStreamThresholds({ thresholds, dataStreams })).toEqual(ALARM_STREAM_ANNOTATIONS); + expect(getAlarmStreamThresholds({ thresholds, dataStreams })).toEqual( + ALARM_STREAM_ANNOTATIONS + ); }); diff --git a/packages/react-components/src/hooks/utils/getAlarmStreamThresholds.ts b/packages/react-components/src/hooks/utils/getAlarmStreamThresholds.ts index 5013a9514..4985e2672 100644 --- a/packages/react-components/src/hooks/utils/getAlarmStreamThresholds.ts +++ b/packages/react-components/src/hooks/utils/getAlarmStreamThresholds.ts @@ -11,7 +11,10 @@ export const getAlarmStreamThresholds = ({ return ( 'dataStreamIds' in yAnnotation && yAnnotation.dataStreamIds?.some((dataStreamId) => - dataStreams.some((dataStream) => dataStream.streamType === 'ALARM' && dataStreamId === dataStream.id) + dataStreams.some( + (dataStream) => + dataStream.streamType === 'ALARM' && dataStreamId === dataStream.id + ) ) ); }); diff --git a/packages/react-components/src/index.ts b/packages/react-components/src/index.ts index 1877bd765..3bf7f206c 100644 --- a/packages/react-components/src/index.ts +++ b/packages/react-components/src/index.ts @@ -15,7 +15,11 @@ export { StatusTimeline } from './components/status-timeline'; export { Status } from './components/status/status'; export { Tooltip } from './components/tooltip'; export { KnowledgeGraph } from './components/knowledge-graph'; -export type { NodeData, EdgeData, IQueryData } from './components/knowledge-graph'; +export type { + NodeData, + EdgeData, + IQueryData, +} from './components/knowledge-graph'; export { WebglContext } from '@iot-app-kit/charts'; export { TimeSync } from './components/time-sync'; @@ -31,4 +35,8 @@ export { Chart } from './components/chart'; export { TrendCursorSync } from './components/trend-cursor-sync'; -export type { TableColumnDefinition, TableItem, TableItemRef } from './components/table'; +export type { + TableColumnDefinition, + TableItem, + TableItemRef, +} from './components/table'; diff --git a/packages/react-components/src/store/config.ts b/packages/react-components/src/store/config.ts index bd716cdda..d92c14f2f 100644 --- a/packages/react-components/src/store/config.ts +++ b/packages/react-components/src/store/config.ts @@ -7,6 +7,7 @@ export interface ConfigState { export const createConfigSlice: StateCreator = () => ({ config: { useModelBasedQuery: !!localStorage?.getItem('USE_MODEL_BASED_QUERY'), - showTanstackTable: localStorage?.getItem('SHOW_LEGEND_TANSTACK_TABLE') === 'true', + showTanstackTable: + localStorage?.getItem('SHOW_LEGEND_TANSTACK_TABLE') === 'true', }, }); diff --git a/packages/react-components/src/store/index.ts b/packages/react-components/src/store/index.ts index 58768c350..509653ec4 100644 --- a/packages/react-components/src/store/index.ts +++ b/packages/react-components/src/store/index.ts @@ -15,4 +15,5 @@ const useDataStore = create()( export default useDataStore; -export const useGetConfigValue = (configName: Flags) => useDataStore((state) => state.config[configName]); +export const useGetConfigValue = (configName: Flags) => + useDataStore((state) => state.config[configName]); diff --git a/packages/react-components/src/store/trendCusorSlice.ts b/packages/react-components/src/store/trendCusorSlice.ts index 4459ed4d2..c5767776e 100644 --- a/packages/react-components/src/store/trendCusorSlice.ts +++ b/packages/react-components/src/store/trendCusorSlice.ts @@ -15,15 +15,41 @@ export interface TrendCursorsData { export interface TrendCursorsState extends TrendCursorsData { addTrendCursorsGroup: (groupId: string) => void; deleteTrendCursorsGroup: (groupId: string) => void; - addTrendCursors: ({ groupId, tcId, timestamp }: { groupId: string; tcId: string; timestamp: number }) => void; - updateTrendCursors: ({ groupId, tcId, timestamp }: { groupId: string; tcId: string; timestamp: number }) => void; - deleteTrendCursors: ({ groupId, tcId }: { groupId: string; tcId: string }) => void; + addTrendCursors: ({ + groupId, + tcId, + timestamp, + }: { + groupId: string; + tcId: string; + timestamp: number; + }) => void; + updateTrendCursors: ({ + groupId, + tcId, + timestamp, + }: { + groupId: string; + tcId: string; + timestamp: number; + }) => void; + deleteTrendCursors: ({ + groupId, + tcId, + }: { + groupId: string; + tcId: string; + }) => void; } -export const createTrendCursorsSlice: StateCreator = (set) => ({ +export const createTrendCursorsSlice: StateCreator = ( + set +) => ({ trendCursorGroups: {}, addTrendCursorsGroup: (groupId) => - set((state) => ({ trendCursorGroups: { ...state.trendCursorGroups, [groupId]: {} } })), + set((state) => ({ + trendCursorGroups: { ...state.trendCursorGroups, [groupId]: {} }, + })), deleteTrendCursorsGroup: (groupId) => set((state) => { const allGroups = { ...state.trendCursorGroups }; @@ -35,7 +61,10 @@ export const createTrendCursorsSlice: StateCreator = (set) => Object.assign({}, state, { trendCursorGroups: { ...state.trendCursorGroups, - [groupId]: { ...state.trendCursorGroups[groupId], [tcId]: { timestamp } }, + [groupId]: { + ...state.trendCursorGroups[groupId], + [tcId]: { timestamp }, + }, }, }) ), @@ -44,7 +73,10 @@ export const createTrendCursorsSlice: StateCreator = (set) => Object.assign({}, state, { trendCursorGroups: { ...state.trendCursorGroups, - [groupId]: { ...state.trendCursorGroups[groupId], [tcId]: { timestamp } }, + [groupId]: { + ...state.trendCursorGroups[groupId], + [tcId]: { timestamp }, + }, }, }) ), @@ -52,6 +84,8 @@ export const createTrendCursorsSlice: StateCreator = (set) => set((state: TrendCursorsData) => { const tempGroup = { ...state.trendCursorGroups[groupId] }; delete tempGroup[tcId]; - return Object.assign({}, state, { trendCursorGroups: { ...state.trendCursorGroups, [groupId]: tempGroup } }); + return Object.assign({}, state, { + trendCursorGroups: { ...state.trendCursorGroups, [groupId]: tempGroup }, + }); }), }); diff --git a/packages/react-components/src/testing/mockWidgetProperties.ts b/packages/react-components/src/testing/mockWidgetProperties.ts index 66a22481a..5af5f6926 100644 --- a/packages/react-components/src/testing/mockWidgetProperties.ts +++ b/packages/react-components/src/testing/mockWidgetProperties.ts @@ -1,4 +1,10 @@ -import { DATA_TYPE, STATUS_ICON_TYPE, Threshold, DataStream, COMPARISON_OPERATOR } from '@iot-app-kit/core'; +import { + DATA_TYPE, + STATUS_ICON_TYPE, + Threshold, + DataStream, + COMPARISON_OPERATOR, +} from '@iot-app-kit/core'; import { DAY_IN_MS } from '../utils/time'; import { VIEWPORT } from '../utils/testUtil'; @@ -116,4 +122,6 @@ export const ALARM_THRESHOLD: Threshold = { }; export const WITHIN_VIEWPORT_DATE = new Date(2000, 0, 1); -export const BEFORE_VIEWPORT_DATE = new Date(VIEWPORT.start.getTime() - DAY_IN_MS); +export const BEFORE_VIEWPORT_DATE = new Date( + VIEWPORT.start.getTime() - DAY_IN_MS +); diff --git a/packages/react-components/src/utils/aggregationFrequency.spec.ts b/packages/react-components/src/utils/aggregationFrequency.spec.ts index 5cbf9316b..ff0378889 100644 --- a/packages/react-components/src/utils/aggregationFrequency.spec.ts +++ b/packages/react-components/src/utils/aggregationFrequency.spec.ts @@ -2,9 +2,15 @@ import { getAggregationFrequency } from './aggregationFrequency'; it('returns correct string per resolution', () => { expect(getAggregationFrequency()).toEqual('raw data'); - expect(getAggregationFrequency(30000000000, 'minimum')).toEqual('347 days minimum'); - expect(getAggregationFrequency(10000000, 'standard_deviation')).toEqual('2 hours standard deviation'); + expect(getAggregationFrequency(30000000000, 'minimum')).toEqual( + '347 days minimum' + ); + expect(getAggregationFrequency(10000000, 'standard_deviation')).toEqual( + '2 hours standard deviation' + ); expect(getAggregationFrequency(450000, 'sum')).toEqual('7 minutes sum'); - expect(getAggregationFrequency(50000, 'maximum')).toEqual('50 seconds maximum'); + expect(getAggregationFrequency(50000, 'maximum')).toEqual( + '50 seconds maximum' + ); expect(getAggregationFrequency(40, 'other')).toEqual('N/A'); }); diff --git a/packages/react-components/src/utils/aggregationFrequency.ts b/packages/react-components/src/utils/aggregationFrequency.ts index 0a73dfcee..4dc91cb5d 100644 --- a/packages/react-components/src/utils/aggregationFrequency.ts +++ b/packages/react-components/src/utils/aggregationFrequency.ts @@ -4,7 +4,10 @@ const aggregateToString = (aggregate: string): string => { return aggregate.replace(/_/g, ' ').toLowerCase(); }; -export const getAggregationFrequency = (dataResolution?: number, aggregationType?: string) => { +export const getAggregationFrequency = ( + dataResolution?: number, + aggregationType?: string +) => { if (!dataResolution || !aggregationType || dataResolution === 0) { return 'raw data'; } diff --git a/packages/react-components/src/utils/breachedThreshold.spec.ts b/packages/react-components/src/utils/breachedThreshold.spec.ts index 54e585ee5..ee06faf92 100644 --- a/packages/react-components/src/utils/breachedThreshold.spec.ts +++ b/packages/react-components/src/utils/breachedThreshold.spec.ts @@ -1,5 +1,8 @@ import { DATA_TYPE } from '@iot-app-kit/core'; -import { breachedAlarmThresholds, breachedThreshold } from './breachedThreshold'; +import { + breachedAlarmThresholds, + breachedThreshold, +} from './breachedThreshold'; import { COMPARISON_OPERATOR, StreamType } from '../common/constants'; import { SECOND_IN_MS } from './time'; import type { Threshold, DataStream, DataPoint } from '@iot-app-kit/core'; @@ -157,7 +160,10 @@ describe('breachedThreshold', () => { date: new Date(), dataStream: PROPERTY_STREAM, dataStreams: DATA_STREAMS, - thresholds: [ALARM_W_SEVERITY_1_THRESHOLD, ALARM_W_SEVERITY_2_THRESHOLD], + thresholds: [ + ALARM_W_SEVERITY_1_THRESHOLD, + ALARM_W_SEVERITY_2_THRESHOLD, + ], }) ).toEqual(ALARM_W_SEVERITY_1_THRESHOLD); }); @@ -189,7 +195,10 @@ describe('breachedThreshold', () => { date: new Date(), dataStream: PROPERTY_STREAM, dataStreams: DATA_STREAMS, - thresholds: [ALARM_W_SEVERITY_1_THRESHOLD, ALARM_W_SEVERITY_2_THRESHOLD], + thresholds: [ + ALARM_W_SEVERITY_1_THRESHOLD, + ALARM_W_SEVERITY_2_THRESHOLD, + ], }) ).toEqual(ALARM_W_SEVERITY_1_THRESHOLD); }); diff --git a/packages/react-components/src/utils/breachedThreshold.ts b/packages/react-components/src/utils/breachedThreshold.ts index 578e899bd..eda44b420 100644 --- a/packages/react-components/src/utils/breachedThreshold.ts +++ b/packages/react-components/src/utils/breachedThreshold.ts @@ -2,9 +2,17 @@ import { getBreachedThreshold } from './thresholdUtils'; import { isDefined } from './predicates'; import { closestPoint } from './closestPoint'; import { DATA_ALIGNMENT, StreamType } from '../common/constants'; -import type { Threshold, DataStream, DataStreamId, Primitive } from '@iot-app-kit/core'; +import type { + Threshold, + DataStream, + DataStreamId, + Primitive, +} from '@iot-app-kit/core'; -const isHigherPriority = (t1: undefined | Threshold, t2: Threshold): Threshold => { +const isHigherPriority = ( + t1: undefined | Threshold, + t2: Threshold +): Threshold => { if (t1 == null) { return t2; } @@ -28,7 +36,9 @@ const isHigherPriority = (t1: undefined | Threshold, t2: Threshold): Threshold = * * If no thresholds are present with `severity`, the first threshold is returned. */ -export const highestPriorityThreshold = (thresholds: Threshold[]): Threshold | undefined => { +export const highestPriorityThreshold = ( + thresholds: Threshold[] +): Threshold | undefined => { return thresholds.reduce(isHigherPriority, undefined); }; @@ -37,7 +47,10 @@ export const highestPriorityThreshold = (thresholds: Threshold[]): Threshold | u * * EXPOSED FOR TESTING */ -export const thresholdAppliesToDataStream = (threshold: Threshold, dataStreamId: DataStreamId): boolean => { +export const thresholdAppliesToDataStream = ( + threshold: Threshold, + dataStreamId: DataStreamId +): boolean => { const { dataStreamIds } = threshold; if (dataStreamIds == null) { return true; @@ -65,18 +78,29 @@ export const breachedAlarmThresholds = ({ }): Threshold[] => { const alarmStreamIds: string[] = dataStream.associatedStreams != null - ? dataStream.associatedStreams.filter(({ type }) => type === StreamType.ALARM).map(({ id }) => id) + ? dataStream.associatedStreams + .filter(({ type }) => type === StreamType.ALARM) + .map(({ id }) => id) : []; - const isAssociatedAlarm = (stream: DataStream) => alarmStreamIds.includes(stream.id); + const isAssociatedAlarm = (stream: DataStream) => + alarmStreamIds.includes(stream.id); const alarmStreams = dataStreams.filter(isAssociatedAlarm); // thresholds considered breech, across all alarms for the requested data stream const allBreachedAlarmThresholds = alarmStreams .map((stream) => { - const alarmThresholds = thresholds.filter((threshold) => thresholdAppliesToDataStream(threshold, stream.id)); - const latestAlarmValue = closestPoint(stream.data, date, DATA_ALIGNMENT.LEFT); - return latestAlarmValue != null ? getBreachedThreshold(latestAlarmValue.y, alarmThresholds) : undefined; + const alarmThresholds = thresholds.filter((threshold) => + thresholdAppliesToDataStream(threshold, stream.id) + ); + const latestAlarmValue = closestPoint( + stream.data, + date, + DATA_ALIGNMENT.LEFT + ); + return latestAlarmValue != null + ? getBreachedThreshold(latestAlarmValue.y, alarmThresholds) + : undefined; }) .filter(isDefined); @@ -105,8 +129,13 @@ export const breachedThreshold = ({ // stream associated with the point who's value is being evaluated. Used to find associated alarms dataStream: DataStream; }): Threshold | undefined => { - const applicableThresholds = thresholds.filter((threshold) => thresholdAppliesToDataStream(threshold, dataStream.id)); - const dataThreshold = value != null ? getBreachedThreshold(value, applicableThresholds) : undefined; + const applicableThresholds = thresholds.filter((threshold) => + thresholdAppliesToDataStream(threshold, dataStream.id) + ); + const dataThreshold = + value != null + ? getBreachedThreshold(value, applicableThresholds) + : undefined; const alarmThresholds = breachedAlarmThresholds({ date, @@ -115,5 +144,7 @@ export const breachedThreshold = ({ thresholds, }); - return highestPriorityThreshold([dataThreshold, ...alarmThresholds].filter(isDefined)); + return highestPriorityThreshold( + [dataThreshold, ...alarmThresholds].filter(isDefined) + ); }; diff --git a/packages/react-components/src/utils/closestPoint.ts b/packages/react-components/src/utils/closestPoint.ts index 6f9a210ba..5c053833d 100644 --- a/packages/react-components/src/utils/closestPoint.ts +++ b/packages/react-components/src/utils/closestPoint.ts @@ -38,7 +38,9 @@ export const closestPoint = ( if (maxDistance == null) { return rightPoint; } - return rightPoint.x - date.getTime() <= maxDistance ? rightPoint : undefined; + return rightPoint.x - date.getTime() <= maxDistance + ? rightPoint + : undefined; } /** Left Alignment */ diff --git a/packages/react-components/src/utils/isDataStreamInList.spec.ts b/packages/react-components/src/utils/isDataStreamInList.spec.ts index b6e3342bc..63fbfd747 100644 --- a/packages/react-components/src/utils/isDataStreamInList.spec.ts +++ b/packages/react-components/src/utils/isDataStreamInList.spec.ts @@ -2,7 +2,9 @@ import { DATA_STREAM, DATA_STREAM_2 } from '../testing/mockWidgetProperties'; import { isDataStreamInList } from './isDataStreamInList'; it('returns true if the data stream is in the list', () => { - expect(isDataStreamInList([DATA_STREAM, DATA_STREAM_2])(DATA_STREAM)).toBeTrue(); + expect( + isDataStreamInList([DATA_STREAM, DATA_STREAM_2])(DATA_STREAM) + ).toBeTrue(); }); it('returns false if the data stream is not in the list', () => { diff --git a/packages/react-components/src/utils/isDataStreamInList.ts b/packages/react-components/src/utils/isDataStreamInList.ts index 86c975881..b4751128f 100644 --- a/packages/react-components/src/utils/isDataStreamInList.ts +++ b/packages/react-components/src/utils/isDataStreamInList.ts @@ -1,4 +1,5 @@ import { DataStream } from '@iot-app-kit/core'; -export const isDataStreamInList = (datastreams: DataStream[]) => (datastream?: DataStream) => - !!datastream && !!datastreams.find(({ id }) => id === datastream.id); +export const isDataStreamInList = + (datastreams: DataStream[]) => (datastream?: DataStream) => + !!datastream && !!datastreams.find(({ id }) => id === datastream.id); diff --git a/packages/react-components/src/utils/predicates.spec.ts b/packages/react-components/src/utils/predicates.spec.ts index 56bdedcf1..d180100ae 100644 --- a/packages/react-components/src/utils/predicates.spec.ts +++ b/packages/react-components/src/utils/predicates.spec.ts @@ -57,9 +57,15 @@ describe('isNumberDataStream', () => { ], }; - const EMPTY_STRING_STREAM: DataStream = { ...STRING_DATA_STREAM, data: [] }; + const EMPTY_STRING_STREAM: DataStream = { + ...STRING_DATA_STREAM, + data: [], + }; - const EMPTY_NUMBER_STREAM: DataStream = { ...NUMBER_DATA_STREAM, data: [] }; + const EMPTY_NUMBER_STREAM: DataStream = { + ...NUMBER_DATA_STREAM, + data: [], + }; it('returns true when given empty number stream', () => { expect(isNumberDataStream(EMPTY_NUMBER_STREAM)).toBe(true); diff --git a/packages/react-components/src/utils/predicates.ts b/packages/react-components/src/utils/predicates.ts index b132a37d6..1fe7ab826 100644 --- a/packages/react-components/src/utils/predicates.ts +++ b/packages/react-components/src/utils/predicates.ts @@ -29,14 +29,17 @@ import type { DataStream } from '@iot-app-kit/core'; * */ -export const isDefined = (value: T | null | undefined): value is T => value != null; +export const isDefined = (value: T | null | undefined): value is T => + value != null; export const isValid = (predicate: (t: Partial) => boolean) => (t: Partial): t is T => predicate(t); -export const isNumberDataStream = (stream: DataStream): stream is DataStream => - stream.dataType === DATA_TYPE.NUMBER; +export const isNumberDataStream = ( + stream: DataStream +): stream is DataStream => stream.dataType === DATA_TYPE.NUMBER; -export const isNumber = (val: T | number): val is number => typeof val === 'number'; +export const isNumber = (val: T | number): val is number => + typeof val === 'number'; diff --git a/packages/react-components/src/utils/sort.spec.ts b/packages/react-components/src/utils/sort.spec.ts index f11907f58..9316c6a42 100644 --- a/packages/react-components/src/utils/sort.spec.ts +++ b/packages/react-components/src/utils/sort.spec.ts @@ -16,17 +16,21 @@ const TOOLTIP_POINT_2 = { point: POINT_2 }; const UNDEFINED_TOOLTIP_POINT: { point: undefined } = { point: undefined }; it('returns points in decreasing order of y value', () => { - expect([TOOLTIP_POINT_1, TOOLTIP_POINT_2].sort(sortPoints((p) => p.y))).toEqual([TOOLTIP_POINT_2, TOOLTIP_POINT_1]); + expect( + [TOOLTIP_POINT_1, TOOLTIP_POINT_2].sort(sortPoints((p) => p.y)) + ).toEqual([TOOLTIP_POINT_2, TOOLTIP_POINT_1]); }); it('maintains order of items in correct order', () => { - expect([TOOLTIP_POINT_1, TOOLTIP_POINT_2].sort(sortPoints((p) => p.x))).toEqual([TOOLTIP_POINT_1, TOOLTIP_POINT_2]); + expect( + [TOOLTIP_POINT_1, TOOLTIP_POINT_2].sort(sortPoints((p) => p.x)) + ).toEqual([TOOLTIP_POINT_1, TOOLTIP_POINT_2]); }); it('returns undefined points first', () => { - expect([TOOLTIP_POINT_1, UNDEFINED_TOOLTIP_POINT, TOOLTIP_POINT_2].sort(sortPoints((p) => p.y))).toEqual([ - UNDEFINED_TOOLTIP_POINT, - TOOLTIP_POINT_2, - TOOLTIP_POINT_1, - ]); + expect( + [TOOLTIP_POINT_1, UNDEFINED_TOOLTIP_POINT, TOOLTIP_POINT_2].sort( + sortPoints((p) => p.y) + ) + ).toEqual([UNDEFINED_TOOLTIP_POINT, TOOLTIP_POINT_2, TOOLTIP_POINT_1]); }); diff --git a/packages/react-components/src/utils/thresholdUtils.spec.ts b/packages/react-components/src/utils/thresholdUtils.spec.ts index c2bd2f9ea..377184e9c 100644 --- a/packages/react-components/src/utils/thresholdUtils.spec.ts +++ b/packages/react-components/src/utils/thresholdUtils.spec.ts @@ -7,7 +7,10 @@ import { isThresholdBreached, sortThreshold, } from './thresholdUtils'; -import { highestPriorityThreshold, thresholdAppliesToDataStream } from './breachedThreshold'; +import { + highestPriorityThreshold, + thresholdAppliesToDataStream, +} from './breachedThreshold'; import { COMPARISON_OPERATOR } from '../common/constants'; import type { Threshold } from '@iot-app-kit/core'; @@ -57,18 +60,21 @@ describe('annotation logic', () => { ${'STOP'} | ${'STOP'} | ${COMPARISON_OPERATOR.CONTAINS} | ${true} ${'STOPPED'} | ${'STOP'} | ${COMPARISON_OPERATOR.CONTAINS} | ${true} ${'3 STOP'} | ${'STOP'} | ${COMPARISON_OPERATOR.CONTAINS} | ${true} - `('Check if data point is within the threshold', ({ key, thresholdValue, operator, expected }) => { - test(`Given the data value of + `( + 'Check if data point is within the threshold', + ({ key, thresholdValue, operator, expected }) => { + test(`Given the data value of ${key} and threshold value of ${thresholdValue} and the operator ${operator}, we expect: ${expected}`, () => { - const threshold: Threshold = { - color: 'red', - value: thresholdValue, - comparisonOperator: operator, - }; - expect(isThresholdBreached(key, threshold)).toEqual(expected); - }); - }); + const threshold: Threshold = { + color: 'red', + value: thresholdValue, + comparisonOperator: operator, + }; + expect(isThresholdBreached(key, threshold)).toEqual(expected); + }); + } + ); describe('threshold utils', () => { it('returns undefined when empty annotations are passed in', () => { @@ -89,7 +95,9 @@ describe('annotation logic', () => { }, ]; - expect(getBreachedThreshold(dataValue, thresholds)).toEqual(expect.objectContaining({ value: expectedValue })); + expect(getBreachedThreshold(dataValue, thresholds)).toEqual( + expect.objectContaining({ value: expectedValue }) + ); }); it('returns undefined when the value is equal to an annotation that only checks less then logic', () => { @@ -119,7 +127,9 @@ describe('annotation logic', () => { }, ]; - expect(getBreachedThreshold(dataValue, thresholds)).toEqual(expect.objectContaining({ value: expectedValue })); + expect(getBreachedThreshold(dataValue, thresholds)).toEqual( + expect.objectContaining({ value: expectedValue }) + ); }); it('returns undefined when the value is greater than the annotation that checks for less than or equal', () => { @@ -149,7 +159,9 @@ describe('annotation logic', () => { }, ]; - expect(getBreachedThreshold(dataValue, thresholds)).toEqual(expect.objectContaining({ value: expectedValue })); + expect(getBreachedThreshold(dataValue, thresholds)).toEqual( + expect.objectContaining({ value: expectedValue }) + ); }); it('returns undefined when the value is equal to an annotation that only checks greater then logic', () => { @@ -179,7 +191,9 @@ describe('annotation logic', () => { }, ]; - expect(getBreachedThreshold(dataValue, thresholds)).toEqual(expect.objectContaining({ value: expectedValue })); + expect(getBreachedThreshold(dataValue, thresholds)).toEqual( + expect.objectContaining({ value: expectedValue }) + ); }); it('returns undefined when the value is less than then annotation that checks for greater than or equal', () => { @@ -217,7 +231,9 @@ describe('annotation logic', () => { }, ]; - expect(getBreachedThreshold(3, thresholds)).toEqual(expect.objectContaining({ value: expectedValue })); + expect(getBreachedThreshold(3, thresholds)).toEqual( + expect.objectContaining({ value: expectedValue }) + ); }); it('returns the annotation with the highest value that covers the point', () => { @@ -241,7 +257,9 @@ describe('annotation logic', () => { }, ]; - expect(getBreachedThreshold(3, thresholds)).toEqual(expect.objectContaining({ value: expectValue })); + expect(getBreachedThreshold(3, thresholds)).toEqual( + expect.objectContaining({ value: expectValue }) + ); }); it('returns the upper annotation when a positive point data point breaches two annotations', () => { @@ -270,7 +288,9 @@ describe('annotation logic', () => { }, ]; - expect(getBreachedThreshold(6, thresholds)).toEqual(expect.objectContaining({ value: expectValue })); + expect(getBreachedThreshold(6, thresholds)).toEqual( + expect.objectContaining({ value: expectValue }) + ); }); it('returns the lower annotation when a negative point data point breaches two annotations', () => { @@ -299,7 +319,9 @@ describe('annotation logic', () => { }, ]; - expect(getBreachedThreshold(-15, thresholds)).toEqual(expect.objectContaining({ value: expectValue })); + expect(getBreachedThreshold(-15, thresholds)).toEqual( + expect.objectContaining({ value: expectValue }) + ); }); it('returns the correct annotation when unsorted annotations passed in', () => { @@ -328,7 +350,9 @@ describe('annotation logic', () => { }, ]; - expect(getBreachedThreshold(2, thresholds)).toEqual(expect.objectContaining({ value: expectValue })); + expect(getBreachedThreshold(2, thresholds)).toEqual( + expect.objectContaining({ value: expectValue }) + ); }); it('returns true when the object is a threshold type', () => { @@ -368,7 +392,9 @@ describe('annotation logic', () => { }, ]; - expect(getBreachedThreshold(2, thresholds)).toEqual(expect.objectContaining({ color: expectColor })); + expect(getBreachedThreshold(2, thresholds)).toEqual( + expect.objectContaining({ color: expectColor }) + ); }); it('returns the correct breached threshold when there are two of the same threshold value for negative value', () => { @@ -387,7 +413,9 @@ describe('annotation logic', () => { }, ]; - expect(getBreachedThreshold(-2, thresholds)).toEqual(expect.objectContaining({ color: expectColor })); + expect(getBreachedThreshold(-2, thresholds)).toEqual( + expect.objectContaining({ color: expectColor }) + ); }); it('returns the most recently added threshold when there are two of the same threshold with the same value and comparison operator for the exact value', () => { @@ -406,7 +434,9 @@ describe('annotation logic', () => { }, ]; - expect(getBreachedThreshold(0, thresholds)).toEqual(expect.objectContaining({ color: expectColor })); + expect(getBreachedThreshold(0, thresholds)).toEqual( + expect.objectContaining({ color: expectColor }) + ); }); it('returns undefined when two of the same threshold value that does not equal to the exact value', () => { @@ -446,7 +476,9 @@ describe('annotation logic', () => { }, ]; - expect(getBreachedThreshold(26, thresholds)).toEqual(expect.objectContaining({ color: expectedColor })); + expect(getBreachedThreshold(26, thresholds)).toEqual( + expect.objectContaining({ color: expectedColor }) + ); }); it('returns the correct threshold when the point is higher than a "less than" threshold but still technically breached by a lower "greater than" threshold', () => { @@ -464,7 +496,9 @@ describe('annotation logic', () => { }, ]; - expect(getBreachedThreshold(40, thresholds)).toEqual(expect.objectContaining({ color: expectedColor })); + expect(getBreachedThreshold(40, thresholds)).toEqual( + expect.objectContaining({ color: expectedColor }) + ); }); it('returns the correct threshold when the point is lower than a "greater than" threshold but still technically breached by a higher "less than" threshold', () => { @@ -482,7 +516,9 @@ describe('annotation logic', () => { }, ]; - expect(getBreachedThreshold(3, thresholds)).toEqual(expect.objectContaining({ color: expectedColor })); + expect(getBreachedThreshold(3, thresholds)).toEqual( + expect.objectContaining({ color: expectedColor }) + ); }); }); }); @@ -642,13 +678,21 @@ describe('thresholdAppliesToDataStream', () => { }); it('returns that threshold does not apply, if threshold does specify data stream ids, and does not include the request id', () => { - expect(thresholdAppliesToDataStream({ ...threshold, dataStreamIds: ['id-1', 'id-2'] }, 'any-random-id')).toBe( - false - ); + expect( + thresholdAppliesToDataStream( + { ...threshold, dataStreamIds: ['id-1', 'id-2'] }, + 'any-random-id' + ) + ).toBe(false); }); it('returns that threshold does apply, if threshold does specify data stream ids, and does include the request id', () => { - expect(thresholdAppliesToDataStream({ ...threshold, dataStreamIds: ['id-1', 'id-2'] }, 'id-2')).toBe(true); + expect( + thresholdAppliesToDataStream( + { ...threshold, dataStreamIds: ['id-1', 'id-2'] }, + 'id-2' + ) + ).toBe(true); }); }); @@ -695,74 +739,96 @@ describe('highestPriorityThreshold', () => { }); it('always return the threshold with the lowest severity', () => { - expect(highestPriorityThreshold([ALARM_THRESHOLD_1_SEVERITY, ALARM_THRESHOLD_2_SEVERITY])).toBe( - ALARM_THRESHOLD_1_SEVERITY - ); // Has lower seveirty + expect( + highestPriorityThreshold([ + ALARM_THRESHOLD_1_SEVERITY, + ALARM_THRESHOLD_2_SEVERITY, + ]) + ).toBe(ALARM_THRESHOLD_1_SEVERITY); // Has lower seveirty }); it('always returns a threshold with a severity, over one without', () => { const LOW_SEVERITY = { ...ALARM_THRESHOLD_1_SEVERITY, severity: 999999 }; - expect(highestPriorityThreshold([LOW_SEVERITY, DATA_THRESHOLD])).toBe(LOW_SEVERITY); + expect(highestPriorityThreshold([LOW_SEVERITY, DATA_THRESHOLD])).toBe( + LOW_SEVERITY + ); }); }); describe('isHigherPriorityThresholds', () => { it('returns an array with threshold when passed no thresholds', () => { - expect(isHigherPriorityThresholds([], DATA_THRESHOLD)).toStrictEqual([DATA_THRESHOLD]); + expect(isHigherPriorityThresholds([], DATA_THRESHOLD)).toStrictEqual([ + DATA_THRESHOLD, + ]); }); it('returns an array with all thresholds when neither have severity', () => { - expect(isHigherPriorityThresholds([DATA_THRESHOLD], DATA_THRESHOLD_2)).toStrictEqual([ - DATA_THRESHOLD, - DATA_THRESHOLD_2, - ]); + expect( + isHigherPriorityThresholds([DATA_THRESHOLD], DATA_THRESHOLD_2) + ).toStrictEqual([DATA_THRESHOLD, DATA_THRESHOLD_2]); }); it('returns an array with all thresholds when they have the same severity', () => { - expect(isHigherPriorityThresholds([ALARM_THRESHOLD_1_SEVERITY], ALARM_THRESHOLD_1_SEVERITY_2)).toStrictEqual([ - ALARM_THRESHOLD_1_SEVERITY, - ALARM_THRESHOLD_1_SEVERITY_2, - ]); + expect( + isHigherPriorityThresholds( + [ALARM_THRESHOLD_1_SEVERITY], + ALARM_THRESHOLD_1_SEVERITY_2 + ) + ).toStrictEqual([ALARM_THRESHOLD_1_SEVERITY, ALARM_THRESHOLD_1_SEVERITY_2]); }); it('returns a new array with the threshold with severity when the initial thresholds array has no severity', () => { - expect(isHigherPriorityThresholds([DATA_THRESHOLD], ALARM_THRESHOLD_1_SEVERITY)).toStrictEqual([ - ALARM_THRESHOLD_1_SEVERITY, - ]); + expect( + isHigherPriorityThresholds([DATA_THRESHOLD], ALARM_THRESHOLD_1_SEVERITY) + ).toStrictEqual([ALARM_THRESHOLD_1_SEVERITY]); }); it('returns the initial thresholds array with severity when the new threshold has no severity', () => { - expect(isHigherPriorityThresholds([ALARM_THRESHOLD_1_SEVERITY], DATA_THRESHOLD)).toStrictEqual([ - ALARM_THRESHOLD_1_SEVERITY, - ]); + expect( + isHigherPriorityThresholds([ALARM_THRESHOLD_1_SEVERITY], DATA_THRESHOLD) + ).toStrictEqual([ALARM_THRESHOLD_1_SEVERITY]); }); it('returns an array with the higher priority threshold', () => { - expect(isHigherPriorityThresholds([ALARM_THRESHOLD_1_SEVERITY], ALARM_THRESHOLD_2_SEVERITY)).toStrictEqual([ - ALARM_THRESHOLD_1_SEVERITY, - ]); - expect(isHigherPriorityThresholds([ALARM_THRESHOLD_2_SEVERITY], ALARM_THRESHOLD_1_SEVERITY)).toStrictEqual([ - ALARM_THRESHOLD_1_SEVERITY, - ]); + expect( + isHigherPriorityThresholds( + [ALARM_THRESHOLD_1_SEVERITY], + ALARM_THRESHOLD_2_SEVERITY + ) + ).toStrictEqual([ALARM_THRESHOLD_1_SEVERITY]); + expect( + isHigherPriorityThresholds( + [ALARM_THRESHOLD_2_SEVERITY], + ALARM_THRESHOLD_1_SEVERITY + ) + ).toStrictEqual([ALARM_THRESHOLD_1_SEVERITY]); }); }); describe('highestPriorityThresholds', () => { it('returns an array of a threshold with severity', () => { - expect(highestPriorityThresholds([DATA_THRESHOLD, ALARM_THRESHOLD_1_SEVERITY])).toStrictEqual([ - ALARM_THRESHOLD_1_SEVERITY, - ]); + expect( + highestPriorityThresholds([DATA_THRESHOLD, ALARM_THRESHOLD_1_SEVERITY]) + ).toStrictEqual([ALARM_THRESHOLD_1_SEVERITY]); }); it('returns an array of thresholds with the same severity', () => { expect( - highestPriorityThresholds([DATA_THRESHOLD, ALARM_THRESHOLD_1_SEVERITY, ALARM_THRESHOLD_1_SEVERITY_2]) + highestPriorityThresholds([ + DATA_THRESHOLD, + ALARM_THRESHOLD_1_SEVERITY, + ALARM_THRESHOLD_1_SEVERITY_2, + ]) ).toStrictEqual([ALARM_THRESHOLD_1_SEVERITY, ALARM_THRESHOLD_1_SEVERITY_2]); }); it('returns an array of thresholds with higher priority', () => { expect( - highestPriorityThresholds([ALARM_THRESHOLD_2_SEVERITY, ALARM_THRESHOLD_1_SEVERITY, ALARM_THRESHOLD_1_SEVERITY_2]) + highestPriorityThresholds([ + ALARM_THRESHOLD_2_SEVERITY, + ALARM_THRESHOLD_1_SEVERITY, + ALARM_THRESHOLD_1_SEVERITY_2, + ]) ).toStrictEqual([ALARM_THRESHOLD_1_SEVERITY, ALARM_THRESHOLD_1_SEVERITY_2]); }); }); diff --git a/packages/react-components/src/utils/thresholdUtils.ts b/packages/react-components/src/utils/thresholdUtils.ts index c70c5e2a2..96aa4d89e 100644 --- a/packages/react-components/src/utils/thresholdUtils.ts +++ b/packages/react-components/src/utils/thresholdUtils.ts @@ -24,7 +24,10 @@ export const getNumberThresholds = (thresholds: Threshold[]): Threshold[] => * @param {Threshold} t2 * @returns {Array.} */ -export const isHigherPriorityThresholds = (t1Array: Threshold[], t2: Threshold): Threshold[] => { +export const isHigherPriorityThresholds = ( + t1Array: Threshold[], + t2: Threshold +): Threshold[] => { const t1Severity = t1Array[0]?.severity; const t2Severity = t2.severity; const t2Array = [t2]; @@ -66,16 +69,26 @@ export const isHigherPriorityThresholds = (t1Array: Threshold[], t2: Threshold): * @param {Array.} thresholds * @returns {Array.} */ -export const highestPriorityThresholds = (thresholds: Threshold[]): Threshold[] => { +export const highestPriorityThresholds = ( + thresholds: Threshold[] +): Threshold[] => { return thresholds.reduce(isHigherPriorityThresholds, []); }; -export const isThresholdBreached = (value: Primitive, threshold: Threshold): boolean => { +export const isThresholdBreached = ( + value: Primitive, + threshold: Threshold +): boolean => { const dataStreamValue = isNumeric(value) ? Number(value) : value; - const thresholdValue = isNumeric(threshold.value) ? Number(threshold.value) : threshold.value; + const thresholdValue = isNumeric(threshold.value) + ? Number(threshold.value) + : threshold.value; const thresholdComparison = threshold.comparisonOperator; - if (typeof dataStreamValue === 'number' && typeof thresholdValue === 'number') { + if ( + typeof dataStreamValue === 'number' && + typeof thresholdValue === 'number' + ) { switch (thresholdComparison) { case COMPARISON_OPERATOR.GT: return dataStreamValue > thresholdValue; @@ -96,11 +109,16 @@ export const isThresholdBreached = (value: Primitive, threshold: Threshold): boo return false; default: - throw new Error(`Unsupported number threshold comparison operator: ${thresholdComparison}`); + throw new Error( + `Unsupported number threshold comparison operator: ${thresholdComparison}` + ); } } - if (typeof dataStreamValue === 'string' && typeof thresholdValue === 'string') { + if ( + typeof dataStreamValue === 'string' && + typeof thresholdValue === 'string' + ) { switch (thresholdComparison) { case COMPARISON_OPERATOR.GT: case COMPARISON_OPERATOR.GTE: @@ -115,11 +133,16 @@ export const isThresholdBreached = (value: Primitive, threshold: Threshold): boo return dataStreamValue.includes(thresholdValue); default: - throw new Error(`Unsupported string threshold comparison operator: ${thresholdComparison}`); + throw new Error( + `Unsupported string threshold comparison operator: ${thresholdComparison}` + ); } } - if (typeof dataStreamValue === 'boolean' && typeof thresholdValue === 'boolean') { + if ( + typeof dataStreamValue === 'boolean' && + typeof thresholdValue === 'boolean' + ) { switch (thresholdComparison) { case COMPARISON_OPERATOR.GT: case COMPARISON_OPERATOR.GTE: @@ -132,14 +155,18 @@ export const isThresholdBreached = (value: Primitive, threshold: Threshold): boo return dataStreamValue === thresholdValue; default: - throw new Error(`Unsupported boolean threshold comparison operator: ${thresholdComparison}`); + throw new Error( + `Unsupported boolean threshold comparison operator: ${thresholdComparison}` + ); } } return false; }; -const thresholdBisector = bisector((threshold: Threshold) => threshold.value).left; +const thresholdBisector = bisector( + (threshold: Threshold) => threshold.value +).left; /** * This a map of comparison operator to order. The higher the order means higher the precedence. @@ -167,7 +194,10 @@ const operatorOrder = { export const sortThreshold = (thresholds: Threshold[]): Threshold[] => [...thresholds].sort((a, b) => { if (a.value === b.value) { - return operatorOrder[a.comparisonOperator] - operatorOrder[b.comparisonOperator]; + return ( + operatorOrder[a.comparisonOperator] - + operatorOrder[b.comparisonOperator] + ); } // TODO: Fix this to work for all cases. value is not always a number or comparing to the same type return (a.value as number) - (b.value as number); @@ -184,13 +214,19 @@ export const sortThreshold = (thresholds: Threshold[]): Threshold[] => * * 2) When the value is negative, then we will take the lower threshold, which is the lesser one. */ -export const getBreachedThreshold = (value: Primitive, thresholds: Threshold[]): Threshold | undefined => { +export const getBreachedThreshold = ( + value: Primitive, + thresholds: Threshold[] +): Threshold | undefined => { if (thresholds.length === 0) { return undefined; } if (typeof value === 'string' || typeof value === 'boolean') { - return thresholds.find((threshold) => isThresholdBreached(value, threshold)) || undefined; + return ( + thresholds.find((threshold) => isThresholdBreached(value, threshold)) || + undefined + ); } /** @@ -201,10 +237,13 @@ export const getBreachedThreshold = (value: Primitive, thresholds: Threshold[]): * TO-DO: Add the 'in between operator' feature as one of the operator selections to consider * breached data points in between 2 thresholds. */ - const breachedThresholds = thresholds.filter((threshold) => isThresholdBreached(value, threshold)); + const breachedThresholds = thresholds.filter((threshold) => + isThresholdBreached(value, threshold) + ); // Only consider the highest severity breached thresholds. - const highestSeverityThresholds = highestPriorityThresholds(breachedThresholds); + const highestSeverityThresholds = + highestPriorityThresholds(breachedThresholds); const numberThresholds = getNumberThresholds(highestSeverityThresholds); @@ -225,7 +264,11 @@ export const getBreachedThreshold = (value: Primitive, thresholds: Threshold[]): } // Special case when the idx is at 0 and that the first two values are of the same value. - if (idx === 0 && numberThresholds.length > 1 && sortedThresholds[idx].value === sortedThresholds[idx + 1].value) { + if ( + idx === 0 && + numberThresholds.length > 1 && + sortedThresholds[idx].value === sortedThresholds[idx + 1].value + ) { annotationLeft = sortedThresholds[idx]; annotationRight = sortedThresholds[idx + 1]; } @@ -235,24 +278,40 @@ export const getBreachedThreshold = (value: Primitive, thresholds: Threshold[]): } if (annotationLeft != null && annotationRight == null) { - return isThresholdBreached(value, annotationLeft) ? annotationLeft : undefined; + return isThresholdBreached(value, annotationLeft) + ? annotationLeft + : undefined; } if (annotationLeft == null && annotationRight != null) { - return isThresholdBreached(value, annotationRight) ? annotationRight : undefined; + return isThresholdBreached(value, annotationRight) + ? annotationRight + : undefined; } - if (isThresholdBreached(value, annotationLeft) && isThresholdBreached(value, annotationRight)) { + if ( + isThresholdBreached(value, annotationLeft) && + isThresholdBreached(value, annotationRight) + ) { return value >= 0 ? annotationRight : annotationLeft; } - if (isThresholdBreached(value, annotationLeft) && !isThresholdBreached(value, annotationRight)) { + if ( + isThresholdBreached(value, annotationLeft) && + !isThresholdBreached(value, annotationRight) + ) { return annotationLeft; } - if (!isThresholdBreached(value, annotationLeft) && isThresholdBreached(value, annotationRight)) { + if ( + !isThresholdBreached(value, annotationLeft) && + isThresholdBreached(value, annotationRight) + ) { return annotationRight; } return undefined; }; -export const isThreshold = isValid((maybeThreshold: Partial) => maybeThreshold.comparisonOperator != null); +export const isThreshold = isValid( + (maybeThreshold: Partial) => + maybeThreshold.comparisonOperator != null +); diff --git a/packages/react-components/src/utils/time.ts b/packages/react-components/src/utils/time.ts index 315d592ba..34b2377dc 100644 --- a/packages/react-components/src/utils/time.ts +++ b/packages/react-components/src/utils/time.ts @@ -64,7 +64,11 @@ export const convertMS = ( }; }; -export const displayDate = (date: Date, resolution: number, { start, end }: { start: Date; end: Date }): string => { +export const displayDate = ( + date: Date, + resolution: number, + { start, end }: { start: Date; end: Date } +): string => { const viewportDurationMS = end.getTime() - start.getTime(); if (resolution < HOUR_IN_MS) { if (viewportDurationMS < MINUTE_IN_MS) { diff --git a/packages/react-components/stories/chart/base-chart.stories.tsx b/packages/react-components/stories/chart/base-chart.stories.tsx index 8de110e91..785a66f5d 100644 --- a/packages/react-components/stories/chart/base-chart.stories.tsx +++ b/packages/react-components/stories/chart/base-chart.stories.tsx @@ -5,7 +5,13 @@ import { TimeSelection, TimeSync, useViewport, Chart } from '../../src'; import { getTimeSeriesDataQuery, queryConfigured } from '../utils/query'; import { ChartOptions, Visualization } from '../../src/components/chart/types'; -const chartTypes: Visualization[] = ['line', 'scatter', 'step-start', 'step-middle', 'step-end']; // removing bar for now +const chartTypes: Visualization[] = [ + 'line', + 'scatter', + 'step-start', + 'step-middle', + 'step-end', +]; // removing bar for now export default { title: 'Widgets/Base Chart', component: Chart, @@ -17,7 +23,10 @@ export default { defaultValue: undefined, }, significantDigits: { control: { type: 'number', defaultValue: undefined } }, - size: { control: { type: 'object' }, defaultValue: { width: 800, height: 500 } }, + size: { + control: { type: 'object' }, + defaultValue: { width: 800, height: 500 }, + }, styleSettings: { control: { type: 'object' }, defaultValue: undefined }, }, parameters: { @@ -27,7 +36,12 @@ export default { type StoryInputs = ChartOptions; -export const BaseChartExample: ComponentStory> = ({ id, significantDigits, size, styleSettings }) => { +export const BaseChartExample: ComponentStory> = ({ + id, + significantDigits, + size, + styleSettings, +}) => { const { viewport } = useViewport(); return ( @@ -54,12 +68,9 @@ export const BaseChartExample: ComponentStory> = ({ id, signific ); }; -export const SiteWiseConnectedBaseChartExample: ComponentStory> = ({ - id, - significantDigits, - size, - styleSettings, -}) => { +export const SiteWiseConnectedBaseChartExample: ComponentStory< + FC +> = ({ id, significantDigits, size, styleSettings }) => { const { viewport } = useViewport(); if (!queryConfigured()) { diff --git a/packages/react-components/stories/chart/chart.stories.tsx b/packages/react-components/stories/chart/chart.stories.tsx index 6097338ce..db50bf5e9 100644 --- a/packages/react-components/stories/chart/chart.stories.tsx +++ b/packages/react-components/stories/chart/chart.stories.tsx @@ -1,16 +1,32 @@ import React from 'react'; import type { ComponentMeta, ComponentStory } from '@storybook/react'; -import { MOCK_TIME_SERIES_DATA_QUERY, MOCK_TIME_SERIES_DATA_AGGREGATED_QUERY, VIEWPORT } from './mock-data'; +import { + MOCK_TIME_SERIES_DATA_QUERY, + MOCK_TIME_SERIES_DATA_AGGREGATED_QUERY, + VIEWPORT, +} from './mock-data'; // Should be part of the public API, i.e. exported from src -import { LineChart, ScatterChart, BarChart, StatusTimeline, WebglContext, TimeSync, useViewport } from '../../src'; +import { + LineChart, + ScatterChart, + BarChart, + StatusTimeline, + WebglContext, + TimeSync, + useViewport, +} from '../../src'; const ViewportConsumer = () => { const { viewport, setViewport } = useViewport(); const chooseRandomViewport = () => { setViewport({ - start: new Date(new Date(1900, 0, 0).getTime() + 1000000000000 * Math.random()), - end: new Date(new Date(2000, 0, 0).getTime() + 1000000000000 * Math.random()), + start: new Date( + new Date(1900, 0, 0).getTime() + 1000000000000 * Math.random() + ), + end: new Date( + new Date(2000, 0, 0).getTime() + 1000000000000 * Math.random() + ), }); }; @@ -59,7 +75,10 @@ export const MultipleBarChartExample: ComponentStory = () => { export const ScatterChartExample: ComponentStory = () => { return (
    - +
    ); @@ -68,16 +87,24 @@ export const ScatterChartExample: ComponentStory = () => { export const BarChartExample: ComponentStory = () => { return (
    - +
    ); }; -export const StatusTimelineExample: ComponentStory = () => { +export const StatusTimelineExample: ComponentStory< + typeof StatusTimeline +> = () => { return (
    - +
    ); diff --git a/packages/react-components/stories/chart/mock-data.ts b/packages/react-components/stories/chart/mock-data.ts index d291e689f..20e0006bb 100644 --- a/packages/react-components/stories/chart/mock-data.ts +++ b/packages/react-components/stories/chart/mock-data.ts @@ -3,7 +3,10 @@ import { DATA_TYPE, DAY_IN_MS } from '@iot-app-kit/core'; import type { DataStream } from '@iot-app-kit/core'; const NUM_STREAMS = 2; -export const VIEWPORT = { start: new Date(2000, 0, 0), end: new Date(2010, 0, 0) }; +export const VIEWPORT = { + start: new Date(2000, 0, 0), + end: new Date(2010, 0, 0), +}; const DATA_STREAM: DataStream = { id: 'some-asset-id---some-property-id', diff --git a/packages/react-components/stories/dial/dialBase.stories.tsx b/packages/react-components/stories/dial/dialBase.stories.tsx index e4c71400e..825950239 100644 --- a/packages/react-components/stories/dial/dialBase.stories.tsx +++ b/packages/react-components/stories/dial/dialBase.stories.tsx @@ -9,8 +9,14 @@ export default { title: 'Widgets/Dial/Dial base', component: DialBase, argTypes: { - propertyPoint: { control: { type: 'object' }, defaultValue: { x: 123123213, y: 100.13 } }, - alarmPoint: { control: { type: 'object' }, defaultValue: { x: 123123213, y: 'Warning' } }, + propertyPoint: { + control: { type: 'object' }, + defaultValue: { x: 123123213, y: 100.13 }, + }, + alarmPoint: { + control: { type: 'object' }, + defaultValue: { x: 123123213, y: 'Warning' }, + }, yMin: { control: { type: 'number' }, defaultValue: 0 }, yMax: { control: { type: 'number' }, defaultValue: 100 }, showName: { control: { type: 'boolean' }, defaultValue: true }, @@ -21,9 +27,18 @@ export default { }, } as ComponentMeta; -type StoryInputs = DialSettings & { alarmPoint?: DataPoint; propertyPoint?: DataPoint }; +type StoryInputs = DialSettings & { + alarmPoint?: DataPoint; + propertyPoint?: DataPoint; +}; -export const Main: ComponentStory> = ({ yMin, yMax, showName, showUnit, ...args }) => ( +export const Main: ComponentStory> = ({ + yMin, + yMax, + showName, + showUnit, + ...args +}) => (
    diff --git a/packages/react-components/stories/graph/dev.stories.tsx b/packages/react-components/stories/graph/dev.stories.tsx index 021cd9fd6..da7f6b1c0 100644 --- a/packages/react-components/stories/graph/dev.stories.tsx +++ b/packages/react-components/stories/graph/dev.stories.tsx @@ -39,21 +39,24 @@ export default { }, } as ComponentMeta; -export const Basic: ComponentStory = Template.bind({}); +export const Basic: ComponentStory = + Template.bind({}); Basic.parameters = { KG: { queryResult: response3, }, }; -export const InContainers: ComponentStory = TemplateInContainer.bind({}); +export const InContainers: ComponentStory = + TemplateInContainer.bind({}); InContainers.parameters = { KG: { queryResult: response, }, }; -export const OverrideStyles: ComponentStory = Template.bind({}); +export const OverrideStyles: ComponentStory = + Template.bind({}); OverrideStyles.parameters = { KG: { queryResult: response3, diff --git a/packages/react-components/stories/graph/graphDecorator.tsx b/packages/react-components/stories/graph/graphDecorator.tsx index 7375ede91..fb973835e 100644 --- a/packages/react-components/stories/graph/graphDecorator.tsx +++ b/packages/react-components/stories/graph/graphDecorator.tsx @@ -1,5 +1,8 @@ import React from 'react'; -import { context, KnowledgeGraphContext } from '../../src/components/knowledge-graph/StateManager'; +import { + context, + KnowledgeGraphContext, +} from '../../src/components/knowledge-graph/StateManager'; import { IntlProvider } from 'react-intl'; import { useParameter } from '@storybook/addons'; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/react-components/stories/graph/mock-data.ts b/packages/react-components/stories/graph/mock-data.ts index add766162..4896ac6e3 100644 --- a/packages/react-components/stories/graph/mock-data.ts +++ b/packages/react-components/stories/graph/mock-data.ts @@ -33,34 +33,36 @@ export const mapResponseData = (res: ExecuteQueryResponse) => { return res.rows ?.map((r) => r.rowData) .reduce((acc, rowData) => { - const newElements: ElementDefinition[] = res.columnDescriptions?.map((col, i) => { - const entity = !!rowData && rowData[i]; + const newElements: ElementDefinition[] = res.columnDescriptions?.map( + (col, i) => { + const entity = !!rowData && rowData[i]; - if (entity) { - if (col.type === 'EDGE') { - const relationship = entity as Edge; + if (entity) { + if (col.type === 'EDGE') { + const relationship = entity as Edge; + return { + data: { + source: relationship.sourceEntityId, + target: relationship.targetEntityId, + label: relationship.relationshipName, + lineStyle: edgeStyles[relationship.relationshipName], + ...relationship, + }, + } as ElementDefinition; + } + + const node = entity as Node; return { data: { - source: relationship.sourceEntityId, - target: relationship.targetEntityId, - label: relationship.relationshipName, - lineStyle: edgeStyles[relationship.relationshipName], - ...relationship, + id: node.entityId, + label: node.entityName, + shape: nodeShapes[node.components[0].componentTypeId], + ...node, }, } as ElementDefinition; } - - const node = entity as Node; - return { - data: { - id: node.entityId, - label: node.entityName, - shape: nodeShapes[node.components[0].componentTypeId], - ...node, - }, - } as ElementDefinition; } - }) as ElementDefinition[]; + ) as ElementDefinition[]; return [...acc, ...newElements]; }, [] as ElementDefinition[]) as ElementDefinition[]; diff --git a/packages/react-components/stories/kpi/kpiBase.stories.tsx b/packages/react-components/stories/kpi/kpiBase.stories.tsx index 63a511825..e673bb33f 100644 --- a/packages/react-components/stories/kpi/kpiBase.stories.tsx +++ b/packages/react-components/stories/kpi/kpiBase.stories.tsx @@ -23,7 +23,10 @@ export default { showTimestamp: { control: { type: 'boolean' } }, showUnit: { control: { type: 'boolean' } }, isLoading: { control: { type: 'boolean' } }, - propertyPoint: { control: { type: 'object' }, defaultValue: { x: 123123213, y: 100 } }, + propertyPoint: { + control: { type: 'object' }, + defaultValue: { x: 123123213, y: 100 }, + }, alarmPoint: { control: { type: 'object' } }, fontSize: { control: { type: 'number' } }, secondaryFontSize: { control: { type: 'number' } }, @@ -33,7 +36,10 @@ export default { }, } as ComponentMeta; -type StoryInputs = KPISettings & { alarmPoint?: DataPoint; propertyPoint?: DataPoint }; +type StoryInputs = KPISettings & { + alarmPoint?: DataPoint; + propertyPoint?: DataPoint; +}; export const Main: ComponentStory> = ({ showName, @@ -42,4 +48,15 @@ export const Main: ComponentStory> = ({ fontSize, secondaryFontSize, ...args -}) => ; +}) => ( + +); diff --git a/packages/react-components/stories/menu/menu.stories.tsx b/packages/react-components/stories/menu/menu.stories.tsx index 5e129718d..39b87c2c1 100644 --- a/packages/react-components/stories/menu/menu.stories.tsx +++ b/packages/react-components/stories/menu/menu.stories.tsx @@ -4,7 +4,13 @@ import type { FC } from 'react'; import { Icon } from '@cloudscape-design/components'; import { getColor } from '../../src/components/chart/utils/getColor'; -import { Menu, MenuOption, MenuProps, PositionableMenu, PositionableMenuProps } from '../../src/components/menu'; +import { + Menu, + MenuOption, + MenuProps, + PositionableMenu, + PositionableMenuProps, +} from '../../src/components/menu'; const defaultPosition = { x: 10, y: 10, z: 0 }; @@ -86,7 +92,11 @@ export const Absolute: ComponentStory> = ({ position }) => ( const randomOptions = () => Array(5) .fill(10) - .map((v) => ({ id: (Math.random() * v).toFixed(0), color: getColor(), label: (Math.random() * v).toFixed(0) })); + .map((v) => ({ + id: (Math.random() * v).toFixed(0), + color: getColor(), + label: (Math.random() * v).toFixed(0), + })); const ColorIcon = ({ color }: { color: string }) => (
    @@ -130,7 +140,8 @@ export const Relative: ComponentStory> = () => {
    {Object.entries(selectedMin).map(([color, visible]) => (
    - :
    {visible ? 'visible' : 'invisible'}
    + :{' '} +
    {visible ? 'visible' : 'invisible'}
    ))}
    @@ -138,7 +149,8 @@ export const Relative: ComponentStory> = () => {
    {Object.entries(selectedMax).map(([color, visible]) => (
    - :
    {visible ? 'visible' : 'invisible'}
    + :{' '} +
    {visible ? 'visible' : 'invisible'}
    ))}
    @@ -154,7 +166,13 @@ export const Relative: ComponentStory> = () => {
    } + iconStart={() => ( + + )} label='Y-Min' action={() => setMinOpen(!minOpen)} /> @@ -166,7 +184,12 @@ export const Relative: ComponentStory> = () => { setHighlightedMin(color)} onPointerLeave={() => setHighlightedMin('')} - action={() => setSelectedMin({ ...selectedMin, [color]: !(color in selectedMin && selectedMin[color]) })} + action={() => + setSelectedMin({ + ...selectedMin, + [color]: !(color in selectedMin && selectedMin[color]), + }) + } key={color} iconStart={() => } label={label} @@ -186,7 +209,11 @@ export const Relative: ComponentStory> = () => {
    } + iconStart={() => ( + + )} label='Y-Max' action={() => setMaxOpen(!maxOpen)} /> @@ -198,7 +225,12 @@ export const Relative: ComponentStory> = () => { setHighlightedMax(color)} onPointerLeave={() => setHighlightedMax('')} - action={() => setSelectedMax({ ...selectedMax, [color]: !(color in selectedMax && selectedMax[color]) })} + action={() => + setSelectedMax({ + ...selectedMax, + [color]: !(color in selectedMax && selectedMax[color]), + }) + } key={color} iconStart={() => } label={label} diff --git a/packages/react-components/stories/status/statusBase.stories.tsx b/packages/react-components/stories/status/statusBase.stories.tsx index 5153733f3..aad073070 100644 --- a/packages/react-components/stories/status/statusBase.stories.tsx +++ b/packages/react-components/stories/status/statusBase.stories.tsx @@ -10,7 +10,10 @@ export default { title: 'Widgets/Status/Status base', component: StatusBase, argTypes: { - propertyPoint: { control: { type: 'object' }, defaultValue: { x: 123123213, y: 100 } }, + propertyPoint: { + control: { type: 'object' }, + defaultValue: { x: 123123213, y: 100 }, + }, alarmPoint: { control: { type: 'object' } }, name: { control: { type: 'text' }, defaultValue: 'Windmill' }, error: { control: { type: 'text' } }, @@ -33,7 +36,10 @@ export default { }, } as ComponentMeta; -type StoryInputs = StatusSettings & { alarmPoint?: DataPoint; propertyPoint?: DataPoint }; +type StoryInputs = StatusSettings & { + alarmPoint?: DataPoint; + propertyPoint?: DataPoint; +}; export const Main: ComponentStory> = ({ showName, @@ -44,6 +50,9 @@ export const Main: ComponentStory> = ({ ...args }) => (
    - +
    ); diff --git a/packages/react-components/stories/time-sync/timesync.stories.tsx b/packages/react-components/stories/time-sync/timesync.stories.tsx index ea5dfa684..5c4e513e4 100644 --- a/packages/react-components/stories/time-sync/timesync.stories.tsx +++ b/packages/react-components/stories/time-sync/timesync.stories.tsx @@ -68,7 +68,9 @@ export const MultipleTimeSyncs: ComponentStory = () => (
    ); -export const MultipleTimeSyncSameGroup: ComponentStory = () => ( +export const MultipleTimeSyncSameGroup: ComponentStory< + typeof TimeSync +> = () => (
    diff --git a/packages/react-components/stories/trend-cursor-sync/timesync.stories.tsx b/packages/react-components/stories/trend-cursor-sync/timesync.stories.tsx index 0caac2235..a1f85e010 100644 --- a/packages/react-components/stories/trend-cursor-sync/timesync.stories.tsx +++ b/packages/react-components/stories/trend-cursor-sync/timesync.stories.tsx @@ -4,7 +4,13 @@ import { TrendCursorSync, Chart, TimeSync } from '../../src'; import { MOCK_TIME_SERIES_DATA_QUERY, VIEWPORT } from '../chart/mock-data'; import { Visualization } from '../../src/components/chart/types'; -const chartTypes: Visualization[] = ['line', 'scatter', 'step-start', 'step-middle', 'step-end']; +const chartTypes: Visualization[] = [ + 'line', + 'scatter', + 'step-start', + 'step-middle', + 'step-end', +]; export default { title: 'Builder Components/TrendCursorSync/TrendCursorSync', component: TrendCursorSync, diff --git a/packages/react-components/stories/utils/query.ts b/packages/react-components/stories/utils/query.ts index 6ef87e66e..3177e01f3 100644 --- a/packages/react-components/stories/utils/query.ts +++ b/packages/react-components/stories/utils/query.ts @@ -1,8 +1,14 @@ import { getIotEventsClient, getSiteWiseClient } from '@iot-app-kit/core-util'; -import { SiteWiseDataStreamQuery, initialize } from '@iot-app-kit/source-iotsitewise'; +import { + SiteWiseDataStreamQuery, + initialize, +} from '@iot-app-kit/source-iotsitewise'; const getEnvCredentials = () => { - if (process.env.AWS_ACCESS_KEY_ID == null || process.env.AWS_SECRET_ACCESS_KEY == null) { + if ( + process.env.AWS_ACCESS_KEY_ID == null || + process.env.AWS_SECRET_ACCESS_KEY == null + ) { throw new Error( 'Missing credentials: must provide the following env variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN within .env' ); @@ -17,7 +23,9 @@ const getEnvCredentials = () => { const getRegion = () => { if (process.env.AWS_REGION == null) { - throw new Error('Missing credentials: Must provide the following env variables: AWS_REGION'); + throw new Error( + 'Missing credentials: Must provide the following env variables: AWS_REGION' + ); } return process.env.AWS_REGION; }; @@ -52,7 +60,9 @@ export const getIotSiteWiseQuery = () => { }).query; }; -export const getTimeSeriesDataQuery = (dataStreamQuery?: SiteWiseDataStreamQuery) => { +export const getTimeSeriesDataQuery = ( + dataStreamQuery?: SiteWiseDataStreamQuery +) => { if (dataStreamQuery) { return getIotSiteWiseQuery().timeSeriesData(dataStreamQuery); } diff --git a/packages/related-table/src/HOC/withUseTreeCollection.tsx b/packages/related-table/src/HOC/withUseTreeCollection.tsx index addb38de4..725396872 100644 --- a/packages/related-table/src/HOC/withUseTreeCollection.tsx +++ b/packages/related-table/src/HOC/withUseTreeCollection.tsx @@ -4,10 +4,14 @@ import Pagination from '@awsui/components-react/pagination'; import { NonCancelableCustomEvent, TableProps } from '@awsui/components-react'; import { EmptyState, EmptyStateProps } from '../RelatedTable/EmptyState'; import { RelatedTableProps } from '../RelatedTable/RelatedTable'; -import { useTreeCollection, UseTreeCollection } from '../Hooks/useTreeCollection'; +import { + useTreeCollection, + UseTreeCollection, +} from '../Hooks/useTreeCollection'; import { ITreeNode } from '../Model/TreeNode'; -export interface RelatedTableExtendedProps extends Omit, 'empty'> { +export interface RelatedTableExtendedProps + extends Omit, 'empty'> { items: T[]; empty: EmptyStateProps; collectionOptions: UseTreeCollection; @@ -64,7 +68,9 @@ export const withUseTreeCollection = (RelatedTableComp: FC) => { expandNode(node); expandChildren(node); }, - onSortingChange: (event: NonCancelableCustomEvent>) => { + onSortingChange: ( + event: NonCancelableCustomEvent> + ) => { if (onSortingChange) { onSortingChange(event); } @@ -72,7 +78,11 @@ export const withUseTreeCollection = (RelatedTableComp: FC) => { collectionProps.onSortingChange(event); } }, - onSelectionChange: (event: NonCancelableCustomEvent>) => { + onSelectionChange: ( + event: NonCancelableCustomEvent< + TableProps.SelectionChangeDetail + > + ) => { if (onSelectionChange) { onSelectionChange(event); } diff --git a/packages/related-table/src/Hooks/useTreeCollection.ts b/packages/related-table/src/Hooks/useTreeCollection.ts index 59e843344..017dee51a 100644 --- a/packages/related-table/src/Hooks/useTreeCollection.ts +++ b/packages/related-table/src/Hooks/useTreeCollection.ts @@ -1,4 +1,9 @@ -import { FilteringOptions, useCollection, UseCollectionOptions, UseCollectionResult } from '@awsui/collection-hooks'; +import { + FilteringOptions, + useCollection, + UseCollectionOptions, + UseCollectionResult, +} from '@awsui/collection-hooks'; import { useEffect, useState } from 'react'; import { TableProps } from '@awsui/components-react/table'; import { ITreeNode, TreeMap } from '../Model/TreeNode'; @@ -11,13 +16,15 @@ import { sortTree, } from '../utils'; -export interface UseTreeCollection extends UseCollectionOptions> { +export interface UseTreeCollection + extends UseCollectionOptions> { keyPropertyName: string; parentKeyPropertyName: string; columnDefinitions: ReadonlyArray>; } -export interface UseTreeCollectionResult extends UseCollectionResult> { +export interface UseTreeCollectionResult + extends UseCollectionResult> { expandNode: (node: ITreeNode) => void; reset: () => void; } @@ -27,27 +34,48 @@ export const useTreeCollection = ( props: UseTreeCollection, expanded = false ): UseTreeCollectionResult => { - const { keyPropertyName, parentKeyPropertyName, columnDefinitions, ...collectionProps } = props; + const { + keyPropertyName, + parentKeyPropertyName, + columnDefinitions, + ...collectionProps + } = props; const [treeMap, setTreeMap] = useState>(new Map()); const [nodes, setNodes] = useState[]>([]); const [sortState, setSortState] = useState>({ ...(collectionProps.sorting?.defaultState || {}), } as TableProps.SortingState); const [columnsDefinitions] = useState(columnDefinitions); - const [nodesExpanded, addNodesExpanded] = useState<{ [key: string]: boolean }>({}); + const [nodesExpanded, addNodesExpanded] = useState<{ + [key: string]: boolean; + }>({}); useEffect(() => { - const treeNodes = buildTreeNodes(items, treeMap, keyPropertyName, parentKeyPropertyName); + const treeNodes = buildTreeNodes( + items, + treeMap, + keyPropertyName, + parentKeyPropertyName + ); sortTree(treeNodes, sortState, columnsDefinitions); // only builds prefix after building and sorting the tree const tree = buildTreePrefix(treeNodes); setNodes(flatTree(tree)); - }, [items, keyPropertyName, parentKeyPropertyName, sortState, columnsDefinitions, treeMap]); + }, [ + items, + keyPropertyName, + parentKeyPropertyName, + sortState, + columnsDefinitions, + treeMap, + ]); const expandNode = (node: ITreeNode) => { if (node) { const key = (node as any)[keyPropertyName]; - const internalNode = nodes.find((n) => (n as any)[keyPropertyName] === key); + const internalNode = nodes.find( + (n) => (n as any)[keyPropertyName] === key + ); if (internalNode) { internalNode.toggleExpandCollapse(); expandOrCollapseChildren(internalNode, treeMap, keyPropertyName); @@ -69,7 +97,11 @@ export const useTreeCollection = ( sorting: undefined, // disable useCollection sort in favor of sortTree filtering: { ...(collectionProps.filtering || {}), - filteringFunction: (item: ITreeNode, filteringText: string, filteringFields?: string[]) => + filteringFunction: ( + item: ITreeNode, + filteringText: string, + filteringFields?: string[] + ) => filteringFunction( item as ITreeNode>, filteringText, @@ -110,7 +142,8 @@ export const useTreeCollection = ( sortingDescending: sortState.isDescending, onSortingChange: (event: CustomEvent>) => { setSortState(event.detail); - const customOnSortingChange = collectionResult.collectionProps.onSortingChange; + const customOnSortingChange = + collectionResult.collectionProps.onSortingChange; if (customOnSortingChange) { customOnSortingChange(event); } diff --git a/packages/related-table/src/Model/TreeNode.ts b/packages/related-table/src/Model/TreeNode.ts index d7511bce9..5152de2d7 100644 --- a/packages/related-table/src/Model/TreeNode.ts +++ b/packages/related-table/src/Model/TreeNode.ts @@ -111,7 +111,9 @@ class InternalTreeNode { for (let i = parentLastChildPath.length - 1; i >= 1; i -= 1) { const isParentLastChild = parentLastChildPath[i]; - const treeLineMode = isParentLastChild ? LinePrefixTypes.ChildOfLastChild : LinePrefixTypes.ChildOfMiddleChild; + const treeLineMode = isParentLastChild + ? LinePrefixTypes.ChildOfLastChild + : LinePrefixTypes.ChildOfMiddleChild; prefix.splice(1, 0, treeLineMode); } @@ -120,4 +122,7 @@ class InternalTreeNode { } export type ITreeNode = InternalTreeNode & T; -export const TreeNode = InternalTreeNode as new (props: T, metadata?: Metadata) => ITreeNode; +export const TreeNode = InternalTreeNode as new ( + props: T, + metadata?: Metadata +) => ITreeNode; diff --git a/packages/related-table/src/RelatedTable/ButtonWithTreeLines.tsx b/packages/related-table/src/RelatedTable/ButtonWithTreeLines.tsx index fe750e7a6..62a9ddc8a 100644 --- a/packages/related-table/src/RelatedTable/ButtonWithTreeLines.tsx +++ b/packages/related-table/src/RelatedTable/ButtonWithTreeLines.tsx @@ -1,6 +1,11 @@ import Button from '@awsui/components-react/button'; import React, { ReactNode, memo } from 'react'; -import { EmptySpace, LeftPad, Wrapper, ButtonWrapper } from './Common/StyledComponents'; +import { + EmptySpace, + LeftPad, + Wrapper, + ButtonWrapper, +} from './Common/StyledComponents'; import { ExpandableTableNodeStatus, ITreeNode } from '../Model/TreeNode'; import { createPrefixLines, Theme } from './Common/TreeLines'; import { THEME } from '../config'; @@ -23,7 +28,10 @@ export interface ButtonWithTreeLinesProps { function createToggleButton(props: ButtonWithTreeLinesProps) { const { node, onClick, alwaysExpanded } = props; - const icon = node.isExpanded() || alwaysExpanded ? 'treeview-collapse' : 'treeview-expand'; + const icon = + node.isExpanded() || alwaysExpanded + ? 'treeview-collapse' + : 'treeview-expand'; return node.getChildren().length > 0 || node.hasChildren ? (