From 3d6f458e8987a7c61b9b4d931426f33ef016a3bd Mon Sep 17 00:00:00 2001 From: db <77755322+diehbria@users.noreply.github.com> Date: Tue, 4 Jan 2022 17:27:06 -0800 Subject: [PATCH] feat: api simplification of requestSettings (#27) --- packages/components/src/components.d.ts | 34 +-- .../iot-bar-chart/iot-bar-chart.tsx | 20 +- .../iot-connector/iot-connector.spec.ts | 2 +- .../iot-connector/iot-connector.tsx | 16 +- .../src/components/iot-kpi/iot-kpi.tsx | 20 +- .../iot-line-chart/iot-line-chart.tsx | 22 +- .../iot-scatter-chart/iot-scatter-chart.tsx | 20 +- .../iot-status-grid/iot-status-grid.tsx | 20 +- .../iot-status-timeline.tsx | 21 +- .../src/components/iot-table/iot-table.tsx | 20 +- .../src/testing/createMockSource.ts | 2 +- .../testing/testing-ground/siteWiseQueries.ts | 16 +- .../testing/testing-ground/testing-ground.tsx | 26 +- packages/core/src/asset-modules/mocks.spec.ts | 40 +-- .../assetTreeModule.spec.ts | 18 +- .../assetTreeSession.spec.ts | 140 +++++----- .../sitewise-asset-tree/assetTreeSession.ts | 63 +++-- .../sitewise-asset-tree/types.ts | 36 +-- .../src/asset-modules/sitewise/cache.spec.ts | 26 +- .../core/src/asset-modules/sitewise/cache.ts | 18 +- .../sitewise/requestProcessor.spec.ts | 4 +- .../sitewise/requestProcessor.ts | 104 ++++---- .../sitewise/requestProcessorWorkerGroup.ts | 8 +- .../src/asset-modules/sitewise/session.ts | 12 +- .../core/src/asset-modules/sitewise/types.ts | 27 +- .../data-module/IotAppKitDataModule.spec.ts | 181 ++++++++----- .../src/data-module/IotAppKitDataModule.ts | 52 ++-- .../data-module/data-cache/caching/caching.ts | 4 +- .../data-cache/dataCacheWrapped.spec.ts | 4 +- .../data-cache/dataCacheWrapped.ts | 4 +- .../data-module/data-cache/requestTypes.ts | 38 +-- .../data-source-store/dataSourceStore.spec.ts | 10 +- .../data-source-store/dataSourceStore.ts | 13 +- packages/core/src/data-module/index.ts | 3 - .../subscriptionStore.spec.ts | 8 +- .../subscription-store/subscriptionStore.ts | 12 +- packages/core/src/data-module/types.d.ts | 46 ++-- .../site-wise-legacy/data-source.ts | 4 +- .../site-wise/client/client.spec.ts | 18 +- .../client/getAggregatedPropertyDataPoints.ts | 49 ++-- .../client/getHistoricalPropertyDataPoints.ts | 6 +- .../client/getLatestPropertyDataPoint.ts | 2 +- .../site-wise/data-source.spec.ts | 249 +++++++----------- .../src/data-sources/site-wise/data-source.ts | 173 ++++++------ .../site-wise/dataStreamFromSiteWise.ts | 10 +- .../src/data-sources/site-wise/types.d.ts | 8 +- .../data-sources/site-wise/util/resolution.ts | 6 +- 47 files changed, 861 insertions(+), 774 deletions(-) diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index 1295cd8ab..bfdae1443 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -5,7 +5,7 @@ * It contains typing information for all components that exist in this project. */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; -import { AnyDataStreamQuery, AssetSummaryQuery, AssetTreeSubscription, DataModule, Request, RequestConfig, SiteWiseAssetTreeQuery } from "@iot-app-kit/core"; +import { AnyDataStreamQuery, AssetSummaryQuery, AssetTreeSubscription, DataModule, SiteWiseAssetTreeQuery, TimeSeriesDataRequest, TimeSeriesDataRequestSettings } from "@iot-app-kit/core"; import { DataStream, MinimalViewPortConfig } from "@synchro-charts/core"; import { TableProps } from "@awsui/components-react/table"; import { EmptyStateProps, ITreeNode, UseTreeCollection } from "@iot-app-kit/related-table"; @@ -23,7 +23,7 @@ export namespace Components { "appKit": DataModule; "isEditing": boolean | undefined; "query": AnyDataStreamQuery; - "requestConfig": RequestConfig | undefined; + "settings": TimeSeriesDataRequestSettings | undefined; "viewport": MinimalViewPortConfig; "widgetId": string; } @@ -31,13 +31,13 @@ export namespace Components { "appKit": DataModule; "query": AnyDataStreamQuery; "renderFunc": ({ dataStreams }: { dataStreams: DataStream[] }) => unknown; - "requestInfo": Request; + "request": TimeSeriesDataRequest; } interface IotKpi { "appKit": DataModule; "isEditing": boolean | undefined; "query": AnyDataStreamQuery; - "requestConfig": RequestConfig | undefined; + "settings": TimeSeriesDataRequestSettings | undefined; "viewport": MinimalViewPortConfig; "widgetId": string; } @@ -45,7 +45,7 @@ export namespace Components { "appKit": DataModule; "isEditing": boolean | undefined; "query": AnyDataStreamQuery; - "requestConfig": RequestConfig | undefined; + "settings": TimeSeriesDataRequestSettings | undefined; "viewport": MinimalViewPortConfig; "widgetId": string; } @@ -53,7 +53,7 @@ export namespace Components { "appKit": DataModule; "isEditing": boolean | undefined; "query": AnyDataStreamQuery; - "requestConfig": RequestConfig | undefined; + "settings": TimeSeriesDataRequestSettings | undefined; "viewport": MinimalViewPortConfig; "widgetId": string; } @@ -61,7 +61,7 @@ export namespace Components { "appKit": DataModule; "isEditing": boolean | undefined; "query": AnyDataStreamQuery; - "requestConfig": RequestConfig | undefined; + "settings": TimeSeriesDataRequestSettings | undefined; "viewport": MinimalViewPortConfig; "widgetId": string; } @@ -69,14 +69,14 @@ export namespace Components { "appKit": DataModule; "isEditing": boolean | undefined; "query": AnyDataStreamQuery; - "requestConfig": RequestConfig | undefined; + "settings": TimeSeriesDataRequestSettings | undefined; "viewport": MinimalViewPortConfig; "widgetId": string; } interface IotTable { "appKit": DataModule; "query": AnyDataStreamQuery; - "requestConfig": RequestConfig | undefined; + "settings": TimeSeriesDataRequestSettings | undefined; "viewport": MinimalViewPortConfig; "widgetId": string; } @@ -242,7 +242,7 @@ declare namespace LocalJSX { "appKit"?: DataModule; "isEditing"?: boolean | undefined; "query"?: AnyDataStreamQuery; - "requestConfig"?: RequestConfig | undefined; + "settings"?: TimeSeriesDataRequestSettings | undefined; "viewport"?: MinimalViewPortConfig; "widgetId"?: string; } @@ -250,13 +250,13 @@ declare namespace LocalJSX { "appKit"?: DataModule; "query"?: AnyDataStreamQuery; "renderFunc"?: ({ dataStreams }: { dataStreams: DataStream[] }) => unknown; - "requestInfo"?: Request; + "request"?: TimeSeriesDataRequest; } interface IotKpi { "appKit"?: DataModule; "isEditing"?: boolean | undefined; "query"?: AnyDataStreamQuery; - "requestConfig"?: RequestConfig | undefined; + "settings"?: TimeSeriesDataRequestSettings | undefined; "viewport"?: MinimalViewPortConfig; "widgetId"?: string; } @@ -264,7 +264,7 @@ declare namespace LocalJSX { "appKit"?: DataModule; "isEditing"?: boolean | undefined; "query"?: AnyDataStreamQuery; - "requestConfig"?: RequestConfig | undefined; + "settings"?: TimeSeriesDataRequestSettings | undefined; "viewport"?: MinimalViewPortConfig; "widgetId"?: string; } @@ -272,7 +272,7 @@ declare namespace LocalJSX { "appKit"?: DataModule; "isEditing"?: boolean | undefined; "query"?: AnyDataStreamQuery; - "requestConfig"?: RequestConfig | undefined; + "settings"?: TimeSeriesDataRequestSettings | undefined; "viewport"?: MinimalViewPortConfig; "widgetId"?: string; } @@ -280,7 +280,7 @@ declare namespace LocalJSX { "appKit"?: DataModule; "isEditing"?: boolean | undefined; "query"?: AnyDataStreamQuery; - "requestConfig"?: RequestConfig | undefined; + "settings"?: TimeSeriesDataRequestSettings | undefined; "viewport"?: MinimalViewPortConfig; "widgetId"?: string; } @@ -288,14 +288,14 @@ declare namespace LocalJSX { "appKit"?: DataModule; "isEditing"?: boolean | undefined; "query"?: AnyDataStreamQuery; - "requestConfig"?: RequestConfig | undefined; + "settings"?: TimeSeriesDataRequestSettings | undefined; "viewport"?: MinimalViewPortConfig; "widgetId"?: string; } interface IotTable { "appKit"?: DataModule; "query"?: AnyDataStreamQuery; - "requestConfig"?: RequestConfig | undefined; + "settings"?: TimeSeriesDataRequestSettings | undefined; "viewport"?: MinimalViewPortConfig; "widgetId"?: string; } diff --git a/packages/components/src/components/iot-bar-chart/iot-bar-chart.tsx b/packages/components/src/components/iot-bar-chart/iot-bar-chart.tsx index a034ad048..a32dd01ad 100644 --- a/packages/components/src/components/iot-bar-chart/iot-bar-chart.tsx +++ b/packages/components/src/components/iot-bar-chart/iot-bar-chart.tsx @@ -1,6 +1,6 @@ import { Component, Prop, h } from '@stencil/core'; import { MinimalViewPortConfig } from '@synchro-charts/core'; -import { AnyDataStreamQuery, DataModule, Request, RequestConfig } from '@iot-app-kit/core'; +import { AnyDataStreamQuery, DataModule, TimeSeriesDataRequestSettings } from '@iot-app-kit/core'; const DEFAULT_VIEWPORT = { duration: 10 * 1000 * 60 }; @@ -19,27 +19,29 @@ export class IotBarChart { @Prop() isEditing: boolean | undefined; - @Prop() requestConfig: RequestConfig | undefined + @Prop() settings: TimeSeriesDataRequestSettings | undefined; - requestInfo(): Request { + getSettings(): TimeSeriesDataRequestSettings { return { - viewport: this.viewport, - onlyFetchLatestValue: false, - requestConfig: this.requestConfig, + ...this.settings, + fetchFromStartToEnd: true, }; } render() { - const requestInfo = this.requestInfo(); + const settings = this.getSettings(); return ( ( diff --git a/packages/components/src/components/iot-connector/iot-connector.spec.ts b/packages/components/src/components/iot-connector/iot-connector.spec.ts index 180eeab0e..d9a88840a 100644 --- a/packages/components/src/components/iot-connector/iot-connector.spec.ts +++ b/packages/components/src/components/iot-connector/iot-connector.spec.ts @@ -31,7 +31,7 @@ const connectorSpecPage = async (propOverrides: Partial source: 'test-mock', assets: [], } as SiteWiseDataStreamQuery, // static casting because of legacy sw - requestInfo: { viewport, onlyFetchLatestValue: true }, + request: { viewport, settings: { fetchMostRecentBeforeEnd: true } }, ...propOverrides, }; update(connector, props); diff --git a/packages/components/src/components/iot-connector/iot-connector.tsx b/packages/components/src/components/iot-connector/iot-connector.tsx index 34509a692..1ca0828c9 100644 --- a/packages/components/src/components/iot-connector/iot-connector.tsx +++ b/packages/components/src/components/iot-connector/iot-connector.tsx @@ -1,7 +1,13 @@ import { Component, Prop, State, Watch } from '@stencil/core'; import { DataStream } from '@synchro-charts/core'; import isEqual from 'lodash.isequal'; -import { Request, AnyDataStreamQuery, SubscriptionUpdate, subscribeToDataStreams, DataModule } from '@iot-app-kit/core'; +import { + AnyDataStreamQuery, + SubscriptionUpdate, + subscribeToDataStreams, + DataModule, + TimeSeriesDataRequest, +} from '@iot-app-kit/core'; @Component({ tag: 'iot-connector', @@ -12,7 +18,7 @@ export class IotConnector { @Prop() query: AnyDataStreamQuery; - @Prop() requestInfo: Request; + @Prop() request: TimeSeriesDataRequest; @Prop() renderFunc: ({ dataStreams }: { dataStreams: DataStream[] }) => unknown; @@ -28,7 +34,7 @@ export class IotConnector { this.appKit, { query: this.query, - requestInfo: this.requestInfo, + request: this.request, }, (dataStreams: DataStream[]) => { this.dataStreams = dataStreams; @@ -47,13 +53,13 @@ export class IotConnector { /** * Sync subscription to change in queried data */ - @Watch('requestInfo') + @Watch('request') @Watch('query') onUpdateProp(newProp: unknown, oldProp: unknown) { if (!isEqual(newProp, oldProp) && this.update != null) { this.update({ query: this.query, - requestInfo: this.requestInfo, + request: this.request, }); } } diff --git a/packages/components/src/components/iot-kpi/iot-kpi.tsx b/packages/components/src/components/iot-kpi/iot-kpi.tsx index 5dff79e3d..e9cd9db0a 100644 --- a/packages/components/src/components/iot-kpi/iot-kpi.tsx +++ b/packages/components/src/components/iot-kpi/iot-kpi.tsx @@ -1,6 +1,6 @@ import { Component, Prop, h } from '@stencil/core'; import { MinimalViewPortConfig } from '@synchro-charts/core'; -import { AnyDataStreamQuery, DataModule, RequestConfig } from '@iot-app-kit/core'; +import { AnyDataStreamQuery, DataModule, TimeSeriesDataRequestSettings } from '@iot-app-kit/core'; const DEFAULT_VIEWPORT = { duration: 10 * 1000 }; @@ -19,27 +19,29 @@ export class IotKpi { @Prop() isEditing: boolean | undefined; - @Prop() requestConfig: RequestConfig | undefined + @Prop() settings: TimeSeriesDataRequestSettings | undefined; - requestInfo() { + getSettings(): TimeSeriesDataRequestSettings { return { - viewport: this.viewport, - onlyFetchLatestValue: true, - requestConfig: this.requestConfig, + ...this.settings, + fetchMostRecentBeforeEnd: true, }; } render() { - const requestInfo = this.requestInfo(); + const settings = this.getSettings(); return ( ( diff --git a/packages/components/src/components/iot-line-chart/iot-line-chart.tsx b/packages/components/src/components/iot-line-chart/iot-line-chart.tsx index 3389053dd..4e29a19ea 100644 --- a/packages/components/src/components/iot-line-chart/iot-line-chart.tsx +++ b/packages/components/src/components/iot-line-chart/iot-line-chart.tsx @@ -1,6 +1,6 @@ import { Component, Prop, h } from '@stencil/core'; import { MinimalViewPortConfig } from '@synchro-charts/core'; -import { AnyDataStreamQuery, DataModule, Request, RequestConfig } from '@iot-app-kit/core'; +import { AnyDataStreamQuery, DataModule, TimeSeriesDataRequestSettings } from '@iot-app-kit/core'; const DEFAULT_VIEWPORT = { duration: 10 * 1000 * 60 }; @@ -19,27 +19,31 @@ export class IotLineChart { @Prop() isEditing: boolean | undefined; - @Prop() requestConfig: RequestConfig | undefined + @Prop() settings: TimeSeriesDataRequestSettings | undefined; - requestInfo(): Request { + getSettings(): TimeSeriesDataRequestSettings { return { - viewport: this.viewport, - onlyFetchLatestValue: false, - requestConfig: this.requestConfig, + ...this.settings, + fetchFromStartToEnd: true, + // Required to be able to draw line from last point visible, to first point before the viewport. + fetchMostRecentBeforeStart: true, }; } render() { - const requestInfo = this.requestInfo(); + const settings = this.getSettings(); return ( ( diff --git a/packages/components/src/components/iot-scatter-chart/iot-scatter-chart.tsx b/packages/components/src/components/iot-scatter-chart/iot-scatter-chart.tsx index 3542ff0de..5f1d9cbe3 100644 --- a/packages/components/src/components/iot-scatter-chart/iot-scatter-chart.tsx +++ b/packages/components/src/components/iot-scatter-chart/iot-scatter-chart.tsx @@ -1,6 +1,6 @@ import { Component, Prop, h } from '@stencil/core'; import { MinimalViewPortConfig } from '@synchro-charts/core'; -import { AnyDataStreamQuery, DataModule, Request, RequestConfig } from '@iot-app-kit/core'; +import { AnyDataStreamQuery, DataModule, TimeSeriesDataRequestSettings } from '@iot-app-kit/core'; const DEFAULT_VIEWPORT = { duration: 10 * 1000 * 60 }; @@ -19,27 +19,29 @@ export class IotScatterChart { @Prop() isEditing: boolean | undefined; - @Prop() requestConfig: RequestConfig | undefined + @Prop() settings: TimeSeriesDataRequestSettings | undefined; - requestInfo(): Request { + getSettings(): TimeSeriesDataRequestSettings { return { - viewport: this.viewport, - onlyFetchLatestValue: false, - requestConfig: this.requestConfig, + ...this.settings, + fetchFromStartToEnd: true, }; } render() { - const requestInfo = this.requestInfo(); + const settings = this.getSettings(); return ( ( diff --git a/packages/components/src/components/iot-status-grid/iot-status-grid.tsx b/packages/components/src/components/iot-status-grid/iot-status-grid.tsx index 5a0ac49ce..d42d3518e 100644 --- a/packages/components/src/components/iot-status-grid/iot-status-grid.tsx +++ b/packages/components/src/components/iot-status-grid/iot-status-grid.tsx @@ -1,6 +1,6 @@ import { Component, Prop, h } from '@stencil/core'; import { MinimalViewPortConfig } from '@synchro-charts/core'; -import { AnyDataStreamQuery, DataModule, Request, RequestConfig } from '@iot-app-kit/core'; +import { AnyDataStreamQuery, DataModule, TimeSeriesDataRequestSettings } from '@iot-app-kit/core'; const DEFAULT_VIEWPORT = { duration: 10 * 1000 * 60 }; @@ -19,27 +19,29 @@ export class IotStatusGrid { @Prop() isEditing: boolean | undefined; - @Prop() requestConfig: RequestConfig | undefined + @Prop() settings: TimeSeriesDataRequestSettings | undefined; - requestInfo(): Request { + getSettings(): TimeSeriesDataRequestSettings { return { - viewport: this.viewport, - onlyFetchLatestValue: true, - requestConfig: this.requestConfig, + ...this.settings, + fetchMostRecentBeforeEnd: true, }; } render() { - const requestInfo = this.requestInfo(); + const settings = this.getSettings(); return ( ( diff --git a/packages/components/src/components/iot-status-timeline/iot-status-timeline.tsx b/packages/components/src/components/iot-status-timeline/iot-status-timeline.tsx index fda532cde..ab774e301 100644 --- a/packages/components/src/components/iot-status-timeline/iot-status-timeline.tsx +++ b/packages/components/src/components/iot-status-timeline/iot-status-timeline.tsx @@ -1,6 +1,6 @@ import { Component, Prop, h } from '@stencil/core'; import { MinimalViewPortConfig } from '@synchro-charts/core'; -import { AnyDataStreamQuery, DataModule, Request, RequestConfig } from '@iot-app-kit/core'; +import { AnyDataStreamQuery, DataModule, TimeSeriesDataRequestSettings } from '@iot-app-kit/core'; const DEFAULT_VIEWPORT = { duration: 10 * 1000 * 60 }; @@ -19,27 +19,30 @@ export class IotStatusTimeline { @Prop() isEditing: boolean | undefined; - @Prop() requestConfig: RequestConfig | undefined + @Prop() settings: TimeSeriesDataRequestSettings | undefined; - requestInfo(): Request { + getSettings(): TimeSeriesDataRequestSettings { return { - viewport: this.viewport, - onlyFetchLatestValue: false, - requestConfig: this.requestConfig, + ...this.settings, + fetchMostRecentBeforeStart: true, + fetchFromStartToEnd: true, }; } render() { - const requestInfo = this.requestInfo(); + const settings = this.getSettings(); return ( ( diff --git a/packages/components/src/components/iot-table/iot-table.tsx b/packages/components/src/components/iot-table/iot-table.tsx index 1f712268d..08c6a235d 100644 --- a/packages/components/src/components/iot-table/iot-table.tsx +++ b/packages/components/src/components/iot-table/iot-table.tsx @@ -1,6 +1,6 @@ import { Component, Prop, h } from '@stencil/core'; import { MinimalViewPortConfig } from '@synchro-charts/core'; -import { AnyDataStreamQuery, DataModule, Request, RequestConfig } from '@iot-app-kit/core'; +import { AnyDataStreamQuery, DataModule, TimeSeriesDataRequestSettings } from '@iot-app-kit/core'; const DEFAULT_VIEWPORT = { duration: 10 * 1000 * 60 }; @@ -17,25 +17,27 @@ export class IotTable { @Prop() widgetId: string; - @Prop() requestConfig: RequestConfig | undefined + @Prop() settings: TimeSeriesDataRequestSettings | undefined; - requestInfo(): Request { + getSettings(): TimeSeriesDataRequestSettings { return { - viewport: this.viewport, - onlyFetchLatestValue: true, - requestConfig: this.requestConfig, + ...this.settings, + fetchMostRecentBeforeEnd: true, }; } render() { - const requestInfo = this.requestInfo(); + const settings = this.getSettings(); return ( ( - + )} /> ); diff --git a/packages/components/src/testing/createMockSource.ts b/packages/components/src/testing/createMockSource.ts index 53a67c6bb..7b666bde4 100644 --- a/packages/components/src/testing/createMockSource.ts +++ b/packages/components/src/testing/createMockSource.ts @@ -11,7 +11,7 @@ const dataStreamIds = (query: SiteWiseDataStreamQuery) => export const createMockSource = (dataStreams: DataStream[]): DataSource => ({ name: 'test-mock', initiateRequest: ({ onSuccess }: DataSourceRequest) => onSuccess(dataStreams), - getRequestsFromQuery: ({ query, requestInfo }) => + getRequestsFromQuery: ({ query }) => dataStreams .filter(({ id }) => dataStreamIds(query).includes(id)) .map(({ data, aggregates, ...dataStreamInfo }) => dataStreamInfo), diff --git a/packages/components/src/testing/testing-ground/siteWiseQueries.ts b/packages/components/src/testing/testing-ground/siteWiseQueries.ts index b24732fb7..968a9b5a8 100644 --- a/packages/components/src/testing/testing-ground/siteWiseQueries.ts +++ b/packages/components/src/testing/testing-ground/siteWiseQueries.ts @@ -33,13 +33,15 @@ const AGGREGATED_DATA_PROPERTY_2 = '11d2599a-2547-451d-ab79-a47f878dbbe3'; export const AGGREGATED_DATA_QUERY = { source: 'site-wise', - assets: [{ - assetId: AGGREGATED_DATA_ASSET, - properties: [ - { propertyId: AGGREGATED_DATA_PROPERTY }, - { propertyId: AGGREGATED_DATA_PROPERTY_2, resolution: '1m' } - ] - }], + assets: [ + { + assetId: AGGREGATED_DATA_ASSET, + properties: [ + { propertyId: AGGREGATED_DATA_PROPERTY }, + { propertyId: AGGREGATED_DATA_PROPERTY_2, resolution: '1m' }, + ], + }, + ], }; // From demo turbine asset, found at https://p-rlvy2rj8.app.iotsitewise.aws/ diff --git a/packages/components/src/testing/testing-ground/testing-ground.tsx b/packages/components/src/testing/testing-ground/testing-ground.tsx index 2bdf9c5a9..2e8bb40b2 100755 --- a/packages/components/src/testing/testing-ground/testing-ground.tsx +++ b/packages/components/src/testing/testing-ground/testing-ground.tsx @@ -17,7 +17,7 @@ const THREE_MINUTES = 1000 * 60 * 3; const DEFAULT_RESOLUTION_MAPPING = { [THREE_MINUTES]: '1m', -} +}; @Component({ tag: 'testing-ground', @@ -35,20 +35,20 @@ export class TestingGround { private changeResolution = (ev: Event) => { const resolution = (ev.target as HTMLSelectElement)?.value; - if (resolution === 'auto'){ + if (resolution === 'auto') { this.resolution = DEFAULT_RESOLUTION_MAPPING; } else if (resolution === '0') { this.resolution = {}; } else { this.resolution = resolution; } - } + }; private changeDuration = (ev: Event) => { const duration = `${(ev.target as HTMLSelectElement)?.value}m`; this.viewport = { duration }; - } + }; render() { return ( @@ -106,22 +106,28 @@ export class TestingGround { /> - resolution: - + - viewport: - +
diff --git a/packages/core/src/asset-modules/mocks.spec.ts b/packages/core/src/asset-modules/mocks.spec.ts index e5e27b208..e36ede047 100644 --- a/packages/core/src/asset-modules/mocks.spec.ts +++ b/packages/core/src/asset-modules/mocks.spec.ts @@ -1,14 +1,16 @@ import { - AssetHierarchyQuery, assetHierarchyQueryKey, + AssetHierarchyQuery, + assetHierarchyQueryKey, AssetModelQuery, AssetPropertyValueQuery, AssetSummaryQuery, HierarchyAssetSummaryList, isAssetHierarchyQuery, isAssetModelQuery, - isAssetPropertyValueQuery, isAssetSummaryQuery, + isAssetPropertyValueQuery, + isAssetSummaryQuery, SiteWiseAssetModuleInterface, - SiteWiseAssetSessionInterface + SiteWiseAssetSessionInterface, } from './sitewise/types'; import { AssetState, DescribeAssetModelResponse, DescribeAssetResponse, Quality } from '@aws-sdk/client-iotsitewise'; import { Observable, Subscription } from 'rxjs'; @@ -91,23 +93,25 @@ export class MockSiteWiseAssetsReplayData { public assets: Map = new Map(); public addAssetModels(newModels: DescribeAssetModelResponse[]) { - newModels.forEach(model => this.models.set(model.assetModelId as string, model)); + newModels.forEach((model) => this.models.set(model.assetModelId as string, model)); } public addAssetSummaries(newAssetSummaries: AssetSummary[]) { - newAssetSummaries.forEach(summary => this.assets.set(summary.id as string, summary)); + newAssetSummaries.forEach((summary) => this.assets.set(summary.id as string, summary)); } - public addAssetPropertyValues(propertyValue: {assetId: string, propertyId: string, value: AssetPropertyValue}) { + public addAssetPropertyValues(propertyValue: { assetId: string; propertyId: string; value: AssetPropertyValue }) { this.properties.set(propertyValue.assetId + ':' + propertyValue.propertyId, propertyValue.value); } - public addHierarchyAssetSummaryList(query: AssetHierarchyQuery, newHierarchyAssetSummaryList: HierarchyAssetSummaryList) { + public addHierarchyAssetSummaryList( + query: AssetHierarchyQuery, + newHierarchyAssetSummaryList: HierarchyAssetSummaryList + ) { this.hierarchies.set(assetHierarchyQueryKey(query), newHierarchyAssetSummaryList); } } - export class MockSiteWiseAssetSession implements SiteWiseAssetSessionInterface { private readonly replayData: MockSiteWiseAssetsReplayData; @@ -119,11 +123,14 @@ export class MockSiteWiseAssetSession implements SiteWiseAssetSessionInterface { addRequest(query: AssetPropertyValueQuery, observer: (assetPropertyValue: AssetPropertyValue) => void): Subscription; addRequest(query: AssetHierarchyQuery, observer: (assetSummary: HierarchyAssetSummaryList) => void): Subscription; addRequest(query: AssetSummaryQuery, observer: (assetSummary: AssetSummary) => void): Subscription; - addRequest(query: AssetModelQuery | AssetPropertyValueQuery | AssetHierarchyQuery | AssetSummaryQuery, - observer: ((assetModel: DescribeAssetModelResponse) => void) - | ((assetPropertyValue: AssetPropertyValue) => void) - | ((assetSummary: HierarchyAssetSummaryList) => void) - | ((assetSummary: AssetSummary) => void)): Subscription { + addRequest( + query: AssetModelQuery | AssetPropertyValueQuery | AssetHierarchyQuery | AssetSummaryQuery, + observer: + | ((assetModel: DescribeAssetModelResponse) => void) + | ((assetPropertyValue: AssetPropertyValue) => void) + | ((assetSummary: HierarchyAssetSummaryList) => void) + | ((assetSummary: AssetSummary) => void) + ): Subscription { let observable: Observable; if (isAssetModelQuery(query)) { observable = new Observable((observer) => { @@ -148,8 +155,7 @@ export class MockSiteWiseAssetSession implements SiteWiseAssetSessionInterface { return observable.subscribe(observer); } - close(): void { - } + close(): void {} } export class MockSiteWiseAssetModule implements SiteWiseAssetModuleInterface { @@ -164,4 +170,6 @@ export class MockSiteWiseAssetModule implements SiteWiseAssetModuleInterface { } } -it('no-op', () => { expect(true).toBeTruthy()}); +it('no-op', () => { + expect(true).toBeTruthy(); +}); diff --git a/packages/core/src/asset-modules/sitewise-asset-tree/assetTreeModule.spec.ts b/packages/core/src/asset-modules/sitewise-asset-tree/assetTreeModule.spec.ts index 94ded81ce..c202d9945 100644 --- a/packages/core/src/asset-modules/sitewise-asset-tree/assetTreeModule.spec.ts +++ b/packages/core/src/asset-modules/sitewise-asset-tree/assetTreeModule.spec.ts @@ -4,24 +4,20 @@ import { HIERARCHY_ROOT_ID, HierarchyAssetSummaryList, LoadingStateEnum } from ' it('initializes', () => { expect( - () => - new SiteWiseAssetTreeModule(new MockSiteWiseAssetModule(new MockSiteWiseAssetsReplayData())) + () => new SiteWiseAssetTreeModule(new MockSiteWiseAssetModule(new MockSiteWiseAssetsReplayData())) ).not.toThrowError(); }); it('returns a session', () => { let replayData = new MockSiteWiseAssetsReplayData(); - let testData:HierarchyAssetSummaryList = { + let testData: HierarchyAssetSummaryList = { assetHierarchyId: HIERARCHY_ROOT_ID, assets: [sampleAssetSummary], - loadingState: LoadingStateEnum.LOADED - } - replayData.addHierarchyAssetSummaryList({assetHierarchyId: HIERARCHY_ROOT_ID}, testData); + loadingState: LoadingStateEnum.LOADED, + }; + replayData.addHierarchyAssetSummaryList({ assetHierarchyId: HIERARCHY_ROOT_ID }, testData); replayData.addAssetSummaries([sampleAssetSummary]); - expect( - () => - new SiteWiseAssetTreeModule(new MockSiteWiseAssetModule(replayData)) - .startSession({rootAssetId: undefined}) + expect(() => + new SiteWiseAssetTreeModule(new MockSiteWiseAssetModule(replayData)).startSession({ rootAssetId: undefined }) ).not.toBeUndefined(); }); - diff --git a/packages/core/src/asset-modules/sitewise-asset-tree/assetTreeSession.spec.ts b/packages/core/src/asset-modules/sitewise-asset-tree/assetTreeSession.spec.ts index b2ffd28c9..7546c737f 100644 --- a/packages/core/src/asset-modules/sitewise-asset-tree/assetTreeSession.spec.ts +++ b/packages/core/src/asset-modules/sitewise-asset-tree/assetTreeSession.spec.ts @@ -17,37 +17,36 @@ import { it('initializes', () => { let replayData = new MockSiteWiseAssetsReplayData(); - let testData:HierarchyAssetSummaryList = { + let testData: HierarchyAssetSummaryList = { assetHierarchyId: HIERARCHY_ROOT_ID, assets: [sampleAssetSummary], - loadingState: LoadingStateEnum.LOADED - } - replayData.addHierarchyAssetSummaryList({assetHierarchyId: HIERARCHY_ROOT_ID}, testData); + loadingState: LoadingStateEnum.LOADED, + }; + replayData.addHierarchyAssetSummaryList({ assetHierarchyId: HIERARCHY_ROOT_ID }, testData); replayData.addAssetSummaries([sampleAssetSummary]); expect( - () => - new SiteWiseAssetTreeSession(new MockSiteWiseAssetSession(replayData), - {rootAssetId: ''}) + () => new SiteWiseAssetTreeSession(new MockSiteWiseAssetSession(replayData), { rootAssetId: '' }) ).not.toThrowError(); }); describe('root loading functionality', () => { let replayData = new MockSiteWiseAssetsReplayData(); let rootAsset: AssetSummary = { ...sampleAssetSummary }; - rootAsset.hierarchies = [{id: 'bananas1234', name: 'bananas'}]; + rootAsset.hierarchies = [{ id: 'bananas1234', name: 'bananas' }]; let rootHierarchy: HierarchyAssetSummaryList = { assetHierarchyId: HIERARCHY_ROOT_ID, assets: [rootAsset], - loadingState: LoadingStateEnum.NOT_LOADED - } - replayData.addHierarchyAssetSummaryList({assetHierarchyId: HIERARCHY_ROOT_ID}, rootHierarchy); + loadingState: LoadingStateEnum.NOT_LOADED, + }; + replayData.addHierarchyAssetSummaryList({ assetHierarchyId: HIERARCHY_ROOT_ID }, rootHierarchy); replayData.addAssetSummaries([rootAsset]); - it('When you subscribe the root is returned', done => { - const session:SiteWiseAssetTreeSession = new SiteWiseAssetTreeSession(new MockSiteWiseAssetSession(replayData), - {rootAssetId: ''}); - session.subscribe(treeRoot => { + it('When you subscribe the root is returned', (done) => { + const session: SiteWiseAssetTreeSession = new SiteWiseAssetTreeSession(new MockSiteWiseAssetSession(replayData), { + rootAssetId: '', + }); + session.subscribe((treeRoot) => { if (!treeRoot || treeRoot.length == 0) { return; } @@ -55,13 +54,17 @@ describe('root loading functionality', () => { expect(treeRoot[0]?.asset).toEqual(rootAsset); expect(treeRoot[0]?.hierarchies.size).toEqual(1); expect(treeRoot[0]?.hierarchies.get('bananas1234')?.isExpanded).toBeFalse(); - expect(treeRoot[0]?.hierarchies.get('bananas1234')).toEqual({children: [], id: "bananas1234", - isExpanded: false, loadingState: LoadingStateEnum.NOT_LOADED, name: "bananas"}); + expect(treeRoot[0]?.hierarchies.get('bananas1234')).toEqual({ + children: [], + id: 'bananas1234', + isExpanded: false, + loadingState: LoadingStateEnum.NOT_LOADED, + name: 'bananas', + }); - expect(treeRoot[0]?.properties).toBeEmpty() + expect(treeRoot[0]?.properties).toBeEmpty(); done(); }); - }); }); @@ -71,20 +74,20 @@ describe('branch loading functionality', () => { replayData.addAssetSummaries([rootAsset]); // This time the asset has no hierarchis and the loading will stop at just the asset - it('When you subscribe the asset is returned', done => { - const session:SiteWiseAssetTreeSession = new SiteWiseAssetTreeSession(new MockSiteWiseAssetSession(replayData), - {rootAssetId: rootAsset.id}); - session.subscribe(treeRoot => { + it('When you subscribe the asset is returned', (done) => { + const session: SiteWiseAssetTreeSession = new SiteWiseAssetTreeSession(new MockSiteWiseAssetSession(replayData), { + rootAssetId: rootAsset.id, + }); + session.subscribe((treeRoot) => { if (!treeRoot || treeRoot.length == 0) { return; } expect(treeRoot.length).toEqual(1); expect(treeRoot[0]?.asset).toEqual(rootAsset); expect(treeRoot[0]?.hierarchies.size).toEqual(0); - expect(treeRoot[0]?.properties).toBeEmpty() + expect(treeRoot[0]?.properties).toBeEmpty(); done(); }); - }); }); @@ -95,16 +98,18 @@ describe('model loading', () => { let rootHierarchy: HierarchyAssetSummaryList = { assetHierarchyId: HIERARCHY_ROOT_ID, assets: [rootAsset], - loadingState: LoadingStateEnum.NOT_LOADED - } - replayData.addHierarchyAssetSummaryList({assetHierarchyId: HIERARCHY_ROOT_ID}, rootHierarchy); + loadingState: LoadingStateEnum.NOT_LOADED, + }; + replayData.addHierarchyAssetSummaryList({ assetHierarchyId: HIERARCHY_ROOT_ID }, rootHierarchy); replayData.addAssetSummaries([rootAsset]); replayData.addAssetModels([sampleAssetModel]); - it('When you request the model you get the model', done => { - const session:SiteWiseAssetTreeSession = new SiteWiseAssetTreeSession(new MockSiteWiseAssetSession(replayData), - {rootAssetId: '', withModels: true}); - session.subscribe(treeRoot => { + it('When you request the model you get the model', (done) => { + const session: SiteWiseAssetTreeSession = new SiteWiseAssetTreeSession(new MockSiteWiseAssetSession(replayData), { + rootAssetId: '', + withModels: true, + }); + session.subscribe((treeRoot) => { if (!treeRoot || treeRoot.length == 0) { return; } @@ -112,10 +117,9 @@ describe('model loading', () => { expect(treeRoot[0]?.asset).toEqual(rootAsset); expect(treeRoot[0]?.model).toEqual(sampleAssetModel); - expect(treeRoot[0]?.properties).toBeEmpty() + expect(treeRoot[0]?.properties).toBeEmpty(); done(); }); - }); }); @@ -126,9 +130,9 @@ describe('asset property loading', () => { let rootHierarchy: HierarchyAssetSummaryList = { assetHierarchyId: HIERARCHY_ROOT_ID, assets: [rootAsset], - loadingState: LoadingStateEnum.NOT_LOADED - } - replayData.addHierarchyAssetSummaryList({assetHierarchyId: HIERARCHY_ROOT_ID}, rootHierarchy); + loadingState: LoadingStateEnum.NOT_LOADED, + }; + replayData.addHierarchyAssetSummaryList({ assetHierarchyId: HIERARCHY_ROOT_ID }, rootHierarchy); replayData.addAssetSummaries([rootAsset]); const modelWithProperties: DescribeAssetModelResponse = { ...sampleAssetModel }; const sampleProperty: AssetModelProperty = { @@ -137,9 +141,9 @@ describe('asset property loading', () => { name: 'modelNumber', type: { attribute: { - defaultValue: 'Model No. 1234' - } - } + defaultValue: 'Model No. 1234', + }, + }, }; const propertyNotInModel: AssetModelProperty = { dataType: PropertyDataType.STRING, @@ -147,9 +151,9 @@ describe('asset property loading', () => { name: 'propertyNotInModel', type: { attribute: { - defaultValue: 'Bogons' - } - } + defaultValue: 'Bogons', + }, + }, }; modelWithProperties.assetModelProperties = [sampleProperty]; replayData.addAssetModels([modelWithProperties]); @@ -163,19 +167,22 @@ describe('asset property loading', () => { value: expectedPropertyValue, }); const badPropertyValue: AssetPropertyValue = { - value: { stringValue: "This should never get loaded" }, + value: { stringValue: 'This should never get loaded' }, timestamp: { timeInSeconds: 12345, offsetInNanos: 0 }, - } + }; replayData.addAssetPropertyValues({ assetId: rootAsset.id as string, propertyId: propertyNotInModel.id as string, - value: badPropertyValue + value: badPropertyValue, }); - it('When you request a property and it exists it is attached to the asset node', done => { - const session:SiteWiseAssetTreeSession = new SiteWiseAssetTreeSession(new MockSiteWiseAssetSession(replayData), - {rootAssetId: '', withModels: true, propertyIds: ['propertyNotInModel.id.1234', 'modelNumber.id.1234']}); - session.subscribe(treeRoot => { + it('When you request a property and it exists it is attached to the asset node', (done) => { + const session: SiteWiseAssetTreeSession = new SiteWiseAssetTreeSession(new MockSiteWiseAssetSession(replayData), { + rootAssetId: '', + withModels: true, + propertyIds: ['propertyNotInModel.id.1234', 'modelNumber.id.1234'], + }); + session.subscribe((treeRoot) => { if (!treeRoot || treeRoot.length == 0) { return; } @@ -192,7 +199,7 @@ describe('asset property loading', () => { describe('expand functionality', () => { let replayData = new MockSiteWiseAssetsReplayData(); let rootAsset: AssetSummary = { ...sampleAssetSummary }; - rootAsset.hierarchies = [{id: 'bananas1234', name: 'bananas'}]; + rootAsset.hierarchies = [{ id: 'bananas1234', name: 'bananas' }]; let bananaOne: AssetSummary = { ...sampleAssetSummary }; bananaOne.id = bananaOne.name = 'bananaOne'; let bananaTwo: AssetSummary = { ...sampleAssetSummary }; @@ -201,25 +208,25 @@ describe('expand functionality', () => { let rootHierarchy: HierarchyAssetSummaryList = { assetHierarchyId: HIERARCHY_ROOT_ID, assets: [rootAsset], - loadingState: LoadingStateEnum.NOT_LOADED - } - replayData.addHierarchyAssetSummaryList({assetHierarchyId: HIERARCHY_ROOT_ID}, rootHierarchy); + loadingState: LoadingStateEnum.NOT_LOADED, + }; + replayData.addHierarchyAssetSummaryList({ assetHierarchyId: HIERARCHY_ROOT_ID }, rootHierarchy); let bananaHierarchy: HierarchyAssetSummaryList = { assetHierarchyId: 'bananas1234', assets: [bananaOne, bananaTwo], - loadingState: LoadingStateEnum.NOT_LOADED - } - replayData.addHierarchyAssetSummaryList({assetId: rootAsset.id, assetHierarchyId: 'bananas1234'}, - bananaHierarchy); + loadingState: LoadingStateEnum.NOT_LOADED, + }; + replayData.addHierarchyAssetSummaryList({ assetId: rootAsset.id, assetHierarchyId: 'bananas1234' }, bananaHierarchy); replayData.addAssetSummaries([rootAsset, bananaOne, bananaTwo]); - it('Expands a hierarchy when requested', done => { - const session: SiteWiseAssetTreeSession = new SiteWiseAssetTreeSession(new MockSiteWiseAssetSession(replayData), - {rootAssetId: ''}); + it('Expands a hierarchy when requested', (done) => { + const session: SiteWiseAssetTreeSession = new SiteWiseAssetTreeSession(new MockSiteWiseAssetSession(replayData), { + rootAssetId: '', + }); session.expand(new BranchReference(rootAsset.id, 'bananas1234')); - session.subscribe(treeRoot => { + session.subscribe((treeRoot) => { if (treeRoot.length == 0) { return; } @@ -237,11 +244,12 @@ describe('expand functionality', () => { }); }); - it('Collapses and expanded hierarchy', done => { - const session: SiteWiseAssetTreeSession = new SiteWiseAssetTreeSession(new MockSiteWiseAssetSession(replayData), - {rootAssetId: ''}); + it('Collapses and expanded hierarchy', (done) => { + const session: SiteWiseAssetTreeSession = new SiteWiseAssetTreeSession(new MockSiteWiseAssetSession(replayData), { + rootAssetId: '', + }); session.collapse(new BranchReference(rootAsset.id, 'bananas1234')); - session.subscribe(treeRoot => { + session.subscribe((treeRoot) => { if (treeRoot.length == 0) { return; } diff --git a/packages/core/src/asset-modules/sitewise-asset-tree/assetTreeSession.ts b/packages/core/src/asset-modules/sitewise-asset-tree/assetTreeSession.ts index fbfd6d578..9cf8ed454 100644 --- a/packages/core/src/asset-modules/sitewise-asset-tree/assetTreeSession.ts +++ b/packages/core/src/asset-modules/sitewise-asset-tree/assetTreeSession.ts @@ -3,7 +3,7 @@ import { BranchReference, HierarchyGroup, SiteWiseAssetTreeNode, - SiteWiseAssetTreeQuery + SiteWiseAssetTreeQuery, } from './types'; import { BehaviorSubject, debounceTime, Subject, Subscription } from 'rxjs'; import { @@ -11,7 +11,7 @@ import { AssetModelQuery, HIERARCHY_ROOT_ID, LoadingStateEnum, - SiteWiseAssetSessionInterface + SiteWiseAssetSessionInterface, } from '../sitewise/types'; import { AssetPropertyValue, AssetSummary, DescribeAssetModelResponse } from '@aws-sdk/client-iotsitewise'; @@ -43,10 +43,11 @@ export class SiteWiseAssetTreeSession { // a map of subscriptions that can be canceled // private readonly expansionSubscriptions: Record = {}; private readonly subject: BehaviorSubject = new BehaviorSubject([]); - private readonly rootBranchRef = new BranchReference(undefined, HIERARCHY_ROOT_ID) + private readonly rootBranchRef = new BranchReference(undefined, HIERARCHY_ROOT_ID); private readonly treeUpdateSubject = new Subject(); - private treeUpdateSubscription: Subscription = this.treeUpdateSubject.pipe(debounceTime(1000/30)) - .subscribe(() =>this.emitTreeUpdate()); + private treeUpdateSubscription: Subscription = this.treeUpdateSubject + .pipe(debounceTime(1000 / 30)) + .subscribe(() => this.emitTreeUpdate()); constructor(assetSession: SiteWiseAssetSessionInterface, query: SiteWiseAssetTreeQuery) { this.assetSession = assetSession; @@ -59,7 +60,7 @@ export class SiteWiseAssetTreeSession { // query starts at the specified root Asset const root = new Branch(); this.branches[this.rootBranchRef.key] = root; - this.assetSession.addRequest({assetId: query.rootAssetId}, assetSummary => { + this.assetSession.addRequest({ assetId: query.rootAssetId }, (assetSummary) => { this.saveAsset(assetSummary); root.assetIds.push(assetSummary.id as string); this.updateTree(); @@ -71,10 +72,16 @@ export class SiteWiseAssetTreeSession { const subscription: Subscription = this.subject.subscribe(observer); return { - unsubscribe: () => { subscription.unsubscribe() }, - expand: branchRef => { this.expand(branchRef) }, - collapse: branchRef => {this.collapse(branchRef)}, - } + unsubscribe: () => { + subscription.unsubscribe(); + }, + expand: (branchRef) => { + this.expand(branchRef); + }, + collapse: (branchRef) => { + this.collapse(branchRef); + }, + }; } public expand(branchRef: BranchReference): void { @@ -86,9 +93,9 @@ export class SiteWiseAssetTreeSession { const hierarchyQuery: AssetHierarchyQuery = { assetId: branchRef.assetId, assetHierarchyId: branchRef.hierarchyId, - } + }; - this.assetSession.addRequest(hierarchyQuery, results => { + this.assetSession.addRequest(hierarchyQuery, (results) => { this.saveExpandedHierarchy(branchRef, results.assets, results.loadingState); this.updateTree(); }); @@ -97,18 +104,18 @@ export class SiteWiseAssetTreeSession { private loadAssetRelatedData(assetNode: AssetNode) { if (!assetNode.asset || !assetNode.asset.id) { - throw "AssetNode is missing a properly specified Asset"; + throw 'AssetNode is missing a properly specified Asset'; } const assetId: string = assetNode.asset.id; // load related Asset Model and any of the requested properties that the Model contains if (this.query.withModels || this.query.propertyIds?.length) { - this.assetSession.addRequest({assetModelId: assetNode.asset.assetModelId} as AssetModelQuery, model => { + this.assetSession.addRequest({ assetModelId: assetNode.asset.assetModelId } as AssetModelQuery, (model) => { assetNode.model = model; this.updateTree(); - this.query.propertyIds?.forEach(propertyId => { + this.query.propertyIds?.forEach((propertyId) => { if (this.containsPropertyId(model, propertyId)) { - this.assetSession.addRequest({assetId: assetId, propertyId: propertyId}, propertyValue => { + this.assetSession.addRequest({ assetId: assetId, propertyId: propertyId }, (propertyValue) => { assetNode.properties.set(propertyId, propertyValue); this.updateTree(); }); @@ -119,7 +126,7 @@ export class SiteWiseAssetTreeSession { } private containsPropertyId(model: DescribeAssetModelResponse, propertyId: string) { - return propertyId && model.assetModelProperties?.some(prop => propertyId === prop.id); + return propertyId && model.assetModelProperties?.some((prop) => propertyId === prop.id); } public collapse(branch: BranchReference): void { @@ -138,7 +145,7 @@ export class SiteWiseAssetTreeSession { let roots: SiteWiseAssetTreeNode[] = []; const rootBranch = this.getBranch(this.rootBranchRef); if (rootBranch) { - roots = rootBranch.assetIds.map(assetId => { + roots = rootBranch.assetIds.map((assetId) => { return this.toAssetTreeNode(this.assetNodes[assetId]); }); } @@ -160,7 +167,7 @@ export class SiteWiseAssetTreeSession { }); // recursively descend all hierarchies that have been loaded: - assetNode.asset.hierarchies?.forEach(hierarchy => { + assetNode.asset.hierarchies?.forEach((hierarchy) => { const branchRef: BranchReference = new BranchReference(assetNode.asset.id as string, hierarchy.id as string); const group: HierarchyGroup = { name: hierarchy.name, @@ -175,7 +182,7 @@ export class SiteWiseAssetTreeSession { group.isExpanded = existingBranch?.isExpanded; group.loadingState = existingBranch?.loadingState; if (existingBranch.isExpanded) { - group.children = existingBranch.assetIds.map(nodeId => this.toAssetTreeNode(this.assetNodes[nodeId])); + group.children = existingBranch.assetIds.map((nodeId) => this.toAssetTreeNode(this.assetNodes[nodeId])); } } }); @@ -203,25 +210,27 @@ export class SiteWiseAssetTreeSession { return existingBranch; } - private saveExpandedHierarchy(branchRef: BranchReference, - childAssets: AssetSummary[], - loadingState: LoadingStateEnum) { + private saveExpandedHierarchy( + branchRef: BranchReference, + childAssets: AssetSummary[], + loadingState: LoadingStateEnum + ) { let existingBranch = this.getBranch(branchRef); if (!existingBranch) { - existingBranch = new Branch() + existingBranch = new Branch(); existingBranch.isExpanded = true; this.putBranch(branchRef, existingBranch); } - const assetIds: string[] = childAssets.map(assetSummary => assetSummary.id) as string[]; + const assetIds: string[] = childAssets.map((assetSummary) => assetSummary.id) as string[]; existingBranch.assetIds = assetIds; existingBranch.loadingState = loadingState; - childAssets.forEach(asset => this.saveAsset(asset)); + childAssets.forEach((asset) => this.saveAsset(asset)); } // create new asset nodes as needed: private saveAsset(asset: AssetSummary): AssetNode { if (!asset.id) { - throw "AssetSummary is missing an id property"; + throw 'AssetSummary is missing an id property'; } let assetNode: AssetNode = this.assetNodes[asset.id]; if (!assetNode) { diff --git a/packages/core/src/asset-modules/sitewise-asset-tree/types.ts b/packages/core/src/asset-modules/sitewise-asset-tree/types.ts index 87349b03e..c6c72810b 100644 --- a/packages/core/src/asset-modules/sitewise-asset-tree/types.ts +++ b/packages/core/src/asset-modules/sitewise-asset-tree/types.ts @@ -3,31 +3,31 @@ import { Subscription } from 'rxjs'; import { LoadingStateEnum } from '../sitewise/types'; export type SiteWiseAssetTreeNode = { - asset: AssetSummary, - model?: DescribeAssetModelResponse, - properties: Map, - hierarchies: Map, + asset: AssetSummary; + model?: DescribeAssetModelResponse; + properties: Map; + hierarchies: Map; }; export type HierarchyGroup = { - id: string, - name: string | undefined, - isExpanded: boolean, - loadingState: LoadingStateEnum, - children: SiteWiseAssetTreeNode[], -} + id: string; + name: string | undefined; + isExpanded: boolean; + loadingState: LoadingStateEnum; + children: SiteWiseAssetTreeNode[]; +}; export type SiteWiseAssetTreeQuery = { - rootAssetId: string | undefined - withModels?: boolean, - propertyIds?: string[] -} + rootAssetId: string | undefined; + withModels?: boolean; + propertyIds?: string[]; +}; export type AssetTreeSubscription = { - unsubscribe: () => void, - expand: (branchRef: BranchReference) => void, - collapse: (branchRef: BranchReference) => void -} + unsubscribe: () => void; + expand: (branchRef: BranchReference) => void; + collapse: (branchRef: BranchReference) => void; +}; export class BranchReference { public readonly assetId: string | undefined; diff --git a/packages/core/src/asset-modules/sitewise/cache.spec.ts b/packages/core/src/asset-modules/sitewise/cache.spec.ts index 212bfdaac..0f862bf71 100644 --- a/packages/core/src/asset-modules/sitewise/cache.spec.ts +++ b/packages/core/src/asset-modules/sitewise/cache.spec.ts @@ -3,14 +3,14 @@ import { LoadingStateEnum } from './types'; import { ASSET_ID, ASSET_MODEL_ID, - ASSET_PROPERTY_ID, HIERARCHY_ID, + ASSET_PROPERTY_ID, + HIERARCHY_ID, sampleAssetDescription, sampleAssetModel, - sampleAssetSummary, samplePropertyValue + sampleAssetSummary, + samplePropertyValue, } from '../mocks.spec'; - - describe('cacheAssetSummary', () => { const cache: SiteWiseAssetCache = new SiteWiseAssetCache(); it('returns empty when the asset summary is not in the cache', () => { @@ -67,22 +67,28 @@ describe('cacheAssetHierarchy', () => { it('returns the cached hierarchy when one is stored', () => { cache.appendHierarchyResults(HIERARCHY_ID, [sampleAssetSummary], LoadingStateEnum.LOADING, 'next1'); - expect(cache.getHierarchy(HIERARCHY_ID)).toEqual({assetIds: [ASSET_ID], + expect(cache.getHierarchy(HIERARCHY_ID)).toEqual({ + assetIds: [ASSET_ID], loadingStage: LoadingStateEnum.LOADING, - paginationToken: 'next1'}); + paginationToken: 'next1', + }); }); it('returns the combined records when a new one is appended', () => { cache.appendHierarchyResults(HIERARCHY_ID, [sampleAssetSummary], LoadingStateEnum.PAUSED, 'next2'); - expect(cache.getHierarchy(HIERARCHY_ID)).toEqual({assetIds: [ASSET_ID, ASSET_ID], + expect(cache.getHierarchy(HIERARCHY_ID)).toEqual({ + assetIds: [ASSET_ID, ASSET_ID], loadingStage: LoadingStateEnum.PAUSED, - paginationToken: 'next2'}); + paginationToken: 'next2', + }); }); it('returns the updated loading state for a hierarchy when it is changed', () => { cache.setHierarchyLoadingState(HIERARCHY_ID, LoadingStateEnum.LOADED); - expect(cache.getHierarchy(HIERARCHY_ID)).toEqual({assetIds: [ASSET_ID, ASSET_ID], + expect(cache.getHierarchy(HIERARCHY_ID)).toEqual({ + assetIds: [ASSET_ID, ASSET_ID], loadingStage: LoadingStateEnum.LOADED, - paginationToken: 'next2'}); + paginationToken: 'next2', + }); }); }); diff --git a/packages/core/src/asset-modules/sitewise/cache.ts b/packages/core/src/asset-modules/sitewise/cache.ts index 76e2b35ac..d6b0e376d 100644 --- a/packages/core/src/asset-modules/sitewise/cache.ts +++ b/packages/core/src/asset-modules/sitewise/cache.ts @@ -47,7 +47,9 @@ export class SiteWiseAssetCache { } } - public storeAssetSummaries(assetSummaryList: DescribeAssetResponse[] | AssociatedAssetsSummary[]): void { + public storeAssetSummaries( + assetSummaryList: DescribeAssetResponse[] | AssociatedAssetsSummary[] + ): void { assetSummaryList.forEach((summary) => { this.storeAssetSummary(summary as AssetSummary); }); @@ -79,8 +81,8 @@ export class SiteWiseAssetCache { return { assetIds: [], loadingStage: LoadingStateEnum.NOT_LOADED, - paginationToken: undefined - } + paginationToken: undefined, + }; } private setupHierarchyCache(hierarchyId: string): CachedAssetSummaryBlock { @@ -92,8 +94,12 @@ export class SiteWiseAssetCache { return storedHierarchy; } - public appendHierarchyResults(hierarchyId: string, assetSummaries: AssetSummary[] | AssociatedAssetsSummary[] | undefined, - loadingState: LoadingStateEnum, paginationToken: string | undefined) { + public appendHierarchyResults( + hierarchyId: string, + assetSummaries: AssetSummary[] | AssociatedAssetsSummary[] | undefined, + loadingState: LoadingStateEnum, + paginationToken: string | undefined + ) { let storedHierarchy: CachedAssetSummaryBlock = this.setupHierarchyCache(hierarchyId); storedHierarchy.loadingStage = loadingState; @@ -101,7 +107,7 @@ export class SiteWiseAssetCache { if (!assetSummaries) { return; } - assetSummaries.forEach(assetSummary => { + assetSummaries.forEach((assetSummary) => { if (assetSummary.id != undefined) { this.storeAssetSummary(assetSummary); storedHierarchy.assetIds.push(assetSummary.id); diff --git a/packages/core/src/asset-modules/sitewise/requestProcessor.spec.ts b/packages/core/src/asset-modules/sitewise/requestProcessor.spec.ts index 22a3988c5..5de7cffd4 100644 --- a/packages/core/src/asset-modules/sitewise/requestProcessor.spec.ts +++ b/packages/core/src/asset-modules/sitewise/requestProcessor.spec.ts @@ -4,8 +4,6 @@ import { SiteWiseAssetCache } from './cache'; it('initializes', () => { expect( - () => - new RequestProcessor(new IoTSiteWiseClient({ region: 'us-east' }), new SiteWiseAssetCache()) + () => new RequestProcessor(new IoTSiteWiseClient({ region: 'us-east' }), new SiteWiseAssetCache()) ).not.toThrowError(); }); - diff --git a/packages/core/src/asset-modules/sitewise/requestProcessor.ts b/packages/core/src/asset-modules/sitewise/requestProcessor.ts index ea8df9b5a..07b5353f9 100644 --- a/packages/core/src/asset-modules/sitewise/requestProcessor.ts +++ b/packages/core/src/asset-modules/sitewise/requestProcessor.ts @@ -1,12 +1,13 @@ import { - AssetHierarchyQuery, assetHierarchyQueryKey, + AssetHierarchyQuery, + assetHierarchyQueryKey, AssetModelQuery, AssetPropertyValueQuery, AssetSummaryQuery, CachedAssetSummaryBlock, HIERARCHY_ROOT_ID, HierarchyAssetSummaryList, - LoadingStateEnum + LoadingStateEnum, } from './types'; import { EMPTY, Observable, Subscriber } from 'rxjs'; import { @@ -22,7 +23,7 @@ import { ListAssetsFilter, ListAssociatedAssetsCommand, ListAssociatedAssetsCommandOutput, - TraversalDirection + TraversalDirection, } from '@aws-sdk/client-iotsitewise'; import { SiteWiseAssetCache } from './cache'; import { SiteWiseAssetSession } from './session'; @@ -33,12 +34,14 @@ export class RequestProcessor { private readonly api: IoTSiteWiseClient; private readonly cache: SiteWiseAssetCache; private readonly MAX_RESULTS: number = 250; - private readonly hierarchyWorkers: RequestProcessorWorkerGroup = - new RequestProcessorWorkerGroup( - query => this.loadHierarchyWorkerFactory(query), - query => assetHierarchyQueryKey(query), - query => this.hierarchyFromCache(query), - ); + private readonly hierarchyWorkers: RequestProcessorWorkerGroup< + AssetHierarchyQuery, + HierarchyAssetSummaryList + > = new RequestProcessorWorkerGroup( + (query) => this.loadHierarchyWorkerFactory(query), + (query) => assetHierarchyQueryKey(query), + (query) => this.hierarchyFromCache(query) + ); constructor(api: IoTSiteWiseClient, cache: SiteWiseAssetCache) { this.api = api; @@ -112,7 +115,7 @@ export class RequestProcessor { private buildAssetSummaryList(hierarchyId: string, cachedValue: CachedAssetSummaryBlock): HierarchyAssetSummaryList { return { assetHierarchyId: hierarchyId, - assets: cachedValue.assetIds.map(assetId => this.cache.getAssetSummary(assetId) as AssetSummary), + assets: cachedValue.assetIds.map((assetId) => this.cache.getAssetSummary(assetId) as AssetSummary), loadingState: cachedValue.loadingStage, }; } @@ -128,38 +131,51 @@ export class RequestProcessor { return this.buildAssetSummaryList(hierarchyRequest.assetHierarchyId, cachedValue); } - private hierarchyRootRequest(paginationToken: string|undefined): Observable { - return new Observable(observer => { - this.api.send(new ListAssetsCommand({ - filter: ListAssetsFilter.TOP_LEVEL, - maxResults: this.MAX_RESULTS, - nextToken: paginationToken, - assetModelId: undefined, - })).then(result => observer.next(result)); + private hierarchyRootRequest(paginationToken: string | undefined): Observable { + return new Observable((observer) => { + this.api + .send( + new ListAssetsCommand({ + filter: ListAssetsFilter.TOP_LEVEL, + maxResults: this.MAX_RESULTS, + nextToken: paginationToken, + assetModelId: undefined, + }) + ) + .then((result) => observer.next(result)); }); } - private hierarchyBranchRequest(query: AssetHierarchyQuery, - paginationToken: string | undefined): Observable { - return new Observable(observer => { - this.api.send(new ListAssociatedAssetsCommand({ - hierarchyId: query.assetHierarchyId, - maxResults: this.MAX_RESULTS, - traversalDirection: TraversalDirection.CHILD, - assetId: query.assetId, - nextToken: paginationToken, - })).then(result => observer.next(result)); + private hierarchyBranchRequest( + query: AssetHierarchyQuery, + paginationToken: string | undefined + ): Observable { + return new Observable((observer) => { + this.api + .send( + new ListAssociatedAssetsCommand({ + hierarchyId: query.assetHierarchyId, + maxResults: this.MAX_RESULTS, + traversalDirection: TraversalDirection.CHILD, + assetId: query.assetId, + nextToken: paginationToken, + }) + ) + .then((result) => observer.next(result)); }); } - private cacheHierarchyUpdate(query: AssetHierarchyQuery, - results: ListAssetsCommandOutput | ListAssociatedAssetsCommandOutput) - : HierarchyAssetSummaryList { + private cacheHierarchyUpdate( + query: AssetHierarchyQuery, + results: ListAssetsCommandOutput | ListAssociatedAssetsCommandOutput + ): HierarchyAssetSummaryList { const hasMoreResults = !!results.nextToken; - this.cache.appendHierarchyResults(assetHierarchyQueryKey(query), + this.cache.appendHierarchyResults( + assetHierarchyQueryKey(query), results.assetSummaries, hasMoreResults ? LoadingStateEnum.LOADING : LoadingStateEnum.LOADED, - results.nextToken); + results.nextToken + ); return this.hierarchyFromCache(query); } @@ -168,32 +184,32 @@ export class RequestProcessor { let cachedValue = this.cache.getHierarchy(assetHierarchyQueryKey(query)) as CachedAssetSummaryBlock; if (query.assetHierarchyId !== HIERARCHY_ROOT_ID && !query.assetId) { - throw "Queries for children require a parent AssetId"; + throw 'Queries for children require a parent AssetId'; } - return new Observable(observer => { + return new Observable((observer) => { let observable: Observable; if (query.assetHierarchyId === HIERARCHY_ROOT_ID) { observable = this.hierarchyRootRequest(cachedValue.paginationToken).pipe( expand((result: ListAssetsCommandOutput) => { - return (!abort && result.nextToken) ? this.hierarchyRootRequest(cachedValue.paginationToken) : EMPTY; + return !abort && result.nextToken ? this.hierarchyRootRequest(cachedValue.paginationToken) : EMPTY; }), - map(value => { + map((value) => { return this.cacheHierarchyUpdate(query, value); - })); + }) + ); } else { observable = this.hierarchyBranchRequest(query, cachedValue.paginationToken).pipe( expand((result: ListAssociatedAssetsCommandOutput) => { - - return (!abort && result.nextToken) ? - this.hierarchyBranchRequest(query, cachedValue.paginationToken) : EMPTY; + return !abort && result.nextToken ? this.hierarchyBranchRequest(query, cachedValue.paginationToken) : EMPTY; }), - map(value => { + map((value) => { return this.cacheHierarchyUpdate(query, value); - })); + }) + ); } - observable.subscribe(results => { + observable.subscribe((results) => { if (results) { observer.next(results); } diff --git a/packages/core/src/asset-modules/sitewise/requestProcessorWorkerGroup.ts b/packages/core/src/asset-modules/sitewise/requestProcessorWorkerGroup.ts index 22e9c16fa..2166ccda9 100644 --- a/packages/core/src/asset-modules/sitewise/requestProcessorWorkerGroup.ts +++ b/packages/core/src/asset-modules/sitewise/requestProcessorWorkerGroup.ts @@ -8,9 +8,11 @@ export class RequestProcessorWorkerGroup { private readonly queryToKey: (query: TQuery) => string; private readonly startValue: (query: TQuery) => TResult; - constructor(workerFactory: (query: TQuery) => Observable, - queryToKey: (query: TQuery) => string, - startValue: (query: TQuery) => TResult) { + constructor( + workerFactory: (query: TQuery) => Observable, + queryToKey: (query: TQuery) => string, + startValue: (query: TQuery) => TResult + ) { this.workerFactory = workerFactory; this.queryToKey = queryToKey; this.startValue = startValue; diff --git a/packages/core/src/asset-modules/sitewise/session.ts b/packages/core/src/asset-modules/sitewise/session.ts index 9e3733c92..030905eaa 100644 --- a/packages/core/src/asset-modules/sitewise/session.ts +++ b/packages/core/src/asset-modules/sitewise/session.ts @@ -4,11 +4,13 @@ import { AssetModelQuery, AssetPropertyValueQuery, AssetQuery, - AssetSummaryQuery, HierarchyAssetSummaryList, + AssetSummaryQuery, + HierarchyAssetSummaryList, isAssetHierarchyQuery, isAssetModelQuery, isAssetPropertyValueQuery, - isAssetSummaryQuery, SiteWiseAssetSessionInterface, + isAssetSummaryQuery, + SiteWiseAssetSessionInterface, } from './types'; import { AssetSummary, DescribeAssetModelResponse } from '@aws-sdk/client-iotsitewise/dist-types'; import { RequestProcessor } from './requestProcessor'; @@ -24,13 +26,15 @@ export class SiteWiseAssetSession implements SiteWiseAssetSessionInterface { this.processor = processor; } - public addRequest(query: AssetModelQuery, observer: (assetModel: DescribeAssetModelResponse) => void): Subscription; public addRequest( query: AssetPropertyValueQuery, observer: (assetPropertyValue: AssetPropertyValue) => void ): Subscription; - public addRequest(query: AssetHierarchyQuery, observer: (assetSummary: HierarchyAssetSummaryList) => void): Subscription; + public addRequest( + query: AssetHierarchyQuery, + observer: (assetSummary: HierarchyAssetSummaryList) => void + ): Subscription; public addRequest(query: AssetSummaryQuery, observer: (assetSummary: AssetSummary) => void): Subscription; public addRequest(query: AssetQuery, observerAny: (consumedType: Result) => void): Subscription { let observable: Observable; diff --git a/packages/core/src/asset-modules/sitewise/types.ts b/packages/core/src/asset-modules/sitewise/types.ts index 38674cff6..fe1063f09 100644 --- a/packages/core/src/asset-modules/sitewise/types.ts +++ b/packages/core/src/asset-modules/sitewise/types.ts @@ -32,7 +32,7 @@ export type AssetHierarchyQuery = AssetQuery & { }; export function assetHierarchyQueryKey(query: AssetHierarchyQuery): string { - return (query.assetId ? (query.assetId + ":") : '') + query.assetHierarchyId; + return (query.assetId ? query.assetId + ':' : '') + query.assetHierarchyId; } export const isAssetHierarchyQuery = (query: AssetQuery): query is AssetHierarchyQuery => (query as AssetHierarchyQuery).assetHierarchyId != undefined; @@ -47,30 +47,27 @@ export enum LoadingStateEnum { export const HIERARCHY_ROOT_ID = 'HIERARCHY_ROOT_ID'; export type CachedAssetSummaryBlock = { - assetIds: string[], - loadingStage: LoadingStateEnum, - paginationToken: string | undefined -} + assetIds: string[]; + loadingStage: LoadingStateEnum; + paginationToken: string | undefined; +}; export type HierarchyAssetSummaryList = { - assetHierarchyId: string, - assets: AssetSummary[], - loadingState: LoadingStateEnum, -} + assetHierarchyId: string; + assets: AssetSummary[]; + loadingState: LoadingStateEnum; +}; export interface SiteWiseAssetModuleInterface { - startSession(): SiteWiseAssetSessionInterface + startSession(): SiteWiseAssetSessionInterface; } export interface SiteWiseAssetSessionInterface { addRequest(query: AssetModelQuery, observer: (assetModel: DescribeAssetModelResponse) => void): Subscription; - addRequest( - query: AssetPropertyValueQuery, - observer: (assetPropertyValue: AssetPropertyValue) => void - ): Subscription; + addRequest(query: AssetPropertyValueQuery, observer: (assetPropertyValue: AssetPropertyValue) => void): Subscription; addRequest(query: AssetHierarchyQuery, observer: (assetSummary: HierarchyAssetSummaryList) => void): Subscription; addRequest(query: AssetSummaryQuery, observer: (assetSummary: AssetSummary) => void): Subscription; // addRequest(query: AssetQuery, observerAny: (consumedType: Result) => void): Subscription; - close(): void + close(): void; } diff --git a/packages/core/src/data-module/IotAppKitDataModule.spec.ts b/packages/core/src/data-module/IotAppKitDataModule.spec.ts index a53aefa6b..57f552695 100644 --- a/packages/core/src/data-module/IotAppKitDataModule.spec.ts +++ b/packages/core/src/data-module/IotAppKitDataModule.spec.ts @@ -2,7 +2,7 @@ import flushPromises from 'flush-promises'; import { DATA_STREAM, DATA_STREAM_INFO, STRING_INFO_1 } from '../testing/__mocks__/mockWidgetProperties'; import { DataSource, DataSourceRequest } from './types.d'; import { DataPoint, DataStream, DataStreamInfo, Resolution } from '@synchro-charts/core'; -import { Request } from './data-cache/requestTypes'; +import { TimeSeriesDataRequest, TimeSeriesDataRequestSettings } from './data-cache/requestTypes'; import { DataStreamsStore, DataStreamStore } from './data-cache/types'; import * as caching from './data-cache/caching/caching'; import { createSiteWiseLegacyDataSource } from '../data-sources/site-wise-legacy/data-source'; @@ -15,6 +15,7 @@ import { SiteWiseDataStreamQuery } from '../data-sources/site-wise/types'; import { toDataStreamId, toSiteWiseAssetProperty } from '../data-sources/site-wise/util/dataStreamId'; import Mock = jest.Mock; +import { SiteWiseLegacyDataStreamQuery } from '../data-sources/site-wise-legacy'; const { EMPTY_CACHE } = caching; @@ -56,11 +57,11 @@ it('subscribes to an empty set of queries', async () => { const onSuccess = jest.fn(); dataModule.subscribeToDataStreams( { - query: { source: dataSource.name, assets: [] }, - requestInfo: { + query: { source: dataSource.name, assets: [] } as SiteWiseDataStreamQuery, + request: { viewport: { start: new Date(2000, 0, 0), end: new Date(2000, 0, 2) }, - onlyFetchLatestValue: false, - requestConfig: { + settings: { + fetchFromStartToEnd: true, requestBuffer: 0, }, }, @@ -85,9 +86,11 @@ describe('update subscription', () => { const { update } = dataModule.subscribeToDataStreams( { query, - requestInfo: { + request: { viewport: { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0) }, - onlyFetchLatestValue: false, + settings: { + fetchFromStartToEnd: true, + }, }, }, dataStreamCallback @@ -122,9 +125,11 @@ describe('initial request', () => { dataModule.subscribeToDataStreams( { query: { source: dataSource.name }, - requestInfo: { + request: { viewport: { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0) }, - onlyFetchLatestValue: false, + settings: { + fetchFromStartToEnd: true, + }, }, }, dataStreamCallback @@ -151,7 +156,7 @@ describe('initial request', () => { dataModule.subscribeToDataStreams( { query: DATA_STREAM_QUERY, - requestInfo: { viewport: { start: START, end: END }, onlyFetchLatestValue: false }, + request: { viewport: { start: START, end: END }, settings: { fetchFromStartToEnd: true } }, }, dataStreamCallback ); @@ -187,9 +192,9 @@ it('subscribes to a single data stream', async () => { }, ], }, - requestInfo: { - viewport: { start: new Date(2000, 0, 0), end: new Date(2002, 0, 0) }, - onlyFetchLatestValue: false, + request: { + viewport: { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0) }, + settings: { fetchFromStartToEnd: true }, }, }, dataStreamCallback @@ -210,10 +215,12 @@ it('throws error when subscribing to a non-existent data source', () => { expect(() => dataModule.subscribeToDataStreams( { - query: { source: 'fake-source', assets: [] }, - requestInfo: { + query: { source: 'fake-source', assets: [] } as SiteWiseDataStreamQuery, + request: { viewport: { start: new Date(2000, 0, 0), end: new Date(2002, 0, 0) }, - onlyFetchLatestValue: false, + settings: { + fetchFromStartToEnd: true, + }, }, }, () => {} @@ -235,10 +242,12 @@ it('requests data from a custom data source', () => { query: { assets: [{ assetId, properties: [{ propertyId }] }], source: customSource.name, - }, - requestInfo: { + } as SiteWiseDataStreamQuery, + request: { viewport: { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0) }, - onlyFetchLatestValue: false, + settings: { + fetchFromStartToEnd: true, + }, }, }, onSuccess @@ -251,9 +260,11 @@ it('subscribes to multiple data streams', () => { const onRequestData = jest.fn(); const source = createSiteWiseLegacyDataSource(onRequestData); - const requestInfo: Request = { + const request: TimeSeriesDataRequest = { viewport: { start: new Date(1999, 0, 0), end: new Date() }, - onlyFetchLatestValue: false, + settings: { + fetchFromStartToEnd: true, + }, }; const dataStreamInfos: DataStreamInfo[] = [STRING_INFO_1, DATA_STREAM_INFO]; @@ -269,7 +280,7 @@ it('subscribes to multiple data streams', () => { dataModule.subscribeToDataStreams( { query, - requestInfo, + request, }, onSuccess ); @@ -282,6 +293,8 @@ it('only requests latest value', () => { const onRequestData = jest.fn(); const source = createSiteWiseLegacyDataSource(onRequestData); + const LATEST_VALUE_REQUEST_SETTINGS: TimeSeriesDataRequestSettings = { fetchMostRecentBeforeEnd: true }; + const dataModule = new IotAppKitDataModule(); const onSuccess = jest.fn(); @@ -292,14 +305,21 @@ it('only requests latest value', () => { query: { dataStreamInfos: [DATA_STREAM_INFO], source: source.name, + } as SiteWiseLegacyDataStreamQuery, + request: { + viewport: { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0) }, + settings: LATEST_VALUE_REQUEST_SETTINGS, }, - requestInfo: { viewport: { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0) }, onlyFetchLatestValue: true }, }, onSuccess ); expect(onRequestData).toBeCalledWith( - expect.objectContaining({ request: expect.objectContaining({ onlyFetchLatestValue: true }) }) + expect.objectContaining({ + request: expect.objectContaining({ + settings: LATEST_VALUE_REQUEST_SETTINGS, + }), + }) ); }); @@ -347,7 +367,10 @@ describe('error handling', () => { dataModule.subscribeToDataStreams( { query: DATA_STREAM_QUERY, - requestInfo: { viewport: { start: new Date(2000, 0, 0), end: new Date() }, onlyFetchLatestValue: false }, + request: { + viewport: { start: new Date(2000, 0, 0), end: new Date() }, + settings: { fetchFromStartToEnd: true }, + }, }, dataStreamCallback ); @@ -367,7 +390,13 @@ describe('error handling', () => { dataModule.subscribeToDataStreams( { query: DATA_STREAM_QUERY, - requestInfo: { viewport: { duration: 900000 }, onlyFetchLatestValue: false, refreshRate: SECOND_IN_MS / 10 }, + request: { + viewport: { duration: 900000 }, + settings: { + refreshRate: SECOND_IN_MS / 10, + fetchFromStartToEnd: true, + }, + }, }, dataStreamCallback ); @@ -389,7 +418,10 @@ describe('error handling', () => { dataModule.subscribeToDataStreams( { query: DATA_STREAM_QUERY, - requestInfo: { viewport: { start: new Date(2000, 0, 0), end: new Date() }, onlyFetchLatestValue: false }, + request: { + viewport: { start: new Date(2000, 0, 0), end: new Date() }, + settings: { fetchFromStartToEnd: true }, + }, }, dataStreamCallback ); @@ -423,16 +455,16 @@ describe('caching', () => { const { update } = dataModule.subscribeToDataStreams( { query: DATA_STREAM_QUERY, - requestInfo: { viewport: { start: START_1, end: END_1 }, onlyFetchLatestValue: false }, + request: { viewport: { start: START_1, end: END_1 }, settings: { fetchFromStartToEnd: true } }, }, dataStreamCallback ); - update({ requestInfo: { viewport: { start: START_2, end: END_2 }, onlyFetchLatestValue: false } }); + update({ request: { viewport: { start: START_2, end: END_2 }, settings: { fetchFromStartToEnd: true } } }); (dataSource.initiateRequest as Mock).mockClear(); - update({ requestInfo: { viewport: { start: START_1, end: END_1 }, onlyFetchLatestValue: false } }); + update({ request: { viewport: { start: START_1, end: END_1 }, settings: { fetchFromStartToEnd: true } } }); expect(dataSource.initiateRequest).not.toBeCalled(); }); @@ -455,14 +487,14 @@ describe('caching', () => { const { update } = dataModule.subscribeToDataStreams( { query: DATA_STREAM_QUERY, - requestInfo: { viewport: { start: START_1, end: END_1 }, onlyFetchLatestValue: false }, + request: { viewport: { start: START_1, end: END_1 }, settings: { fetchFromStartToEnd: true } }, }, dataStreamCallback ); (dataSource.initiateRequest as Mock).mockClear(); - update({ requestInfo: { viewport: { start: START_2, end: END_2 }, onlyFetchLatestValue: false } }); + update({ request: { viewport: { start: START_2, end: END_2 }, settings: { fetchFromStartToEnd: true } } }); await flushPromises(); @@ -496,14 +528,16 @@ describe('caching', () => { const { update } = dataModule.subscribeToDataStreams( { query: DATA_STREAM_QUERY, - requestInfo: { viewport: { start: START_1, end: END_1 }, onlyFetchLatestValue: false }, + request: { viewport: { start: START_1, end: END_1 }, settings: { fetchFromStartToEnd: true } }, }, dataStreamCallback ); (dataSource.initiateRequest as Mock).mockClear(); - update({ requestInfo: { viewport: { start: START_2, end: END_2 }, onlyFetchLatestValue: false } }); + update({ + request: { viewport: { start: START_2, end: END_2 } }, + }); expect(dataSource.initiateRequest).toBeCalledWith(expect.any(Object), [ { @@ -527,7 +561,7 @@ describe('caching', () => { const { update } = dataModule.subscribeToDataStreams( { query: DATA_STREAM_QUERY, - requestInfo: { viewport: { start: START_1, end: END_1 }, onlyFetchLatestValue: false }, + request: { viewport: { start: START_1, end: END_1 }, settings: { fetchFromStartToEnd: true } }, }, dataStreamCallback ); @@ -536,7 +570,9 @@ describe('caching', () => { jest.advanceTimersByTime(MINUTE_IN_MS); - update({ requestInfo: { viewport: { start: START_1, end: END_1 }, onlyFetchLatestValue: false } }); + // Need to trigger a re-request, we should be able to just 'recieve' the updated value instead. + // TODO: Remove this line after completing task 'non-live mode subscribers have non-expired data' + update({ request: { viewport: { start: START_1, end: END_1 } } }); expect(dataSource.initiateRequest).toBeCalledWith(expect.any(Object), [ { @@ -568,16 +604,17 @@ describe('caching', () => { const { update } = dataModule.subscribeToDataStreams( { query: DATA_STREAM_QUERY, - requestInfo: { viewport: { start: START_1, end: END_1 }, onlyFetchLatestValue: false }, + request: { viewport: { start: START_1, end: END_1 }, settings: { fetchFromStartToEnd: true } }, }, dataStreamCallback ); (dataSource.initiateRequest as Mock).mockClear(); - jest.advanceTimersByTime(MINUTE_IN_MS); - update({ requestInfo: { viewport: { start: START_1, end: END_1 }, onlyFetchLatestValue: false } }); + // Need to trigger a re-request, we should be able to just 'recieve' the updated value instead. + // TODO: Remove this line after completing task 'non-live mode subscribers have non-expired data' + update({ request: { viewport: { start: START_1, end: END_1 } } }); expect(dataSource.initiateRequest).toBeCalledWith(expect.any(Object), [ { @@ -601,7 +638,10 @@ describe('request scheduler', () => { const { unsubscribe } = dataModule.subscribeToDataStreams( { query: DATA_STREAM_QUERY, - requestInfo: { viewport: { duration: 900000 }, onlyFetchLatestValue: false, refreshRate: SECOND_IN_MS * 0.1 }, + request: { + viewport: { duration: 900000 }, + settings: { fetchFromStartToEnd: true, refreshRate: SECOND_IN_MS * 0.1 }, + }, }, dataStreamCallback ); @@ -627,10 +667,12 @@ describe('request scheduler', () => { const { unsubscribe } = dataModule.subscribeToDataStreams( { query: DATA_STREAM_QUERY, - requestInfo: { + request: { viewport: { duration: SECOND_IN_MS }, - onlyFetchLatestValue: false, - refreshRate: SECOND_IN_MS * 0.1, + settings: { + fetchFromStartToEnd: true, + refreshRate: SECOND_IN_MS * 0.1, + }, }, }, dataStreamCallback @@ -655,20 +697,24 @@ describe('request scheduler', () => { const { update, unsubscribe } = dataModule.subscribeToDataStreams( { query: DATA_STREAM_QUERY, - requestInfo: { + request: { viewport: { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0) }, - onlyFetchLatestValue: true, - refreshRate: SECOND_IN_MS * 0.1, + settings: { + fetchMostRecentBeforeEnd: true, + refreshRate: SECOND_IN_MS * 0.1, + }, }, }, dataStreamCallback ); update({ - requestInfo: { + request: { viewport: { duration: MINUTE_IN_MS }, - refreshRate: SECOND_IN_MS * 0.1, - onlyFetchLatestValue: false, + settings: { + refreshRate: SECOND_IN_MS * 0.1, + fetchFromStartToEnd: true, + }, }, }); dataStreamCallback.mockClear(); @@ -692,17 +738,27 @@ describe('request scheduler', () => { const { update, unsubscribe } = dataModule.subscribeToDataStreams( { query: DATA_STREAM_QUERY, - requestInfo: { + request: { viewport: { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0) }, - onlyFetchLatestValue: false, - refreshRate: SECOND_IN_MS * 0.1, + settings: { + fetchFromStartToEnd: true, + refreshRate: SECOND_IN_MS * 0.1, + }, }, }, dataStreamCallback ); // Update the request info to trigger the live mode - update({ requestInfo: { viewport: { duration: SECOND_IN_MS }, onlyFetchLatestValue: false } }); + update({ + request: { + viewport: { duration: SECOND_IN_MS }, + settings: { + fetchFromStartToEnd: true, + refreshRate: SECOND_IN_MS * 0.1, + }, + }, + }); unsubscribe(); dataStreamCallback.mockClear(); @@ -720,16 +776,18 @@ describe('request scheduler', () => { const { update } = dataModule.subscribeToDataStreams( { query: DATA_STREAM_QUERY, - requestInfo: { viewport: { duration: SECOND_IN_MS }, onlyFetchLatestValue: false }, + request: { viewport: { duration: SECOND_IN_MS }, settings: { fetchFromStartToEnd: true } }, }, dataStreamCallback ); update({ - requestInfo: { + request: { viewport: { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0) }, - refreshRate: SECOND_IN_MS * 0.1, - onlyFetchLatestValue: false, + settings: { + refreshRate: SECOND_IN_MS * 0.1, + fetchFromStartToEnd: true, + }, }, }); dataStreamCallback.mockClear(); @@ -740,7 +798,7 @@ describe('request scheduler', () => { }); }); -it('requests data range with buffer', () => { +it('when data is requested from the viewport start to end with a buffer, include a buffer', () => { const dataModule = new IotAppKitDataModule(); const dataSource = createMockSiteWiseDataSource([DATA_STREAM]); dataModule.registerDataSource(dataSource); @@ -755,15 +813,15 @@ it('requests data range with buffer', () => { const { unsubscribe } = dataModule.subscribeToDataStreams( { query: DATA_STREAM_QUERY, - requestInfo: { viewport: { start, end }, onlyFetchLatestValue: false, requestConfig: { requestBuffer } }, + request: { viewport: { start, end }, settings: { requestBuffer, fetchFromStartToEnd: true } }, }, dataStreamCallback ); expect(dataSource.initiateRequest).toBeCalledWith( expect.objectContaining({ - requestInfo: expect.objectContaining({ - requestConfig: expect.objectContaining({ + request: expect.objectContaining({ + settings: expect.objectContaining({ requestBuffer, }), }), @@ -778,4 +836,3 @@ it('requests data range with buffer', () => { unsubscribe(); }); - diff --git a/packages/core/src/data-module/IotAppKitDataModule.ts b/packages/core/src/data-module/IotAppKitDataModule.ts index 5c6e2de43..561acea6a 100644 --- a/packages/core/src/data-module/IotAppKitDataModule.ts +++ b/packages/core/src/data-module/IotAppKitDataModule.ts @@ -14,7 +14,7 @@ import { DataStreamsStore, CacheSettings } from './data-cache/types'; import DataSourceStore from './data-source-store/dataSourceStore'; import { SubscriptionResponse } from '../data-sources/site-wise/types.d'; import { DataCache } from './data-cache/dataCacheWrapped'; -import { Request } from './data-cache/requestTypes'; +import { TimeSeriesDataRequest } from './data-cache/requestTypes'; import { requestRange } from './data-cache/requestRange'; import { getDateRangesToRequest } from './data-cache/caching/caching'; import { viewportEndDate, viewportStartDate } from '../common/viewport'; @@ -70,14 +70,14 @@ export class IotAppKitDataModule implements DataModule { query, start, end, - requestInfo, + request, }: { query: DataStreamQuery; start: Date; end: Date; - requestInfo: Request; + request: TimeSeriesDataRequest; }) => { - const requestedStreams = this.dataSourceStore.getRequestsFromQuery({ query, requestInfo }); + const requestedStreams = this.dataSourceStore.getRequestsFromQuery({ query, request }); const isRequestedDataStream = ({ id, resolution }: RequestInformation) => this.dataCache.shouldRequestDataStream({ dataStreamId: id, resolution }); @@ -92,7 +92,7 @@ export class IotAppKitDataModule implements DataModule { end, max: new Date(), }, - requestInfo.requestConfig?.requestBuffer + request.settings?.requestBuffer ); const requests = requiredStreams @@ -126,44 +126,26 @@ export class IotAppKitDataModule implements DataModule { ); if (requests.length > 0) { - this.registerRequest({ query, requestInfo }, requests); + this.registerRequest({ query, request }, requests); } }; - public subscribeToDataStreamsFrom = (source: string, callback: DataStreamCallback) => { - const subscriptionId = v4(); - - this.subscriptions.addSubscription(subscriptionId, { - source, - emit: callback, - }); - - /** - * subscription management - */ - const unsubscribe = () => { - this.unsubscribe(subscriptionId); - }; - - return { unsubscribe }; - }; - public subscribeToDataStreams = ( - { query, requestInfo }: DataModuleSubscription, + { query, request }: DataModuleSubscription, callback: DataStreamCallback ): SubscriptionResponse => { const subscriptionId = v4(); this.subscriptions.addSubscription(subscriptionId, { query, - requestInfo, + request, emit: callback, fulfill: () => { this.fulfillQuery({ - start: viewportStartDate(requestInfo.viewport), - end: viewportEndDate(requestInfo.viewport), + start: viewportStartDate(request.viewport), + end: viewportEndDate(request.viewport), query, - requestInfo, + request, }); }, }); @@ -195,10 +177,10 @@ export class IotAppKitDataModule implements DataModule { ...updatedSubscription, fulfill: () => { this.fulfillQuery({ - start: viewportStartDate(updatedSubscription.requestInfo.viewport), - end: viewportEndDate(updatedSubscription.requestInfo.viewport), + start: viewportStartDate(updatedSubscription.request.viewport), + end: viewportEndDate(updatedSubscription.request.viewport), query: updatedSubscription.query, - requestInfo: updatedSubscription.requestInfo, + request: updatedSubscription.request, }); }, }); @@ -206,14 +188,14 @@ export class IotAppKitDataModule implements DataModule { }; private registerRequest = ( - subscription: { query: Query; requestInfo: Request }, + subscription: { query: Query; request: TimeSeriesDataRequest }, requestInformations: RequestInformationAndRange[] ): void => { this.dataSourceStore.initiateRequest( { - requestInfo: subscription.requestInfo, + request: subscription.request, query: subscription.query, - onSuccess: this.dataCache.onSuccess(subscription.requestInfo), + onSuccess: this.dataCache.onSuccess(subscription.request), onError: this.dataCache.onError, }, requestInformations 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 cb5d7d2f3..729798c19 100755 --- a/packages/core/src/data-module/data-cache/caching/caching.ts +++ b/packages/core/src/data-module/data-cache/caching/caching.ts @@ -11,7 +11,7 @@ import { import { CacheSettings, DataStreamsStore, DataStreamStore, TTLDurationMapping } from '../types'; import { getExpiredCacheIntervals } from './expiredCacheIntervals'; -import { RequestConfig } from '../requestTypes'; +import { TimeSeriesDataRequestSettings } from '../requestTypes'; import { pointBisector } from '../../../common/dataFilters'; export const unexpiredCacheIntervals = ( @@ -197,7 +197,7 @@ 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: RequestConfig | undefined) => { +export const validateRequestConfig = (requestConfig: TimeSeriesDataRequestSettings | undefined) => { if (requestConfig) { return requestConfig.fetchMostRecentBeforeStart; } 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 09ce68ba0..365c4043c 100644 --- a/packages/core/src/data-module/data-cache/dataCacheWrapped.spec.ts +++ b/packages/core/src/data-module/data-cache/dataCacheWrapped.spec.ts @@ -101,7 +101,9 @@ describe('actions', () => { it('onSuccess works', () => { const dataCache = new DataCache(); - dataCache.onSuccess({ onlyFetchLatestValue: false, viewport: { duration: SECOND_IN_MS } })([DATA_STREAM]); + dataCache.onSuccess({ settings: { fetchFromStartToEnd: true }, viewport: { duration: SECOND_IN_MS } })([ + DATA_STREAM, + ]); const state = dataCache.getState() as any; expect(state[DATA_STREAM.id][DATA_STREAM.resolution]).toBeDefined(); diff --git a/packages/core/src/data-module/data-cache/dataCacheWrapped.ts b/packages/core/src/data-module/data-cache/dataCacheWrapped.ts index aadd5b312..e332936b0 100644 --- a/packages/core/src/data-module/data-cache/dataCacheWrapped.ts +++ b/packages/core/src/data-module/data-cache/dataCacheWrapped.ts @@ -2,7 +2,7 @@ import { Store } from 'redux'; import { DataStream, Resolution } from '@synchro-charts/core'; import { DataStreamsStore } from './types'; import { configureStore } from './createStore'; -import { Request } from './requestTypes'; +import { TimeSeriesDataRequest } from './requestTypes'; import { onErrorAction, onRequestAction, onSuccessAction } from './dataActions'; import { viewportEndDate, viewportStartDate } from '../../common/viewport'; import { getDataStreamStore } from './getDataStreamStore'; @@ -93,7 +93,7 @@ export class DataCache { * coordinating the dispatching of the action throughout the file. */ - public onSuccess = (queryConfig: Request) => (dataStreams: DataStream[]): void => { + public onSuccess = (queryConfig: TimeSeriesDataRequest) => (dataStreams: DataStream[]): void => { const queryStart: Date = viewportStartDate(queryConfig.viewport); const queryEnd: Date = viewportEndDate(queryConfig.viewport); diff --git a/packages/core/src/data-module/data-cache/requestTypes.ts b/packages/core/src/data-module/data-cache/requestTypes.ts index 8ce46ead2..68d8bb4c2 100755 --- a/packages/core/src/data-module/data-cache/requestTypes.ts +++ b/packages/core/src/data-module/data-cache/requestTypes.ts @@ -9,17 +9,32 @@ export type Viewport = /** * Request Information utilized by consumers of the widgets to connect the `data-provider` to their data source. */ -export type Request = { +export type TimeSeriesDataRequest = { viewport: MinimalViewPortConfig; - // when this is true, we only require the latest value to be returned when in a live view, and will return most recent when in a historical view. - onlyFetchLatestValue: boolean; - // how long before waiting to check if another request should be initiated, in ms - refreshRate?: number; - requestConfig?: RequestConfig; + settings?: TimeSeriesDataRequestSettings; }; +export type ResolutionConfig = ResolutionMapping | string; + +export interface TimeSeriesDataRequestSettings { + // Higher buffer will lead to more off-viewport data to be requested. + requestBuffer?: number; + + // refresh rate in milliseconds + refreshRate?: number; + + resolution?: ResolutionConfig; + + fetchAggregatedData?: boolean; + + // Specify what data intervals to request given a viewport + fetchFromStartToEnd?: boolean; + fetchMostRecentBeforeStart?: boolean; + fetchMostRecentBeforeEnd?: boolean; +} + export type OnRequestData = (opts: { - request: Request; + request: TimeSeriesDataRequest; resolution: number; // milliseconds, 0 for raw data onError: (id: DataStreamId, resolution: Resolution, error: string) => void; onSuccess: (id: DataStreamId, data: DataStream, first: Date, last: Date) => void; @@ -29,12 +44,3 @@ export type OnRequestData = (opts: { export type ResolutionMapping = { [viewportDuration: number]: number | string; }; - -export type ResolutionConfig = ResolutionMapping | string; - -export interface RequestConfig { - fetchMostRecentBeforeStart?: boolean; - requestBuffer?: number; - fetchAggregatedData?: boolean; - resolution?: ResolutionConfig; -} 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 0486641e1..1c356f0e4 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 @@ -16,10 +16,10 @@ it('initiate a request on a registered data source', () => { const query = { source: 'custom' }; - const requestInfo = { viewport: { start: new Date(), end: new Date() }, onlyFetchLatestValue: false }; + const request = { viewport: { start: new Date(), end: new Date() }, settings: { fetchFromStartToEnd: true } }; dataSourceStore.initiateRequest( { - requestInfo, + request, query, onSuccess: () => {}, onError: () => {}, @@ -29,7 +29,7 @@ it('initiate a request on a registered data source', () => { expect(customSource.initiateRequest).toBeCalledWith( { - requestInfo, + request, query, onSuccess: expect.toBeFunction(), onError: expect.toBeFunction(), @@ -41,11 +41,11 @@ it('initiate a request on a registered data source', () => { it('throws error when attempting to initiate a request to a non-existent data source', () => { const dataSourceStore = new DataSourceStore(); - const requestInfo = { viewport: { start: new Date(), end: new Date() }, onlyFetchLatestValue: false }; + const request = { viewport: { start: new Date(), end: new Date() }, settings: { fetchFromStartToEnd: true } }; expect(() => dataSourceStore.initiateRequest( { - requestInfo, + request, query: { source: 'some-name' }, onSuccess: () => {}, onError: () => {}, 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 db96b2ab1..bde59b876 100644 --- a/packages/core/src/data-module/data-source-store/dataSourceStore.ts +++ b/packages/core/src/data-module/data-source-store/dataSourceStore.ts @@ -6,8 +6,7 @@ import { RequestInformation, RequestInformationAndRange, } from '../types.d'; -import { Request } from '../data-cache/requestTypes'; - +import { TimeSeriesDataRequest } from '../data-cache/requestTypes'; /** * Manages the collection of registered data sources, as well as delegating requests to the correct data-source. @@ -28,9 +27,15 @@ export default class DataSourceStore { return this.dataSources[source]; }; - public getRequestsFromQuery = ({ query, requestInfo }: { query: Query, requestInfo: Request }): RequestInformation[] => { + public getRequestsFromQuery = ({ + query, + request, + }: { + query: Query; + request: TimeSeriesDataRequest; + }): RequestInformation[] => { const dataSource = this.getDataSource(query.source); - return dataSource.getRequestsFromQuery({ query, requestInfo }); + return dataSource.getRequestsFromQuery({ query, request }); }; public initiateRequest = ( diff --git a/packages/core/src/data-module/index.ts b/packages/core/src/data-module/index.ts index 721e4f6b6..90c621770 100644 --- a/packages/core/src/data-module/index.ts +++ b/packages/core/src/data-module/index.ts @@ -16,9 +16,6 @@ export const registerDataSource: RegisterDataSource = (dataModule, ...inputs) => export const subscribeToDataStreams: SubscribeToDataStreams = (dataModule, ...inputs) => dataModule.subscribeToDataStreams(...inputs); -export const subscribeToDataStreamsFrom: SubscribeToDataStreamsFrom = (dataModule, ...inputs) => - dataModule.subscribeToDataStreamsFrom(...inputs); - /** * Initialize IoT App Kit * 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 14dee12f0..ef602eb60 100644 --- a/packages/core/src/data-module/subscription-store/subscriptionStore.spec.ts +++ b/packages/core/src/data-module/subscription-store/subscriptionStore.spec.ts @@ -22,7 +22,13 @@ const createSubscriptionStore = () => { const MOCK_SUBSCRIPTION: Subscription = { emit: () => {}, query: { source: SITEWISE_DATA_SOURCE, assets: [] }, - requestInfo: { viewport: { start: new Date(2000, 0, 0), end: new Date() }, onlyFetchLatestValue: false }, + request: { + viewport: { start: new Date(2000, 0, 0), end: new Date() }, + settings: { + fetchFromStartToEnd: true, + fetchMostRecentBeforeStart: true, + }, + }, fulfill: () => {}, }; diff --git a/packages/core/src/data-module/subscription-store/subscriptionStore.ts b/packages/core/src/data-module/subscription-store/subscriptionStore.ts index 2250e90da..e41a8d1cc 100644 --- a/packages/core/src/data-module/subscription-store/subscriptionStore.ts +++ b/packages/core/src/data-module/subscription-store/subscriptionStore.ts @@ -15,7 +15,7 @@ export default class SubscriptionStore { private dataCache: DataCache; private unsubscribeMap: { [subscriberId: string]: Function } = {}; private scheduler: RequestScheduler = new RequestScheduler(); - private subscriptions: { [subscriptionId: string]: Subscription } = {}; + private subscriptions: { [subscriptionId: string]: Subscription } = {}; constructor({ dataSourceStore, dataCache }: { dataSourceStore: DataSourceStore; dataCache: DataCache }) { this.dataCache = dataCache; @@ -30,21 +30,21 @@ export default class SubscriptionStore { if ('query' in subscription) { subscription.fulfill(); - if ('duration' in subscription.requestInfo.viewport) { + if ('duration' in subscription.request.viewport) { /** has a duration, so periodically request for data */ this.scheduler.create({ id: subscriptionId, cb: () => subscription.fulfill(), - duration: parseDuration((subscription.requestInfo.viewport as MinimalLiveViewport).duration), - refreshRate: subscription.requestInfo.refreshRate, + duration: parseDuration((subscription.request.viewport as MinimalLiveViewport).duration), + refreshRate: subscription.request.settings?.refreshRate, }); } - const { query, requestInfo } = subscription; + const { query, request } = subscription; // Subscribe to changes from the data cache const unsubscribe = this.dataCache.subscribe( - this.dataSourceStore.getRequestsFromQuery({ query, requestInfo }), + this.dataSourceStore.getRequestsFromQuery({ query, request }), subscription.emit ); diff --git a/packages/core/src/data-module/types.d.ts b/packages/core/src/data-module/types.d.ts index ae952a41a..6e93db363 100644 --- a/packages/core/src/data-module/types.d.ts +++ b/packages/core/src/data-module/types.d.ts @@ -1,5 +1,5 @@ import { DataStream, DataStreamId, Resolution } from '@synchro-charts/core'; -import { Request } from './data-cache/requestTypes'; +import { TimeSeriesDataRequest } from './data-cache/requestTypes'; export type RequestInformation = { id: DataStreamId; resolution: Resolution }; export type RequestInformationAndRange = RequestInformation & { start: Date; end: Date }; @@ -10,35 +10,29 @@ export type DataSource = { // An identifier for the name of the source, i.e. 'site-wise', 'roci', etc.. name: DataSourceName; // this is unique initiateRequest: (request: DataSourceRequest, requestInformations: RequestInformationAndRange[]) => void; - getRequestsFromQuery: ({ query, requestInfo}: { query: Query, requestInfo: Request }) => RequestInformation[]; + getRequestsFromQuery: ({ + query, + requestInfo, + }: { + query: Query; + request: TimeSeriesDataRequest; + }) => RequestInformation[]; }; export type DataStreamCallback = (dataStreams: DataStream[]) => void; -export type QuerySubscription = - | { - query: Query; - requestInfo: Request; - emit: DataStreamCallback; - // Initiate requests for the subscription - fulfill: () => void; - } - | { - emit: DataStreamCallback; - source: string; - }; - -export type SourceSubscription = { +export type QuerySubscription = { + query: Query; + request: TimeSeriesDataRequest; emit: DataStreamCallback; - source: string; + // Initiate requests for the subscription + fulfill: () => void; }; -export type Subscription = - | QuerySubscription - | SourceSubscription; +export type Subscription = QuerySubscription; export type DataModuleSubscription = { - requestInfo: Request; + request: TimeSeriesDataRequest; query: Query; }; @@ -53,7 +47,7 @@ export type ErrorCallback = ({ id, resolution, error }) => void; export type SubscriptionUpdate = Partial, 'emit'>>; export type DataSourceRequest = { - requestInfo: Request; + request: TimeSeriesDataRequest; query: Query; onSuccess: DataStreamCallback; onError: ErrorCallback; @@ -95,13 +89,6 @@ export type SubscribeToDataStreamsFrom = ( unsubscribe: () => void; }; -type SubscribeToDataStreamsFromPrivate = ( - source: string, - emit: DataStreamCallback -) => { - unsubscribe: () => void; -}; - /** * Register custom data source to the data module. */ @@ -116,5 +103,4 @@ export type RegisterDataSource = ( export interface DataModule { registerDataSource: RegisterDataSourcePrivate; subscribeToDataStreams: SubscribeToDataStreamsPrivate; - subscribeToDataStreamsFrom: SubscribeToDataStreamsFromPrivate; } diff --git a/packages/core/src/data-sources/site-wise-legacy/data-source.ts b/packages/core/src/data-sources/site-wise-legacy/data-source.ts index ca85c6002..72d5b9351 100644 --- a/packages/core/src/data-sources/site-wise-legacy/data-source.ts +++ b/packages/core/src/data-sources/site-wise-legacy/data-source.ts @@ -14,12 +14,12 @@ export const createSiteWiseLegacyDataSource = ( name: 'site-wise', getRequestsFromQuery: ({ query: { dataStreamInfos } }): RequestInformation[] => dataStreamInfos.map(({ id, resolution }) => ({ id, resolution })), - initiateRequest: ({ query, requestInfo, onSuccess }, requestInformations) => { + initiateRequest: ({ query, request, onSuccess }, requestInformations) => { query.dataStreamInfos .filter((dataStreamInfo) => requestInformations.some((r) => r.id === dataStreamInfo.id)) .forEach((info) => { onRequestData({ - request: requestInfo, + request, resolution: info.resolution, onError: () => {}, onSuccess: (id: DataStreamId, data: DataStream) => { diff --git a/packages/core/src/data-sources/site-wise/client/client.spec.ts b/packages/core/src/data-sources/site-wise/client/client.spec.ts index 88378883a..9235d6c1a 100644 --- a/packages/core/src/data-sources/site-wise/client/client.spec.ts +++ b/packages/core/src/data-sources/site-wise/client/client.spec.ts @@ -188,14 +188,16 @@ describe('getAggregatedPropertyDataPoints', () => { const endDate = new Date(2001, 0, 0); const aggregateTypes = [AggregateType.AVERAGE]; - await expect(async () => { await client.getAggregatedPropertyDataPoints({ - query, - onSuccess, - onError, - start: startDate, - end: endDate, - aggregateTypes, - })}).rejects.toThrowError(); + await expect(async () => { + await client.getAggregatedPropertyDataPoints({ + query, + onSuccess, + onError, + start: startDate, + end: endDate, + aggregateTypes, + }); + }).rejects.toThrowError(); }); it('returns data point on success', async () => { diff --git a/packages/core/src/data-sources/site-wise/client/getAggregatedPropertyDataPoints.ts b/packages/core/src/data-sources/site-wise/client/getAggregatedPropertyDataPoints.ts index 056f4daa5..1246cc299 100644 --- a/packages/core/src/data-sources/site-wise/client/getAggregatedPropertyDataPoints.ts +++ b/packages/core/src/data-sources/site-wise/client/getAggregatedPropertyDataPoints.ts @@ -58,12 +58,14 @@ const getAggregatedPropertyDataPointsForProperty = ({ .map((assetPropertyValue) => aggregateToDataPoint(assetPropertyValue)) .filter(isDefined); - onSuccess([dataStreamFromSiteWise({ - assetId, - propertyId, - dataPoints, - resolution: RESOLUTION_TO_MS_MAPPING[resolution] - })]); + onSuccess([ + dataStreamFromSiteWise({ + assetId, + propertyId, + dataPoints, + resolution: RESOLUTION_TO_MS_MAPPING[resolution], + }), + ]); } if (nextToken) { @@ -112,26 +114,25 @@ export const getAggregatedPropertyDataPoints = async ({ const requests = query.assets .map(({ assetId, properties }) => properties.map(({ propertyId, resolution: propertyResolution }) => { - const resolutionOverride = propertyResolution || resolution; - - if (resolutionOverride == null) { - throw new Error('Resolution must be either specified in requestConfig or query'); - } + const resolutionOverride = propertyResolution || resolution; - return getAggregatedPropertyDataPointsForProperty({ - client, - assetId, - propertyId, - start, - end, - resolution: resolutionOverride, - aggregateTypes, - maxResults, - onSuccess, - onError, - }) + if (resolutionOverride == null) { + throw new Error('Resolution must be either specified in requestConfig or query'); } - ) + + return getAggregatedPropertyDataPointsForProperty({ + client, + assetId, + propertyId, + start, + end, + resolution: resolutionOverride, + aggregateTypes, + maxResults, + onSuccess, + onError, + }); + }) ) .flat(); diff --git a/packages/core/src/data-sources/site-wise/client/getHistoricalPropertyDataPoints.ts b/packages/core/src/data-sources/site-wise/client/getHistoricalPropertyDataPoints.ts index 05b313205..8b8007e57 100644 --- a/packages/core/src/data-sources/site-wise/client/getHistoricalPropertyDataPoints.ts +++ b/packages/core/src/data-sources/site-wise/client/getHistoricalPropertyDataPoints.ts @@ -103,5 +103,9 @@ export const getHistoricalPropertyDataPoints = async ({ ) .flat(); - await Promise.all(requests); + try { + await Promise.all(requests); + } catch (err) { + // NOOP + } }; diff --git a/packages/core/src/data-sources/site-wise/client/getLatestPropertyDataPoint.ts b/packages/core/src/data-sources/site-wise/client/getLatestPropertyDataPoint.ts index f0c691e15..2aeede4de 100644 --- a/packages/core/src/data-sources/site-wise/client/getLatestPropertyDataPoint.ts +++ b/packages/core/src/data-sources/site-wise/client/getLatestPropertyDataPoint.ts @@ -18,7 +18,7 @@ export const getLatestPropertyDataPoint = async ({ client: IoTSiteWiseClient; }): Promise => { const requests = assets - .map(({ assetId,properties }) => + .map(({ assetId, properties }) => properties.map(({ propertyId }) => { return client .send(new GetAssetPropertyValueCommand({ assetId, propertyId })) diff --git a/packages/core/src/data-sources/site-wise/data-source.spec.ts b/packages/core/src/data-sources/site-wise/data-source.spec.ts index 1afab77e6..418c27244 100644 --- a/packages/core/src/data-sources/site-wise/data-source.spec.ts +++ b/packages/core/src/data-sources/site-wise/data-source.spec.ts @@ -3,16 +3,40 @@ import { IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; import { createDataSource, SITEWISE_DATA_SOURCE } from './data-source'; import { MINUTE_IN_MS, HOUR_IN_MS } from '../../common/time'; import { SiteWiseDataStreamQuery } from './types.d'; -import { ASSET_PROPERTY_DOUBLE_VALUE, AGGREGATE_VALUES, ASSET_PROPERTY_VALUE_HISTORY } from '../../common/tests/mocks/assetPropertyValue'; +import { + ASSET_PROPERTY_DOUBLE_VALUE, + AGGREGATE_VALUES, + ASSET_PROPERTY_VALUE_HISTORY, +} from '../../common/tests/mocks/assetPropertyValue'; import { createSiteWiseSDK } from '../../common/tests/util'; import { toDataStreamId } from './util/dataStreamId'; import { IotAppKitDataModule } from '../../data-module/IotAppKitDataModule'; +import { TimeSeriesDataRequest } from '../../data-module/data-cache/requestTypes'; it('initializes', () => { expect(() => createDataSource(new IoTSiteWiseClient({ region: 'us-east' }))).not.toThrowError(); }); const noop = () => {}; +const LAST_MINUTE_REQUEST: TimeSeriesDataRequest = { + viewport: { + duration: MINUTE_IN_MS, + }, + settings: { + fetchMostRecentBeforeEnd: true, + }, +}; + +const HISTORICAL_REQUEST: TimeSeriesDataRequest = { + viewport: { + start: new Date(2010, 0, 0), + end: new Date(2011, 0, 0), + }, + settings: { + fetchFromStartToEnd: true, + }, +}; + describe('initiateRequest', () => { it('does not call SDK when query contains no assets', () => { const getAssetPropertyValue = jest.fn(); @@ -37,12 +61,7 @@ describe('initiateRequest', () => { source: SITEWISE_DATA_SOURCE, assets: [], }, - requestInfo: { - viewport: { - duration: MINUTE_IN_MS, - }, - onlyFetchLatestValue: true, - }, + request: LAST_MINUTE_REQUEST, }, [] ); @@ -53,7 +72,7 @@ describe('initiateRequest', () => { expect(getInterpolatedAssetPropertyValues).not.toBeCalled(); }); - describe('onlyFetchLatestValue', () => { + describe('fetch latest before end', () => { describe('on error', () => { it('calls `onError` callback', async () => { const ERR_MESSAGE = 'some critical error! page oncall immediately'; @@ -79,12 +98,7 @@ describe('initiateRequest', () => { onError, onSuccess, query, - requestInfo: { - viewport: { - duration: MINUTE_IN_MS, - }, - onlyFetchLatestValue: true, - }, + request: LAST_MINUTE_REQUEST, }, [] ); @@ -100,7 +114,7 @@ describe('initiateRequest', () => { }); }); - it('gets latest value when provided with a duration and `onlyFetchLatestValue` is true', async () => { + it('gets latest value when provided with a duration and `fetchLatestBeforeEnd` is true', async () => { const getAssetPropertyValue = jest.fn().mockResolvedValue(ASSET_PROPERTY_DOUBLE_VALUE); const getAssetPropertyAggregates = jest.fn(); const getAssetPropertyValueHistory = jest.fn(); @@ -128,12 +142,7 @@ describe('initiateRequest', () => { onError, onSuccess, query, - requestInfo: { - viewport: { - duration: MINUTE_IN_MS, - }, - onlyFetchLatestValue: true, - }, + request: LAST_MINUTE_REQUEST, }, [] ); @@ -176,7 +185,7 @@ describe('initiateRequest', () => { const query: SiteWiseDataStreamQuery = { source: SITEWISE_DATA_SOURCE, - assets: [{ assetId: ASSET_ID, properties: [ { propertyId: PROPERTY_1 }, { propertyId: PROPERTY_2 }] }], + assets: [{ assetId: ASSET_ID, properties: [{ propertyId: PROPERTY_1 }, { propertyId: PROPERTY_2 }] }], }; dataSource.initiateRequest( @@ -184,12 +193,7 @@ describe('initiateRequest', () => { onError: noop, onSuccess: noop, query, - requestInfo: { - viewport: { - duration: MINUTE_IN_MS, - }, - onlyFetchLatestValue: true, - }, + request: LAST_MINUTE_REQUEST, }, [] ); @@ -232,12 +236,7 @@ describe('initiateRequest', () => { onError: noop, onSuccess: noop, query, - requestInfo: { - viewport: { - duration: MINUTE_IN_MS, - }, - onlyFetchLatestValue: true, - }, + request: LAST_MINUTE_REQUEST, }, [] ); @@ -280,7 +279,7 @@ describe('e2e through data-module', () => { assets: [{ assetId, properties: [{ propertyId }] }], source: dataSource.name, } as SiteWiseDataStreamQuery, - requestInfo: { viewport: { start: new Date(2000, 0, 0), end: new Date() }, onlyFetchLatestValue: false }, + request: HISTORICAL_REQUEST, }, dataStreamCallback ); @@ -321,7 +320,10 @@ describe('e2e through data-module', () => { assets: [{ assetId, properties: [{ propertyId }] }], source: dataSource.name, } as SiteWiseDataStreamQuery, - requestInfo: { viewport: { start: new Date(2000, 0, 0), end: new Date() }, onlyFetchLatestValue: true }, + request: { + viewport: { start: new Date(2000, 0, 0), end: new Date() }, + settings: { fetchMostRecentBeforeEnd: true }, + }, }, dataStreamCallback ); @@ -342,7 +344,7 @@ describe('e2e through data-module', () => { }); describe('aggregated data', () => { - it('determines resolution based on mapping', async () => { + it('requests aggregated data with correct resolution based on resolutionMap and uses default aggregate type', async () => { const getAssetPropertyValue = jest.fn(); const getAssetPropertyAggregates = jest.fn().mockResolvedValue(AGGREGATE_VALUES); const getAssetPropertyValueHistory = jest.fn(); @@ -374,18 +376,18 @@ describe('aggregated data', () => { onError, onSuccess, query, - requestInfo: { + request: { viewport: { duration: FIFTY_FIVE_MINUTES, }, - onlyFetchLatestValue: false, - requestConfig: { + settings: { + fetchMostRecentBeforeEnd: false, fetchAggregatedData: true, resolution: { [FIFTY_HOURS]: '1d', [FIFTY_MINUTES]: '1h', - } - } + }, + }, }, }, [] @@ -403,7 +405,7 @@ describe('aggregated data', () => { assetId: query.assets[0].assetId, propertyId: query.assets[0].properties[0].propertyId, aggregateTypes: ['AVERAGE'], - resolution: '1h' + resolution: '1h', }) ); @@ -426,8 +428,8 @@ describe('aggregated data', () => { { x: 946609200000, y: 10, - } - ] + }, + ], }, resolution: HOUR_IN_MS, dataType: 'NUMBER', @@ -467,15 +469,15 @@ describe('aggregated data', () => { onError, onSuccess, query, - requestInfo: { + request: { viewport: { duration: FIFTY_FIVE_MINUTES, }, - onlyFetchLatestValue: false, - requestConfig: { + settings: { fetchAggregatedData: true, - resolution - } + fetchFromStartToEnd: true, + resolution: resolution, + }, }, }, [] @@ -493,7 +495,7 @@ describe('aggregated data', () => { assetId: query.assets[0].assetId, propertyId: query.assets[0].properties[0].propertyId, aggregateTypes: ['AVERAGE'], - resolution + resolution, }) ); @@ -516,8 +518,8 @@ describe('aggregated data', () => { { x: 946609200000, y: 10, - } - ] + }, + ], }, resolution: MINUTE_IN_MS, dataType: 'NUMBER', @@ -526,16 +528,12 @@ describe('aggregated data', () => { }); it('requests specific resolution per asset property', async () => { - const getAssetPropertyValue = jest.fn(); const getAssetPropertyAggregates = jest.fn().mockResolvedValue(AGGREGATE_VALUES); const getAssetPropertyValueHistory = jest.fn().mockResolvedValue(ASSET_PROPERTY_VALUE_HISTORY); - const getInterpolatedAssetPropertyValues = jest.fn(); const mockSDK = createSiteWiseSDK({ - getAssetPropertyValue, getAssetPropertyValueHistory, getAssetPropertyAggregates, - getInterpolatedAssetPropertyValues, }); const dataSource = createDataSource(mockSDK); @@ -547,36 +545,29 @@ describe('aggregated data', () => { assets: [ { assetId: 'some-asset-id', - properties: [ - { propertyId: 'some-property-id', resolution }, - { propertyId: 'some-property-id2' } - ] + properties: [{ propertyId: 'some-property-id', resolution }, { propertyId: 'some-property-id2' }], }, { assetId: 'some-asset-id2', - properties: [ - { propertyId: 'some-property-id', resolution }, - { propertyId: 'some-property-id2' } - ] + properties: [{ propertyId: 'some-property-id', resolution }, { propertyId: 'some-property-id2' }], }, ], }; - const onError = jest.fn(); const onSuccess = jest.fn(); - const FIFTY_FIVE_MINUTES = MINUTE_IN_MS * 55; - dataSource.initiateRequest( { - onError, + onError: () => {}, onSuccess, query, - requestInfo: { + request: { viewport: { - duration: FIFTY_FIVE_MINUTES, + duration: MINUTE_IN_MS * 55, + }, + settings: { + fetchFromStartToEnd: true, }, - onlyFetchLatestValue: false }, }, [] @@ -584,16 +575,13 @@ describe('aggregated data', () => { await flushPromises(); - expect(getAssetPropertyValue).not.toBeCalled(); - expect(getInterpolatedAssetPropertyValues).not.toBeCalled(); - expect(getAssetPropertyAggregates).toBeCalledTimes(2); expect(getAssetPropertyAggregates).toBeCalledWith( expect.objectContaining({ assetId: query.assets[0].assetId, propertyId: query.assets[0].properties[0].propertyId, aggregateTypes: ['AVERAGE'], - resolution + resolution, }) ); expect(getAssetPropertyAggregates).toBeCalledWith( @@ -601,11 +589,12 @@ describe('aggregated data', () => { assetId: query.assets[1].assetId, propertyId: query.assets[1].properties[0].propertyId, aggregateTypes: ['AVERAGE'], - resolution + resolution, }) ); expect(getAssetPropertyValueHistory).toBeCalledTimes(2); + expect(getAssetPropertyValueHistory).toBeCalledWith( expect.objectContaining({ assetId: query.assets[0].assetId, @@ -619,8 +608,6 @@ describe('aggregated data', () => { }) ); - expect(onError).not.toBeCalled(); - expect(onSuccess).toBeCalledTimes(4); expect(onSuccess).toBeCalledWith([ expect.objectContaining({ @@ -638,53 +625,13 @@ describe('aggregated data', () => { { x: 946609200000, y: 10, - } - ] - }, - resolution: MINUTE_IN_MS, - dataType: 'NUMBER', - }), - ]); - expect(onSuccess).toBeCalledWith([ - expect.objectContaining({ - id: toDataStreamId({ assetId: 'some-asset-id2', propertyId: 'some-property-id' }), - aggregates: { - [MINUTE_IN_MS]: [ - { - x: 946602000000, - y: 5, - }, - { - x: 946605600000, - y: 7, }, - { - x: 946609200000, - y: 10, - } - ] + ], }, resolution: MINUTE_IN_MS, dataType: 'NUMBER', }), ]); - - expect(onSuccess).toBeCalledWith([ - expect.objectContaining({ - id: toDataStreamId({ assetId: 'some-asset-id', propertyId: 'some-property-id2' }), - data: [{ x: 1000099, y: 10.123 }, { x: 2000000, y: 12.01 }], - resolution: 0, - dataType: 'NUMBER', - }), - ]); - expect(onSuccess).toBeCalledWith([ - expect.objectContaining({ - id: toDataStreamId({ assetId: 'some-asset-id2', propertyId: 'some-property-id2' }), - data: [{ x: 1000099, y: 10.123 }, { x: 2000000, y: 12.01 }], - resolution: 0, - dataType: 'NUMBER', - }), - ]); }); it('requests raw data if specified per asset property', async () => { @@ -707,10 +654,8 @@ describe('aggregated data', () => { assets: [ { assetId: 'some-asset-id', - properties: [ - { propertyId: 'some-property-id', resolution: '0' } - ] - } + properties: [{ propertyId: 'some-property-id', resolution: '0' }], + }, ], }; @@ -724,15 +669,15 @@ describe('aggregated data', () => { onError, onSuccess, query, - requestInfo: { + request: { viewport: { duration: FIFTY_FIVE_MINUTES, }, - onlyFetchLatestValue: false, - requestConfig: { + settings: { + fetchFromStartToEnd: true, fetchAggregatedData: true, - resolution: '1m' - } + resolution: '1m', + }, }, }, [] @@ -760,7 +705,10 @@ describe('aggregated data', () => { expect(onSuccess).toBeCalledWith([ expect.objectContaining({ id: toDataStreamId({ assetId: 'some-asset-id', propertyId: 'some-property-id' }), - data: [{ x: 1000099, y: 10.123 }, { x: 2000000, y: 12.01 }], + data: [ + { x: 1000099, y: 10.123 }, + { x: 2000000, y: 12.01 }, + ], resolution: 0, dataType: 'NUMBER', }), @@ -780,28 +728,31 @@ describe('aggregated data', () => { const onError = jest.fn(); const onSuccess = jest.fn(); - expect(() => { dataSource.initiateRequest( - { - onError, - onSuccess, - query, - requestInfo: { - viewport: { - duration: HOUR_IN_MS, - }, - onlyFetchLatestValue: false, - requestConfig: { - fetchAggregatedData: true, - resolution: { - [MINUTE_IN_MS]: 'not_a_valid_resolution' + expect(() => { + dataSource.initiateRequest( + { + onError, + onSuccess, + query, + request: { + viewport: { + duration: HOUR_IN_MS, }, - } + settings: { + fetchAggregatedData: true, + fetchMostRecentBeforeEnd: false, + fetchFromStartToEnd: true, + resolution: { + [MINUTE_IN_MS]: 'not_a_valid_resolution', + }, + }, + }, }, - }, - [] - )}).toThrow(); + [] + ); + }).toThrow(); expect(onError).not.toBeCalled(); expect(onSuccess).not.toBeCalled(); }); -}) +}); diff --git a/packages/core/src/data-sources/site-wise/data-source.ts b/packages/core/src/data-sources/site-wise/data-source.ts index 462d0a197..6ff903f0d 100644 --- a/packages/core/src/data-sources/site-wise/data-source.ts +++ b/packages/core/src/data-sources/site-wise/data-source.ts @@ -14,56 +14,59 @@ const DEFAULT_RESOLUTION_MAPPING = { [MINUTE_IN_MS]: SupportedResolutions.ONE_MINUTE, [HOUR_IN_MS]: SupportedResolutions.ONE_HOUR, [DAY_IN_MS]: SupportedResolutions.ONE_DAY, -} +}; const isSiteWiseResolution = (resolution: string | SupportedResolutions): resolution is SupportedResolutions => { return Object.values(SupportedResolutions).includes(resolution as SupportedResolutions); -} +}; export const determineResolution = ({ resolution, fetchAggregatedData = false, start, - end + end, }: { resolution?: ResolutionConfig; - fetchAggregatedData?: boolean, + fetchAggregatedData?: boolean; start: Date; end: Date; }): string => { - if (fetchAggregatedData) { - const viewportTimeSpan = end.getTime() - start.getTime(); + if (fetchAggregatedData) { + const viewportTimeSpan = end.getTime() - start.getTime(); - const resolutionOverride = resolution || DEFAULT_RESOLUTION_MAPPING; - - if (typeof resolutionOverride === 'string') { - return resolutionOverride; - } + const resolutionOverride = resolution || DEFAULT_RESOLUTION_MAPPING; - const matchedViewport = Object.keys(resolutionOverride) - .sort((a, b) => parseInt(b) - parseInt(a)) - .find(viewport => viewportTimeSpan >= parseInt(viewport)); + if (typeof resolutionOverride === 'string') { + return resolutionOverride; + } - if (matchedViewport) { - const matchedResolution = resolutionOverride[parseInt(matchedViewport)] as string; + const matchedViewport = Object.keys(resolutionOverride) + .sort((a, b) => parseInt(b) - parseInt(a)) + .find((viewport) => viewportTimeSpan >= parseInt(viewport)); - if (!isSiteWiseResolution(matchedResolution)) { - throw new Error(`${matchedResolution} is not a valid SiteWise aggregation resolution, must match regex pattern '1m|1h|1d'`); - } + if (matchedViewport) { + const matchedResolution = resolutionOverride[parseInt(matchedViewport)] as string; - return matchedResolution; + if (!isSiteWiseResolution(matchedResolution)) { + throw new Error( + `${matchedResolution} is not a valid SiteWise aggregation resolution, must match regex pattern '1m|1h|1d'` + ); } + + return matchedResolution; } + } - return '0'; -} + return '0'; +}; -const separateDataQueries = (query: SiteWiseDataStreamQuery): - { - aggregatedDataQueries?: SiteWiseAssetDataStreamQuery; - rawDataQueries?: SiteWiseAssetDataStreamQuery; - defaultResolutionDataQueries?: SiteWiseAssetDataStreamQuery; - } => { +const separateDataQueries = ( + query: SiteWiseDataStreamQuery +): { + aggregatedDataQueries?: SiteWiseAssetDataStreamQuery; + rawDataQueries?: SiteWiseAssetDataStreamQuery; + defaultResolutionDataQueries?: SiteWiseAssetDataStreamQuery; +} => { let aggregatedDataQueries: SiteWiseAssetDataStreamQuery | undefined; let rawDataQueries: SiteWiseAssetDataStreamQuery | undefined; let defaultResolutionDataQueries: SiteWiseAssetDataStreamQuery | undefined; @@ -76,19 +79,19 @@ const separateDataQueries = (query: SiteWiseDataStreamQuery): properties.forEach(({ propertyId, resolution }) => { if (resolution === '0') { if (!rawDataProperties) { - rawDataProperties = [{ propertyId, resolution }] + rawDataProperties = [{ propertyId, resolution }]; } else { rawDataProperties.push({ propertyId, resolution }); } } else if (typeof resolution === 'string' && isSiteWiseResolution(resolution)) { if (!aggregatedDataProperties) { - aggregatedDataProperties = [{ propertyId, resolution }] + aggregatedDataProperties = [{ propertyId, resolution }]; } else { aggregatedDataProperties.push({ propertyId, resolution }); } } else { if (!defaultResolutionDataProperties) { - defaultResolutionDataProperties = [{ propertyId, resolution }] + defaultResolutionDataProperties = [{ propertyId, resolution }]; } else { defaultResolutionDataProperties.push({ propertyId, resolution }); } @@ -97,7 +100,7 @@ const separateDataQueries = (query: SiteWiseDataStreamQuery): if (aggregatedDataProperties) { if (!aggregatedDataQueries) { - aggregatedDataQueries = { ...query, assets: [{ assetId, properties: aggregatedDataProperties }] } + aggregatedDataQueries = { ...query, assets: [{ assetId, properties: aggregatedDataProperties }] }; } else { aggregatedDataQueries.assets.push({ assetId, properties: aggregatedDataProperties }); } @@ -105,7 +108,7 @@ const separateDataQueries = (query: SiteWiseDataStreamQuery): if (rawDataProperties) { if (!rawDataQueries) { - rawDataQueries = { ...query, assets: [{ assetId, properties: rawDataProperties }] } + rawDataQueries = { ...query, assets: [{ assetId, properties: rawDataProperties }] }; } else { rawDataQueries.assets.push({ assetId, properties: rawDataProperties }); } @@ -113,7 +116,7 @@ const separateDataQueries = (query: SiteWiseDataStreamQuery): if (defaultResolutionDataProperties) { if (!defaultResolutionDataQueries) { - defaultResolutionDataQueries = { ...query, assets: [{ assetId, properties: defaultResolutionDataProperties }] } + defaultResolutionDataQueries = { ...query, assets: [{ assetId, properties: defaultResolutionDataProperties }] }; } else { defaultResolutionDataQueries.assets.push({ assetId, properties: defaultResolutionDataProperties }); } @@ -121,91 +124,95 @@ const separateDataQueries = (query: SiteWiseDataStreamQuery): }); return { rawDataQueries, aggregatedDataQueries, defaultResolutionDataQueries }; -} +}; export const createDataSource = (siteWise: IoTSiteWiseClient): DataSource => { const client = new SiteWiseClient(siteWise); return { name: SITEWISE_DATA_SOURCE, - initiateRequest: ({ query, requestInfo, onSuccess, onError }) => { - if (requestInfo.onlyFetchLatestValue) { + initiateRequest: ({ query, request, onSuccess, onError }) => { + if (request.settings?.fetchMostRecentBeforeEnd) { return client.getLatestPropertyDataPoint({ query, onSuccess, onError }); } - const start = viewportStartDate(requestInfo.viewport); - const end = viewportEndDate(requestInfo.viewport); + const start = viewportStartDate(request.viewport); + const end = viewportEndDate(request.viewport); const resolution = determineResolution({ - resolution: requestInfo.requestConfig?.resolution, - fetchAggregatedData: requestInfo.requestConfig?.fetchAggregatedData, + resolution: request.settings?.resolution, + fetchAggregatedData: request.settings?.fetchAggregatedData, start, - end + end, }); - // TODO: Support multiple aggregations - const aggregateTypes = [AggregateType.AVERAGE]; - - const { - aggregatedDataQueries, - rawDataQueries, - defaultResolutionDataQueries - } = separateDataQueries(query); + const { aggregatedDataQueries, rawDataQueries, defaultResolutionDataQueries } = separateDataQueries(query); const requests = []; + // TODO: Support multiple aggregations + const aggregateTypes = [AggregateType.AVERAGE]; + if (aggregatedDataQueries) { - requests.push(() => client.getAggregatedPropertyDataPoints({ - query: aggregatedDataQueries, - onSuccess, - onError, - start, - end, - aggregateTypes - })); + requests.push(() => + client.getAggregatedPropertyDataPoints({ + query: aggregatedDataQueries, + onSuccess, + onError, + start, + end, + aggregateTypes, + }) + ); } if (rawDataQueries) { - requests.push(() => client.getHistoricalPropertyDataPoints({ query: rawDataQueries, onSuccess, onError, start, end })); + requests.push(() => + client.getHistoricalPropertyDataPoints({ query: rawDataQueries, onSuccess, onError, start, end }) + ); } if (defaultResolutionDataQueries) { if (resolution !== '0') { - requests.push(() => client.getAggregatedPropertyDataPoints({ - query: defaultResolutionDataQueries, - onSuccess, - onError, - start, - end, - resolution, - aggregateTypes - })); + requests.push(() => + client.getAggregatedPropertyDataPoints({ + query: defaultResolutionDataQueries, + onSuccess, + onError, + start, + end, + resolution, + aggregateTypes, + }) + ); } else { - requests.push(() => client.getHistoricalPropertyDataPoints({ - query: defaultResolutionDataQueries, - onSuccess, - onError, - start, - end - })); + requests.push(() => + client.getHistoricalPropertyDataPoints({ + query: defaultResolutionDataQueries, + onSuccess, + onError, + start, + end, + }) + ); } } return Promise.all(requests.map(async (request) => request())); }, - getRequestsFromQuery: ({ query, requestInfo }) => { - const start = viewportStartDate(requestInfo.viewport); - const end = viewportEndDate(requestInfo.viewport); + getRequestsFromQuery: ({ query, request }) => { + const start = viewportStartDate(request.viewport); + const end = viewportEndDate(request.viewport); const resolution = determineResolution({ - resolution: requestInfo.requestConfig?.resolution, - fetchAggregatedData: requestInfo.requestConfig?.fetchAggregatedData, + resolution: request.settings?.resolution, + fetchAggregatedData: request.settings?.fetchAggregatedData, start, - end + end, }); - return query.assets.flatMap(({assetId, properties}) => + return query.assets.flatMap(({ assetId, properties }) => properties.map(({ propertyId, resolution: resolutionOverride }) => ({ - id: toDataStreamId({assetId, propertyId}), + id: toDataStreamId({ assetId, propertyId }), resolution: RESOLUTION_TO_MS_MAPPING[resolutionOverride || resolution], })) ); diff --git a/packages/core/src/data-sources/site-wise/dataStreamFromSiteWise.ts b/packages/core/src/data-sources/site-wise/dataStreamFromSiteWise.ts index 6929de355..e7b1f97bd 100644 --- a/packages/core/src/data-sources/site-wise/dataStreamFromSiteWise.ts +++ b/packages/core/src/data-sources/site-wise/dataStreamFromSiteWise.ts @@ -14,18 +14,18 @@ export const dataStreamFromSiteWise = ({ resolution?: number; }): DataStream => { const dataStream: DataStream = { - name: toDataStreamId({assetId, propertyId}), - id: toDataStreamId({assetId, propertyId}), + name: toDataStreamId({ assetId, propertyId }), + id: toDataStreamId({ assetId, propertyId }), data: dataPoints || [], resolution, // TODO: Better support for various data types, will need to utilize associated asset information to infer. dataType: DataType.NUMBER, - } + }; if (resolution) { dataStream.aggregates = { - [resolution]: dataPoints || [] - } + [resolution]: dataPoints || [], + }; } return dataStream; diff --git a/packages/core/src/data-sources/site-wise/types.d.ts b/packages/core/src/data-sources/site-wise/types.d.ts index 6045b5ea0..1de9e7cae 100644 --- a/packages/core/src/data-sources/site-wise/types.d.ts +++ b/packages/core/src/data-sources/site-wise/types.d.ts @@ -11,13 +11,13 @@ export type AssetId = string; export type PropertyAlias = string; export type PropertyQuery = { - propertyId: string, - resolution?: string -} + propertyId: string; + resolution?: string; +}; export type AssetQuery = { assetId: AssetId; - properties: PropertyQuery[] + properties: PropertyQuery[]; }; type SiteWiseAssetDataStreamQuery = DataStreamQuery & { diff --git a/packages/core/src/data-sources/site-wise/util/resolution.ts b/packages/core/src/data-sources/site-wise/util/resolution.ts index be3ade8c7..10a2bb9e5 100644 --- a/packages/core/src/data-sources/site-wise/util/resolution.ts +++ b/packages/core/src/data-sources/site-wise/util/resolution.ts @@ -3,12 +3,12 @@ import { MINUTE_IN_MS, HOUR_IN_MS, DAY_IN_MS } from '../../../common/time'; export enum SupportedResolutions { ONE_MINUTE = '1m', ONE_HOUR = '1h', - ONE_DAY = '1d' + ONE_DAY = '1d', } export const RESOLUTION_TO_MS_MAPPING: { [key: string]: number } = { '0': 0, [SupportedResolutions.ONE_MINUTE]: MINUTE_IN_MS, [SupportedResolutions.ONE_HOUR]: HOUR_IN_MS, - [SupportedResolutions.ONE_DAY]: DAY_IN_MS -} + [SupportedResolutions.ONE_DAY]: DAY_IN_MS, +};