From 173f46a8951339da412a9e5c3ba282f41a897718 Mon Sep 17 00:00:00 2001 From: Bowei Han Date: Wed, 9 Feb 2022 12:19:34 -0500 Subject: [PATCH] feat: query provider classes and TimeSeriesData support (#51) --- .../iot-connector/iot-connector.tsx | 2 +- .../core/src/app-kit-component-session.ts | 39 +++ .../data-module/IotAppKitDataModule.spec.ts | 308 +++++++++++------- .../src/data-module/IotAppKitDataModule.ts | 4 +- .../subscription-store/subscriptionStore.ts | 2 +- packages/core/src/data-module/types.d.ts | 11 +- packages/core/src/index.ts | 3 + packages/core/src/interface.d.ts | 33 +- packages/core/src/iotAppKit.ts | 10 +- .../time-series-data/coordinator.ts | 18 +- .../time-series-data/data-source.spec.ts | 48 +-- .../time-series-data/provider.spec.ts | 112 +++++++ .../iotsitewise/time-series-data/provider.ts | 46 +++ .../iotsitewise/time-series-data/types.d.ts | 7 + packages/core/src/module-namespace.ts | 19 ++ packages/core/src/query-namespace.ts | 24 ++ 16 files changed, 530 insertions(+), 156 deletions(-) create mode 100644 packages/core/src/app-kit-component-session.ts create mode 100644 packages/core/src/iotsitewise/time-series-data/provider.spec.ts create mode 100644 packages/core/src/iotsitewise/time-series-data/provider.ts create mode 100644 packages/core/src/module-namespace.ts create mode 100644 packages/core/src/query-namespace.ts diff --git a/packages/components/src/components/iot-connector/iot-connector.tsx b/packages/components/src/components/iot-connector/iot-connector.tsx index 0c4ad1e79..a7f70740d 100644 --- a/packages/components/src/components/iot-connector/iot-connector.tsx +++ b/packages/components/src/components/iot-connector/iot-connector.tsx @@ -38,7 +38,7 @@ export class IotConnector { queries: this.queries, request: this.request, }, - (dataStreams: DataStream[]) => { + ({ dataStreams }) => { this.dataStreams = bindStylesToDataStreams({ dataStreams, styleSettings: this.styleSettings }); } ); diff --git a/packages/core/src/app-kit-component-session.ts b/packages/core/src/app-kit-component-session.ts new file mode 100644 index 000000000..e3a716b6e --- /dev/null +++ b/packages/core/src/app-kit-component-session.ts @@ -0,0 +1,39 @@ +import { SiteWiseAssetModule } from '.'; +import { IotAppKitDataModule } from './data-module/IotAppKitDataModule'; +import { IoTAppKitComponentSession, DataModuleSession } from './interface.d'; + +/** + * Component session to manage component data module sessions. + * Contains a reference to sitewise data modules + */ +export class AppKitComponentSession implements IoTAppKitComponentSession { + public componentId: string; + + public siteWiseTimeSeriesModule: IotAppKitDataModule; + + public siteWiseAssetModule: SiteWiseAssetModule; + + private sessions: DataModuleSession[] = []; + + constructor({ + componentId, + siteWiseTimeSeriesModule, + siteWiseAssetModule, + }: { + componentId: string; + siteWiseTimeSeriesModule: IotAppKitDataModule; + siteWiseAssetModule: SiteWiseAssetModule; + }) { + this.componentId = componentId; + this.siteWiseTimeSeriesModule = siteWiseTimeSeriesModule; + this.siteWiseAssetModule = siteWiseAssetModule; + } + + attachDataModuleSession(session: DataModuleSession): void { + this.sessions.push(session); + } + + close(): void { + this.sessions.forEach((session) => session.close()); + } +} diff --git a/packages/core/src/data-module/IotAppKitDataModule.spec.ts b/packages/core/src/data-module/IotAppKitDataModule.spec.ts index 4f541eb7b..7b38dff92 100644 --- a/packages/core/src/data-module/IotAppKitDataModule.spec.ts +++ b/packages/core/src/data-module/IotAppKitDataModule.spec.ts @@ -103,7 +103,7 @@ describe('update subscription', () => { dataModule.registerDataSource(dataSource); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); const queries: SiteWiseDataStreamQuery[] = [{ source: dataSource.name, assets: [] }]; @@ -117,10 +117,10 @@ describe('update subscription', () => { }, }, }, - dataStreamCallback + timeSeriesCallback ); - dataStreamCallback.mockClear(); + timeSeriesCallback.mockClear(); (dataSource.initiateRequest as Mock).mockClear(); update({ queries: [DATA_STREAM_QUERY] }); @@ -128,7 +128,7 @@ describe('update subscription', () => { await flushPromises(); jest.advanceTimersByTime(SECOND_IN_MS); - // expect(dataStreamCallback).toHaveBeenLastCalledWith([expect.objectContaining({ id: DATA_STREAM.id })]); + // expect(timeSeriesCallback).toHaveBeenLastCalledWith([expect.objectContaining({ id: DATA_STREAM.id })]); expect(dataSource.initiateRequest).toBeCalled(); }); }); @@ -144,7 +144,7 @@ describe('initial request', () => { dataModule.registerDataSource(dataSource); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); dataModule.subscribeToDataStreams( { @@ -156,10 +156,10 @@ describe('initial request', () => { }, }, }, - dataStreamCallback + timeSeriesCallback ); - expect(dataStreamCallback).not.toBeCalled(); + expect(timeSeriesCallback).not.toBeCalled(); expect(dataSource.initiateRequest).not.toBeCalled(); }); @@ -186,21 +186,27 @@ describe('initial request', () => { dataModule.registerDataSource(dataSource); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); dataModule.subscribeToDataStreams( { queries: [query], request: { viewport: { start: START, end: END }, settings: { fetchFromStartToEnd: true } }, }, - dataStreamCallback + timeSeriesCallback ); - expect(dataStreamCallback).toBeCalledWith([ - expect.objectContaining({ - id: DATA_STREAM.id, - refId: REF_ID, - }), - ]); + expect(timeSeriesCallback).toBeCalledWith({ + dataStreams: [ + expect.objectContaining({ + id: DATA_STREAM.id, + refId: REF_ID, + }), + ], + viewport: { + start: START, + end: END, + }, + }); }); it('initiates a request for a data stream', () => { @@ -215,22 +221,29 @@ describe('initial request', () => { dataModule.registerDataSource(dataSource); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], request: { viewport: { start: START, end: END }, settings: { fetchFromStartToEnd: true } }, }, - dataStreamCallback + timeSeriesCallback ); - expect(dataStreamCallback).toBeCalledWith([ - expect.objectContaining({ - id: DATA_STREAM.id, - isLoading: true, - isRefreshing: true, - } as DataStreamStore), - ]); + + expect(timeSeriesCallback).toBeCalledWith({ + dataStreams: [ + expect.objectContaining({ + id: DATA_STREAM.id, + isLoading: true, + isRefreshing: true, + } as DataStreamStore), + ], + viewport: { + start: START, + end: END, + }, + }); expect(dataSource.initiateRequest).toBeCalledWith(expect.objectContaining({ query: DATA_STREAM_QUERY }), [ { id: DATA_STREAM.id, resolution: DATA_STREAM.resolution, start: START, end: END }, @@ -244,7 +257,10 @@ it('subscribes to a single data stream', async () => { dataModule.registerDataSource(dataSource); const { propertyId, assetId } = toSiteWiseAssetProperty(DATA_STREAM.id); - const dataStreamCallback = jest.fn(); + const START = new Date(2000, 0, 0); + const END = new Date(2001, 0, 0); + + const timeSeriesCallback = jest.fn(); dataModule.subscribeToDataStreams( { queries: [ @@ -259,21 +275,27 @@ it('subscribes to a single data stream', async () => { }, ], request: { - viewport: { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0) }, + viewport: { start: START, end: END }, settings: { fetchFromStartToEnd: true }, }, }, - dataStreamCallback + timeSeriesCallback ); jest.advanceTimersByTime(1); - expect(dataStreamCallback).toBeCalledWith([ - expect.objectContaining({ - id: DATA_STREAM.id, - resolution: DATA_STREAM.resolution, - }), - ]); + expect(timeSeriesCallback).toBeCalledWith({ + dataStreams: [ + expect.objectContaining({ + id: DATA_STREAM.id, + resolution: DATA_STREAM.resolution, + }), + ], + viewport: { + start: START, + end: END, + }, + }); }); it('throws error when subscribing to a non-existent data source', () => { @@ -303,6 +325,9 @@ it('requests data from a custom data source', () => { dataModule.registerDataSource(customSource); + const START = new Date(2000, 0, 0); + const END = new Date(2001, 0, 0); + dataModule.subscribeToDataStreams( { queries: [ @@ -312,7 +337,7 @@ it('requests data from a custom data source', () => { } as SiteWiseDataStreamQuery, ], request: { - viewport: { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0) }, + viewport: { start: START, end: END }, settings: { fetchFromStartToEnd: true, }, @@ -321,7 +346,13 @@ it('requests data from a custom data source', () => { onSuccess ); - expect(onSuccess).toHaveBeenCalledWith([expect.objectContaining({ id: DATA_STREAM.id })]); + expect(onSuccess).toBeCalledWith({ + dataStreams: [expect.objectContaining({ id: DATA_STREAM.id })], + viewport: { + start: START, + end: END, + }, + }); }); it('subscribes to multiple data streams', () => { @@ -361,8 +392,11 @@ it('subscribes to multiple queries on the same data source', () => { const onRequestData = jest.fn(); const source = createSiteWiseLegacyDataSource(onRequestData); + const START = new Date(2000, 0, 0); + const END = new Date(2001, 0, 0); + const request: TimeSeriesDataRequest = { - viewport: { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0) }, + viewport: { start: START, end: END }, }; const dataModule = new IotAppKitDataModule(); @@ -391,18 +425,27 @@ it('subscribes to multiple queries on the same data source', () => { expect(onRequestData).toHaveBeenNthCalledWith(1, expect.objectContaining({ dataStreamId: STRING_INFO_1.id })); expect(onRequestData).toHaveBeenNthCalledWith(2, expect.objectContaining({ dataStreamId: DATA_STREAM_INFO.id })); - expect(onSuccess).toHaveBeenCalledWith([ - expect.objectContaining({ id: STRING_INFO_1.id }), - expect.objectContaining({ id: DATA_STREAM_INFO.id }), - ]); + expect(onSuccess).toBeCalledWith({ + dataStreams: [ + expect.objectContaining({ id: STRING_INFO_1.id }), + expect.objectContaining({ id: DATA_STREAM_INFO.id }), + ], + viewport: { + start: START, + end: END, + }, + }); }); it('subscribes to multiple data sources', () => { const source = createSiteWiseLegacyDataSource(jest.fn()); const customSource = createCustomMockDataSource([DATA_STREAM]); + const START = new Date(2000, 0, 0); + const END = new Date(2001, 0, 0); + const request: TimeSeriesDataRequest = { - viewport: { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0) }, + viewport: { start: START, end: END }, }; const dataModule = new IotAppKitDataModule(); @@ -431,18 +474,27 @@ it('subscribes to multiple data sources', () => { onSuccess ); - expect(onSuccess).toHaveBeenCalledWith([ - expect.objectContaining({ id: STRING_INFO_1.id }), - expect.objectContaining({ id: customSourceAssetId }), - ]); + expect(onSuccess).toBeCalledWith({ + dataStreams: [ + expect.objectContaining({ id: STRING_INFO_1.id }), + expect.objectContaining({ id: customSourceAssetId }), + ], + viewport: { + start: START, + end: END, + }, + }); }); it('subscribes to multiple data streams on multiple data sources', () => { const source = createSiteWiseLegacyDataSource(jest.fn()); const customSource = createCustomMockDataSource([]); + const START = new Date(2000, 0, 0); + const END = new Date(2001, 0, 0); + const request: TimeSeriesDataRequest = { - viewport: { start: new Date(2000, 0, 0), end: new Date(2001, 0, 0) }, + viewport: { start: START, end: END }, }; const dataModule = new IotAppKitDataModule(); @@ -472,12 +524,18 @@ it('subscribes to multiple data streams on multiple data sources', () => { onSuccess ); - expect(onSuccess).toHaveBeenCalledWith([ - expect.objectContaining({ id: DATA_STREAM_INFO.id }), - expect.objectContaining({ id: STRING_INFO_1.id }), - expect.objectContaining({ id: customSourceAssetId_1 }), - expect.objectContaining({ id: customSourceAssetId_2 }), - ]); + expect(onSuccess).toBeCalledWith({ + dataStreams: [ + expect.objectContaining({ id: DATA_STREAM_INFO.id }), + expect.objectContaining({ id: STRING_INFO_1.id }), + expect.objectContaining({ id: customSourceAssetId_1 }), + expect.objectContaining({ id: customSourceAssetId_2 }), + ], + viewport: { + start: START, + end: END, + }, + }); }); it('only requests latest value', () => { @@ -553,30 +611,39 @@ describe('error handling', () => { const customSource = createMockSiteWiseDataSource([DATA_STREAM]); const dataModule = new IotAppKitDataModule({ initialDataCache: CACHE_WITH_ERROR }); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); dataModule.registerDataSource(customSource); + const START = new Date(2000, 0, 0); + const END = new Date(); + dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], request: { - viewport: { start: new Date(2000, 0, 0), end: new Date() }, + viewport: { start: START, end: END }, settings: { fetchFromStartToEnd: true }, }, }, - dataStreamCallback + timeSeriesCallback ); - expect(dataStreamCallback).toBeCalledTimes(1); - expect(dataStreamCallback).toBeCalledWith([expect.objectContaining({ error: ERR_MSG })]); + expect(timeSeriesCallback).toBeCalledTimes(1); + expect(timeSeriesCallback).toBeCalledWith({ + dataStreams: [expect.objectContaining({ error: ERR_MSG })], + viewport: { + start: START, + end: END, + }, + }); }); it('does not re-request a data stream with an error associated with it', async () => { const customSource = createMockSiteWiseDataSource([DATA_STREAM]); const dataModule = new IotAppKitDataModule({ initialDataCache: CACHE_WITH_ERROR }); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); dataModule.registerDataSource(customSource); @@ -591,12 +658,12 @@ describe('error handling', () => { }, }, }, - dataStreamCallback + timeSeriesCallback ); - dataStreamCallback.mockClear(); + timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); - expect(dataStreamCallback).not.toBeCalled(); + expect(timeSeriesCallback).not.toBeCalled(); }); it('does request a data stream which has no error associated with it', () => { @@ -604,23 +671,32 @@ describe('error handling', () => { const dataModule = new IotAppKitDataModule({ initialDataCache: CACHE_WITHOUT_ERROR }); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); dataModule.registerDataSource(customSource); + const START = new Date(2000, 0, 0); + const END = new Date(); + dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], request: { - viewport: { start: new Date(2000, 0, 0), end: new Date() }, + viewport: { start: START, end: END }, settings: { fetchFromStartToEnd: true }, }, }, - dataStreamCallback + timeSeriesCallback ); - expect(dataStreamCallback).toBeCalledTimes(1); - expect(dataStreamCallback).toBeCalledWith([expect.objectContaining({ error: undefined })]); + expect(timeSeriesCallback).toBeCalledTimes(1); + expect(timeSeriesCallback).toBeCalledWith({ + dataStreams: [expect.objectContaining({ error: undefined })], + viewport: { + start: START, + end: END, + }, + }); }); }); @@ -636,13 +712,13 @@ describe('caching', () => { const START_2 = new Date(2001, 1, 0); const END_2 = new Date(2001, 2, 0); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); const { update } = dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], request: { viewport: { start: START_1, end: END_1 }, settings: { fetchFromStartToEnd: true } }, }, - dataStreamCallback + timeSeriesCallback ); update({ request: { viewport: { start: START_2, end: END_2 }, settings: { fetchFromStartToEnd: true } } }); @@ -668,13 +744,13 @@ describe('caching', () => { const START_2 = new Date(START_1.getTime() - MONTH_IN_MS); const END_2 = new Date(END_1.getTime() + MONTH_IN_MS); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); const { update } = dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], request: { viewport: { start: START_1, end: END_1 }, settings: { fetchFromStartToEnd: true } }, }, - dataStreamCallback + timeSeriesCallback ); (dataSource.initiateRequest as Mock).mockClear(); @@ -709,13 +785,13 @@ describe('caching', () => { const START_2 = new Date(1991, 0, 0); const END_2 = new Date(1999, 1, 0); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); const { update } = dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], request: { viewport: { start: START_1, end: END_1 }, settings: { fetchFromStartToEnd: true } }, }, - dataStreamCallback + timeSeriesCallback ); (dataSource.initiateRequest as Mock).mockClear(); @@ -742,7 +818,7 @@ describe('caching', () => { const END = new Date(); const START = new Date(END.getTime() - HOUR_IN_MS); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], @@ -751,7 +827,7 @@ describe('caching', () => { settings: { fetchFromStartToEnd: true, refreshRate: MINUTE_IN_MS }, }, }, - dataStreamCallback + timeSeriesCallback ); (dataSource.initiateRequest as Mock).mockClear(); @@ -784,13 +860,13 @@ describe('caching', () => { const END = new Date(); const START = new Date(END.getTime() - HOUR_IN_MS); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], request: { viewport: { start: START, end: END }, settings: { refreshRate: MINUTE_IN_MS } }, }, - dataStreamCallback + timeSeriesCallback ); (dataSource.initiateRequest as Mock).mockClear(); @@ -823,7 +899,7 @@ it('overrides module-level cache TTL if query-level cache TTL is provided', asyn const END = new Date(); const START = new Date(END.getTime() - HOUR_IN_MS); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); dataModule.subscribeToDataStreams( { queries: [ @@ -839,7 +915,7 @@ it('overrides module-level cache TTL if query-level cache TTL is provided', asyn ], request: { viewport: { start: START, end: END }, settings: { refreshRate: MINUTE_IN_MS } }, }, - dataStreamCallback + timeSeriesCallback ); (dataSource.initiateRequest as Mock).mockClear(); @@ -862,7 +938,7 @@ describe('request scheduler', () => { const dataSource = createMockSiteWiseDataSource([DATA_STREAM]); dataModule.registerDataSource(dataSource); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); const { unsubscribe } = dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], @@ -871,18 +947,18 @@ describe('request scheduler', () => { settings: { fetchFromStartToEnd: true, refreshRate: SECOND_IN_MS * 0.1 }, }, }, - dataStreamCallback + timeSeriesCallback ); - dataStreamCallback.mockClear(); + timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); - expect(dataStreamCallback).toBeCalledTimes(2); + expect(timeSeriesCallback).toBeCalledTimes(2); - dataStreamCallback.mockClear(); + timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); - expect(dataStreamCallback).toBeCalledTimes(2); + expect(timeSeriesCallback).toBeCalledTimes(2); unsubscribe(); }); @@ -900,7 +976,7 @@ describe('request scheduler', () => { const END = new Date(); const START = new Date(END.getTime() - HOUR_IN_MS); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); const { unsubscribe } = dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], @@ -909,26 +985,26 @@ describe('request scheduler', () => { settings: { refreshRate: SECOND_IN_MS * 0.1 }, }, }, - dataStreamCallback + timeSeriesCallback ); - dataStreamCallback.mockClear(); + timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); - expect(dataStreamCallback).toBeCalledTimes(2); + expect(timeSeriesCallback).toBeCalledTimes(2); - dataStreamCallback.mockClear(); + timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); - expect(dataStreamCallback).toBeCalledTimes(2); + expect(timeSeriesCallback).toBeCalledTimes(2); // advance until TTL rules no longer apply (data no longer expireable) jest.advanceTimersByTime(MINUTE_IN_MS); - dataStreamCallback.mockClear(); + timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); - expect(dataStreamCallback).toBeCalledTimes(0); + expect(timeSeriesCallback).toBeCalledTimes(0); unsubscribe(); }); @@ -938,7 +1014,7 @@ describe('request scheduler', () => { const dataSource = createMockSiteWiseDataSource([DATA_STREAM]); dataModule.registerDataSource(dataSource); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); const { unsubscribe } = dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], @@ -950,16 +1026,16 @@ describe('request scheduler', () => { }, }, }, - dataStreamCallback + timeSeriesCallback ); unsubscribe(); await flushPromises(); - dataStreamCallback.mockClear(); + timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); - expect(dataStreamCallback).not.toHaveBeenCalled(); + expect(timeSeriesCallback).not.toHaveBeenCalled(); }); it('periodically requests data after switching from static to duration based viewport', async () => { @@ -968,7 +1044,7 @@ describe('request scheduler', () => { const dataSource = createMockSiteWiseDataSource([{ ...DATA_STREAM, data: [DATA_POINT] }]); dataModule.registerDataSource(dataSource); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); const { update, unsubscribe } = dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], @@ -980,7 +1056,7 @@ describe('request scheduler', () => { }, }, }, - dataStreamCallback + timeSeriesCallback ); update({ @@ -992,14 +1068,14 @@ describe('request scheduler', () => { }, }, }); - dataStreamCallback.mockClear(); + timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); - expect(dataStreamCallback).toBeCalledTimes(2); - dataStreamCallback.mockClear(); + expect(timeSeriesCallback).toBeCalledTimes(2); + timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); - expect(dataStreamCallback).toBeCalledTimes(2); + expect(timeSeriesCallback).toBeCalledTimes(2); unsubscribe(); }); @@ -1009,7 +1085,7 @@ describe('request scheduler', () => { const dataSource = createMockSiteWiseDataSource([DATA_STREAM]); dataModule.registerDataSource(dataSource); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); const { update, unsubscribe } = dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], @@ -1021,7 +1097,7 @@ describe('request scheduler', () => { }, }, }, - dataStreamCallback + timeSeriesCallback ); // Update the request info to trigger the live mode @@ -1036,10 +1112,10 @@ describe('request scheduler', () => { }); unsubscribe(); - dataStreamCallback.mockClear(); + timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); - expect(dataStreamCallback).not.toHaveBeenCalled(); + expect(timeSeriesCallback).not.toHaveBeenCalled(); }); it('stops the request scheduler when request info gets updated with static viewport that does not intersect with any TTL intervals', async () => { @@ -1047,13 +1123,13 @@ describe('request scheduler', () => { const dataSource = createMockSiteWiseDataSource([DATA_STREAM]); dataModule.registerDataSource(dataSource); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); const { update } = dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], request: { viewport: { duration: SECOND_IN_MS }, settings: { fetchFromStartToEnd: true } }, }, - dataStreamCallback + timeSeriesCallback ); update({ @@ -1065,11 +1141,11 @@ describe('request scheduler', () => { }, }, }); - dataStreamCallback.mockClear(); + timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); - expect(dataStreamCallback).not.toBeCalled(); + expect(timeSeriesCallback).not.toBeCalled(); }); it('continues the schedule requests when request info gets updated with static viewport that intersects with TTL intervals', async () => { @@ -1077,13 +1153,13 @@ describe('request scheduler', () => { const dataSource = createMockSiteWiseDataSource([DATA_STREAM]); dataModule.registerDataSource(dataSource); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); const { update } = dataModule.subscribeToDataStreams( { queries: [DATA_STREAM_QUERY], request: { viewport: { duration: SECOND_IN_MS } }, }, - dataStreamCallback + timeSeriesCallback ); const END = new Date(); @@ -1095,11 +1171,11 @@ describe('request scheduler', () => { settings: { refreshRate: SECOND_IN_MS * 0.1 }, }, }); - dataStreamCallback.mockClear(); + timeSeriesCallback.mockClear(); jest.advanceTimersByTime(SECOND_IN_MS * 0.11); - expect(dataStreamCallback).toBeCalledTimes(2); + expect(timeSeriesCallback).toBeCalledTimes(2); }); }); @@ -1108,7 +1184,7 @@ it('when data is requested from the viewport start to end with a buffer, include const dataSource = createMockSiteWiseDataSource([DATA_STREAM]); dataModule.registerDataSource(dataSource); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); const start = new Date(2021, 5, 20, 1, 30); const end = new Date(2021, 5, 20, 1, 50); const expectedStart = new Date(2021, 5, 20, 1, 13, 45); @@ -1120,7 +1196,7 @@ it('when data is requested from the viewport start to end with a buffer, include queries: [DATA_STREAM_QUERY], request: { viewport: { start, end }, settings: { requestBuffer, fetchFromStartToEnd: true } }, }, - dataStreamCallback + timeSeriesCallback ); expect(dataSource.initiateRequest).toBeCalledWith( diff --git a/packages/core/src/data-module/IotAppKitDataModule.ts b/packages/core/src/data-module/IotAppKitDataModule.ts index d86c463db..ba17caa0c 100644 --- a/packages/core/src/data-module/IotAppKitDataModule.ts +++ b/packages/core/src/data-module/IotAppKitDataModule.ts @@ -3,13 +3,13 @@ import SubscriptionStore from './subscription-store/subscriptionStore'; import { DataModule, DataModuleSubscription, - DataStreamCallback, DataStreamQuery, RequestInformation, RequestInformationAndRange, Subscription, SubscriptionUpdate, } from './types.d'; +import { TimeSeriesData } from '../interface'; import { DataStreamsStore, CacheSettings } from './data-cache/types'; import DataSourceStore from './data-source-store/dataSourceStore'; import { SubscriptionResponse } from '../iotsitewise/time-series-data/types.d'; @@ -136,7 +136,7 @@ export class IotAppKitDataModule implements DataModule { public subscribeToDataStreams = ( { queries, request }: DataModuleSubscription, - callback: DataStreamCallback + callback: (data: TimeSeriesData) => void ): SubscriptionResponse => { const subscriptionId = v4(); diff --git a/packages/core/src/data-module/subscription-store/subscriptionStore.ts b/packages/core/src/data-module/subscription-store/subscriptionStore.ts index 21ebe80ac..bd2d6f3a8 100644 --- a/packages/core/src/data-module/subscription-store/subscriptionStore.ts +++ b/packages/core/src/data-module/subscription-store/subscriptionStore.ts @@ -69,7 +69,7 @@ export default class SubscriptionStore { // Subscribe to changes from the data cache const unsubscribe = this.dataCache.subscribe( this.dataSourceStore.getRequestsFromQueries({ queries, request }), - subscription.emit + (dataStreams) => subscription.emit({ dataStreams, viewport: subscription.request.viewport }) ); this.unsubscribeMap[subscriptionId] = () => { diff --git a/packages/core/src/data-module/types.d.ts b/packages/core/src/data-module/types.d.ts index d6dcdda2a..798f97563 100644 --- a/packages/core/src/data-module/types.d.ts +++ b/packages/core/src/data-module/types.d.ts @@ -15,6 +15,7 @@ import { import { RefId } from '../iotsitewise/time-series-data/types'; import { CacheSettings } from './data-cache/types'; import { DataPoint, StreamAssociation } from '@synchro-charts/core/dist/types/utils/dataTypes'; +import { TimeSeriesData } from '../interface'; export type RequestInformation = { id: DataStreamId; @@ -68,7 +69,7 @@ export type DataStreamCallback = (dataStreams: DataStream[]) => void; export type QuerySubscription = { queries: Query[]; request: TimeSeriesDataRequest; - emit: DataStreamCallback; + emit: (data: TimeSeriesData) => void; // Initiate requests for the subscription fulfill: () => void; }; @@ -107,7 +108,7 @@ export type DataSourceRequest = { export type SubscribeToDataStreams = ( dataModule: DataModule, { queries, requestInfo }: DataModuleSubscription, - callback: DataStreamCallback + callback: (data: TimeSeriesData) => void ) => { unsubscribe: () => void; update: (subscriptionUpdate: SubscriptionUpdate) => void; @@ -115,7 +116,7 @@ export type SubscribeToDataStreams = ( type SubscribeToDataStreamsPrivate = ( { queries, requestInfo }: DataModuleSubscription, - callback: DataStreamCallback + callback: (data: TimeSeriesData) => void ) => { unsubscribe: () => void; update: (subscriptionUpdate: SubscriptionUpdate) => void; @@ -129,14 +130,14 @@ type SubscribeToDataStreamsPrivate = ( export type SubscribeToDataStreamsFrom = ( dataModule: DataModule, source: string, - emit: DataStreamCallback + emit: (data: TimeSeriesData) => void ) => { unsubscribe: () => void; }; type SubscribeToDataStreamsFromPrivate = ( source: string, - emit: DataStreamCallback + emit: (data: TimeSeriesData) => void ) => { unsubscribe: () => void; }; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 09c7ade4b..7c073df1f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -2,3 +2,6 @@ export * from './data-module/index'; export * from './asset-modules/index'; export * from './iotAppKit'; export * from './common/tests/util'; +export * from './module-namespace'; +export * from './query-namespace'; +export * from './iotsitewise/time-series-data/provider'; diff --git a/packages/core/src/interface.d.ts b/packages/core/src/interface.d.ts index d41fca481..e6b23b636 100755 --- a/packages/core/src/interface.d.ts +++ b/packages/core/src/interface.d.ts @@ -1,4 +1,5 @@ import { + DataModule, DataModuleSubscription, DataSource, DataStreamCallback, @@ -8,6 +9,9 @@ import { import { IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise/dist-types/IoTSiteWiseClient'; import { Credentials, Provider } from '@aws-sdk/types'; import { SiteWiseDataStreamQuery } from './iotsitewise/time-series-data/types'; +import { IotAppKitDataModule } from './data-module/IotAppKitDataModule'; +import { SiteWiseAssetModule } from './asset-modules'; +import { TimeSeriesData, TimeSeriesDataRequestSettings } from './interface'; export * from './components.d'; export * from './data-module/types.d'; @@ -19,7 +23,7 @@ export * from './iotsitewise/time-series-data/types.d'; export type IoTAppKitSession = { subscribeToTimeSeriesData: ( { queries, request }: DataModuleSubscription, - callback: DataStreamCallback + callback: (data: TimeSeriesData) => void ) => { unsubscribe: () => void; update: (subscriptionUpdate: SubscriptionUpdate) => void; @@ -40,3 +44,30 @@ export type IoTAppKitInitInputs = awsCredentials: Credentials | Provider; awsRegion: string; }; + +export interface IoTAppKit { + session: (componentId: string) => IoTAppKitComponentSession; + registerTimeSeriesDataSource: (dataSource: DataSource) => void; +} + +export interface Closeable { + close(): void; +} + +export interface DataModuleSession extends Closeable {} + +export interface IoTAppKitComponentSession extends Closeable { + componentId: string; + attachDataModuleSession(session: DataModuleSession): void; +} + +export interface Query { + build(session: IoTAppKitComponentSession, params?: Params): Provider; +} + +export interface TimeSeriesQuery extends Query {} + +export interface Provider { + subscribe(callback: (data: DataType) => void): void; + unsubscribe(): void; +} diff --git a/packages/core/src/iotAppKit.ts b/packages/core/src/iotAppKit.ts index 0e4b807ec..7e21e77da 100644 --- a/packages/core/src/iotAppKit.ts +++ b/packages/core/src/iotAppKit.ts @@ -3,7 +3,7 @@ import { sitewiseSdk } from './iotsitewise/time-series-data/sitewise-sdk'; import { SiteWiseAssetDataSource } from './data-module/types'; import { createSiteWiseAssetDataSource } from './iotsitewise/time-series-data/asset-data-source'; import { SiteWiseAssetModule } from './asset-modules'; -import { IoTAppKitSession, IoTAppKitInitInputs } from './interface.d'; +import { IoTAppKitInitInputs, IoTAppKitSession } from './interface.d'; import { createDataSource } from './iotsitewise/time-series-data'; import { subscribeToTimeSeriesData } from './iotsitewise/time-series-data/coordinator'; import { subscribeToAssetTree } from './asset-modules/coordinator'; @@ -15,7 +15,7 @@ import { subscribeToAssetTree } from './asset-modules/coordinator'; * @param awsRegion - Region for AWS based data sources to point towards, i.e. us-east-1 */ export const initialize = (input: IoTAppKitInitInputs) => { - const dataModule = new IotAppKitDataModule(); + const siteWiseTimeSeriesModule = new IotAppKitDataModule(); const siteWiseSdk = 'iotSiteWiseClient' in input ? input.iotSiteWiseClient : sitewiseSdk(input.awsCredentials, input.awsRegion); @@ -25,16 +25,16 @@ export const initialize = (input: IoTAppKitInitInputs) => { if (input.registerDataSources !== false) { /** Automatically registered data sources */ - dataModule.registerDataSource(createDataSource(siteWiseSdk)); + siteWiseTimeSeriesModule.registerDataSource(createDataSource(siteWiseSdk)); } return { session: (): IoTAppKitSession => ({ - subscribeToTimeSeriesData: subscribeToTimeSeriesData(dataModule, siteWiseAssetModuleSession), + subscribeToTimeSeriesData: subscribeToTimeSeriesData(siteWiseTimeSeriesModule, siteWiseAssetModuleSession), iotsitewise: { subscribeToAssetTree: subscribeToAssetTree(siteWiseAssetModuleSession), }, - registerDataSource: dataModule.registerDataSource, + registerDataSource: siteWiseTimeSeriesModule.registerDataSource, }), }; }; diff --git a/packages/core/src/iotsitewise/time-series-data/coordinator.ts b/packages/core/src/iotsitewise/time-series-data/coordinator.ts index 3799e8eb6..cbbad56df 100644 --- a/packages/core/src/iotsitewise/time-series-data/coordinator.ts +++ b/packages/core/src/iotsitewise/time-series-data/coordinator.ts @@ -2,26 +2,34 @@ import { DataModule, DataModuleSubscription, DataStream, - DataStreamCallback, SiteWiseAssetSession, SiteWiseDataStreamQuery, SubscriptionUpdate, } from '../../interface'; import { DescribeAssetModelResponse } from '@aws-sdk/client-iotsitewise'; import { completeDataStreams } from '../../completeDataStreams'; +import { TimeSeriesData } from './types'; +import { MinimalViewPortConfig } from '@synchro-charts/core'; export const subscribeToTimeSeriesData = (dataModule: DataModule, assetModuleSession: SiteWiseAssetSession) => - ({ queries, request }: DataModuleSubscription, callback: DataStreamCallback) => { + ({ queries, request }: DataModuleSubscription, callback: (data: TimeSeriesData) => void) => { let dataStreams: DataStream[] = []; + + let viewport: MinimalViewPortConfig; + const assetModels: Record = {}; const emit = () => { - callback(completeDataStreams({ dataStreams, assetModels })); + callback({ + dataStreams: completeDataStreams({ dataStreams, assetModels }), + viewport, + }); }; - const { update, unsubscribe } = dataModule.subscribeToDataStreams({ queries, request }, (updatedDataStreams) => { - dataStreams = updatedDataStreams; + const { update, unsubscribe } = dataModule.subscribeToDataStreams({ queries, request }, (data) => { + dataStreams = data.dataStreams; + viewport = data.viewport; emit(); }); diff --git a/packages/core/src/iotsitewise/time-series-data/data-source.spec.ts b/packages/core/src/iotsitewise/time-series-data/data-source.spec.ts index 3459d5167..3537d6bb9 100644 --- a/packages/core/src/iotsitewise/time-series-data/data-source.spec.ts +++ b/packages/core/src/iotsitewise/time-series-data/data-source.spec.ts @@ -346,7 +346,7 @@ describe('e2e through data-module', () => { dataModule.registerDataSource(dataSource); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); const assetId = 'asset-id'; const propertyId = 'property-id'; @@ -360,20 +360,24 @@ describe('e2e through data-module', () => { ], request: HISTORICAL_REQUEST, }, - dataStreamCallback + timeSeriesCallback ); await flushPromises(); - expect(dataStreamCallback).toBeCalledTimes(2); - expect(dataStreamCallback).toHaveBeenLastCalledWith([ + expect(timeSeriesCallback).toBeCalledTimes(2); + expect(timeSeriesCallback).toHaveBeenLastCalledWith( expect.objectContaining({ - id: toDataStreamId({ assetId, propertyId }), - error: ERR_MESSAGE, - isLoading: false, - isRefreshing: false, - }), - ]); + dataStreams: [ + expect.objectContaining({ + id: toDataStreamId({ assetId, propertyId }), + error: ERR_MESSAGE, + isLoading: false, + isRefreshing: false, + }), + ], + }) + ); }); }); @@ -389,7 +393,7 @@ describe('e2e through data-module', () => { dataModule.registerDataSource(dataSource); - const dataStreamCallback = jest.fn(); + const timeSeriesCallback = jest.fn(); const assetId = 'asset-id'; const propertyId = 'property-id'; @@ -406,20 +410,24 @@ describe('e2e through data-module', () => { settings: { fetchMostRecentBeforeEnd: true }, }, }, - dataStreamCallback + timeSeriesCallback ); await flushPromises(); - expect(dataStreamCallback).toBeCalledTimes(2); - expect(dataStreamCallback).toHaveBeenLastCalledWith([ + expect(timeSeriesCallback).toBeCalledTimes(2); + expect(timeSeriesCallback).toHaveBeenLastCalledWith( expect.objectContaining({ - id: toDataStreamId({ assetId, propertyId }), - error: ERR_MESSAGE, - isLoading: false, - isRefreshing: false, - }), - ]); + dataStreams: [ + expect.objectContaining({ + id: toDataStreamId({ assetId, propertyId }), + error: ERR_MESSAGE, + isLoading: false, + isRefreshing: false, + }), + ], + }) + ); }); }); }); diff --git a/packages/core/src/iotsitewise/time-series-data/provider.spec.ts b/packages/core/src/iotsitewise/time-series-data/provider.spec.ts new file mode 100644 index 000000000..c7460f4f8 --- /dev/null +++ b/packages/core/src/iotsitewise/time-series-data/provider.spec.ts @@ -0,0 +1,112 @@ +import { SiteWiseTimeSeriesDataProvider } from './provider'; +import { createMockSiteWiseSDK, SiteWiseAssetModule } from '../..'; +import { IotAppKitDataModule } from '../../data-module/IotAppKitDataModule'; +import { createSiteWiseAssetDataSource } from './asset-data-source'; +import { DESCRIBE_ASSET_RESPONSE } from '../../testing/__mocks__/assetSummary'; +import { AppKitComponentSession } from '../../app-kit-component-session'; +import { DATA_STREAM } from '../../testing/__mocks__/mockWidgetProperties'; +import { SiteWiseDataStreamQuery } from './types'; +import { DataSource, DataStream } from '../../interface'; +import { MINUTE_IN_MS } from '../../common/time'; + +const createMockSource = (dataStreams: DataStream[]): DataSource => ({ + name: 'site-wise', + initiateRequest: jest.fn(({ onSuccess }: any) => onSuccess(dataStreams)), + getRequestsFromQuery: () => dataStreams.map((dataStream) => ({ id: dataStream.id, resolution: 0 })), +}); + +const timeSeriesModule = new IotAppKitDataModule(); +const dataSource = createMockSource([DATA_STREAM]); +timeSeriesModule.registerDataSource(dataSource); + +const assetModule = new SiteWiseAssetModule( + createSiteWiseAssetDataSource( + createMockSiteWiseSDK({ + describeAsset: () => Promise.resolve(DESCRIBE_ASSET_RESPONSE), + }) + ) +); + +const componentSession = new AppKitComponentSession({ + componentId: 'componentId', + siteWiseAssetModule: assetModule, + siteWiseTimeSeriesModule: timeSeriesModule, +}); + +beforeAll(() => { + jest.useFakeTimers('modern'); +}); + +afterAll(() => { + jest.useRealTimers(); +}); + +it('subscribes, updates, and unsubscribes to time series data by delegating to underlying data modules', () => { + const START_1 = new Date(2020, 0, 0); + const END_1 = new Date(); + + const refreshRate = MINUTE_IN_MS; + + const provider = new SiteWiseTimeSeriesDataProvider(componentSession, { + queries: [{ source: 'site-wise', assets: [] }], + request: { + viewport: { start: START_1, end: END_1 }, + settings: { fetchFromStartToEnd: true, refreshRate }, + }, + }); + + const timeSeriesCallback = jest.fn(); + + // subscribe + provider.subscribe(timeSeriesCallback); + + expect(timeSeriesCallback).toBeCalledWith({ + dataStreams: [ + expect.objectContaining({ + id: DATA_STREAM.id, + }), + ], + viewport: { + start: START_1, + end: END_1, + }, + }); + + const START_2 = new Date(2019, 0, 0); + const END_2 = new Date(); + + timeSeriesCallback.mockClear(); + + // update + provider.updateSubscription({ + request: { viewport: { start: START_2, end: END_2 } }, + }); + + expect(timeSeriesCallback).toBeCalledWith({ + dataStreams: [ + expect.objectContaining({ + id: DATA_STREAM.id, + }), + ], + viewport: { + start: START_2, + end: END_2, + }, + }); + + timeSeriesCallback.mockClear(); + + // check that subscription refreshes + jest.advanceTimersByTime(refreshRate); + + expect(timeSeriesCallback).toHaveBeenCalled(); + + timeSeriesCallback.mockClear(); + + // unsubscribe + provider.unsubscribe(); + + jest.advanceTimersByTime(refreshRate); + + expect(timeSeriesCallback).not.toHaveBeenCalled(); +}); diff --git a/packages/core/src/iotsitewise/time-series-data/provider.ts b/packages/core/src/iotsitewise/time-series-data/provider.ts new file mode 100644 index 000000000..4b9810a66 --- /dev/null +++ b/packages/core/src/iotsitewise/time-series-data/provider.ts @@ -0,0 +1,46 @@ +import { MinimalViewPortConfig } from '@synchro-charts/core'; +import { Provider, IoTAppKitComponentSession } from '../../interface'; +import { AnyDataStreamQuery, DataModuleSubscription, SubscriptionUpdate } from '../../data-module/types'; +import { datamodule } from '../..'; +import { subscribeToTimeSeriesData } from './coordinator'; +import { TimeSeriesData } from './types'; + +/** + * Provider for SiteWise time series data + */ +export class SiteWiseTimeSeriesDataProvider implements Provider { + private session: IoTAppKitComponentSession; + + private input: DataModuleSubscription; + + private update: (subscriptionUpdate: SubscriptionUpdate) => void; + + constructor(session: IoTAppKitComponentSession, input: DataModuleSubscription) { + this.session = session; + this.input = input; + } + + subscribe(callback: (data: TimeSeriesData) => void) { + const { session } = this; + + const { update, unsubscribe } = subscribeToTimeSeriesData( + datamodule.iotsitewise.timeSeriesDataSession(session), + datamodule.iotsitewise.assetDataSession(session) + )(this.input, callback); + + this.update = update; + + /** @todo move into datamodule namespace when sessions are supported on time series module */ + this.session.attachDataModuleSession({ + close: unsubscribe, + }); + } + + updateSubscription(subscriptionUpdate: SubscriptionUpdate) { + this.update(subscriptionUpdate); + } + + unsubscribe() { + this.session.close(); + } +} diff --git a/packages/core/src/iotsitewise/time-series-data/types.d.ts b/packages/core/src/iotsitewise/time-series-data/types.d.ts index f19b7c882..b15ec73f8 100644 --- a/packages/core/src/iotsitewise/time-series-data/types.d.ts +++ b/packages/core/src/iotsitewise/time-series-data/types.d.ts @@ -1,5 +1,7 @@ +import { MinimalViewPortConfig } from '@synchro-charts/core'; import { CacheSettings } from '../../data-module/data-cache/types'; import { DataStreamQuery, SubscriptionUpdate } from '../../data-module/types'; +import { DataStream } from '../../interface'; /** * Learn more about AWS IoT SiteWise assets at https://docs.aws.amazon.com/iot-sitewise/latest/userguide/industrial-asset-models.html @@ -48,3 +50,8 @@ export type SubscriptionResponse = { /** Update the subscription. This will immediately evaluate if a new query must be requested */ update: (subscriptionUpdate: SubscriptionUpdate) => void; }; + +export type TimeSeriesData = { + dataStreams: DataStream[]; + viewport: MinimalViewPortConfig; +}; diff --git a/packages/core/src/module-namespace.ts b/packages/core/src/module-namespace.ts new file mode 100644 index 000000000..f6dc04274 --- /dev/null +++ b/packages/core/src/module-namespace.ts @@ -0,0 +1,19 @@ +import { IoTAppKitComponentSession } from './interface'; +import { DataModule } from './data-module/types'; +import { SiteWiseAssetSession } from '.'; +import { AppKitComponentSession } from './app-kit-component-session'; + +/** + * Extensible datamodule namespace exposing module sessions. + */ +export namespace datamodule.iotsitewise { + export function timeSeriesDataSession(session: IoTAppKitComponentSession): DataModule { + return (session as AppKitComponentSession).siteWiseTimeSeriesModule; // casting to hide modules from public interface + } + + export function assetDataSession(session: IoTAppKitComponentSession): SiteWiseAssetSession { + const assetSession = (session as AppKitComponentSession).siteWiseAssetModule.startSession(); + session.attachDataModuleSession(assetSession); + return assetSession; + } +} diff --git a/packages/core/src/query-namespace.ts b/packages/core/src/query-namespace.ts new file mode 100644 index 000000000..aeb117e63 --- /dev/null +++ b/packages/core/src/query-namespace.ts @@ -0,0 +1,24 @@ +import { IoTAppKitComponentSession, TimeSeriesDataRequestSettings, TimeSeriesQuery } from './interface'; +import { AnyDataStreamQuery, DataModuleSubscription } from './data-module/types'; +import { SiteWiseTimeSeriesDataProvider } from './iotsitewise/time-series-data/provider'; + +/** + * Extensible query namespace exposing methods that return Query implementations + */ +export namespace query.iotsitewise { + export const timeSeriesData = ( + input: DataModuleSubscription + ): TimeSeriesQuery => ({ + build: (session: IoTAppKitComponentSession, params?: TimeSeriesDataRequestSettings) => + new SiteWiseTimeSeriesDataProvider(session, { + ...input, + request: { + ...input.request, + settings: { + ...(params || {}), + ...input.request.settings, + }, + }, + }), + }); +}