Skip to content

Commit

Permalink
feat: Add support for update within subscribeToTimeSeriesData (#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
diehbria committed Feb 10, 2022
1 parent 557484f commit 83b100f
Show file tree
Hide file tree
Showing 6 changed files with 309 additions and 21 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/iotAppKit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { createSiteWiseAssetDataSource } from './iotsitewise/time-series-data/as
import { SiteWiseAssetModule } from './asset-modules';
import { IoTAppKitInitInputs, IoTAppKitSession } from './interface.d';
import { createDataSource } from './iotsitewise/time-series-data';
import { subscribeToTimeSeriesData } from './iotsitewise/time-series-data/coordinator';
import { subscribeToTimeSeriesData } from './iotsitewise/time-series-data/subscribeToTimeSeriesData';
import { subscribeToAssetTree } from './asset-modules/coordinator';

/**
Expand Down
55 changes: 54 additions & 1 deletion packages/core/src/iotsitewise/__mocks__/asset.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { AssetState, AssetSummary, DescribeAssetResponse } from "@aws-sdk/client-iotsitewise";
import {
AssetState,
AssetSummary,
DescribeAssetModelResponse,
DescribeAssetResponse
} from "@aws-sdk/client-iotsitewise";
import { ASSET_MODEL_ID } from "./assetModel";

export const ASSET_SUMMARY: AssetSummary = {
Expand Down Expand Up @@ -61,3 +66,51 @@ export const sampleAssetDescription: DescribeAssetResponse = {
assetCompositeModels: [],
assetProperties: []
};

export const createAssetResponse = ({
assetId,
assetModelId,
}: {
assetId: string;
assetModelId: string;
}): DescribeAssetResponse => ({
assetId: assetId,
assetName: `${assetId}-name`,
assetModelId,
assetCreationDate: undefined,
assetLastUpdateDate: undefined,
assetStatus: undefined,
assetHierarchies: [],
assetProperties: [],
assetArn: undefined,
});

export const createAssetModelResponse = ({
propertyId,
assetModelId,
propertyName = 'property-name',
}: {
propertyId: string;
assetModelId: string;
propertyName: string;
}): DescribeAssetModelResponse => ({
assetModelId,
assetModelName: `${assetModelId}-name`,
assetModelDescription: undefined,
assetModelProperties: [
{
id: propertyId,
dataType: 'DOUBLE',
name: propertyName,
unit: 'm/s',
type: undefined,
},
],
assetModelStatus: undefined,
assetModelCompositeModels: [],
assetModelHierarchies: [],
assetModelCreationDate: undefined,
assetModelLastUpdateDate: undefined,
assetModelArn: undefined,
});

3 changes: 1 addition & 2 deletions packages/core/src/iotsitewise/time-series-data/provider.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
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 { subscribeToTimeSeriesData } from './subscribeToTimeSeriesData';
import { TimeSeriesData } from './types';

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import { subscribeToTimeSeriesData } from './subscribeToTimeSeriesData';
import { IotAppKitDataModule } from '../../data-module/IotAppKitDataModule';
import { SiteWiseAssetDataSource } from '../../data-module/types';
import { createSiteWiseAssetDataSource } from './asset-data-source';
import { createMockSiteWiseSDK } from '../__mocks__/iotsitewiseSDK';
import { SiteWiseAssetModule } from '../../asset-modules';
import { IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise';
import flushPromises from 'flush-promises';
import { createDataSource } from './data-source';
import { createAssetModelResponse, createAssetResponse } from '../__mocks__/asset';
import { toDataStreamId } from './util/dataStreamId';
import { ASSET_PROPERTY_VALUE_HISTORY } from '../__mocks__/assetPropertyValue';

const initializeSubscribeToTimeSeriesData = (client: IoTSiteWiseClient) => {
const assetDataSource: SiteWiseAssetDataSource = createSiteWiseAssetDataSource(client);
const siteWiseAssetModule = new SiteWiseAssetModule(assetDataSource);
const siteWiseAssetModuleSession = siteWiseAssetModule.startSession();
const dataModule = new IotAppKitDataModule();
dataModule.registerDataSource(createDataSource(client));

return subscribeToTimeSeriesData(dataModule, siteWiseAssetModuleSession);
};

it('does not emit any data streams when empty query is subscribed to', async () => {
const subscribe = initializeSubscribeToTimeSeriesData(createMockSiteWiseSDK());
const cb = jest.fn();
subscribe({ queries: [], request: { viewport: { duration: '5m' } } }, cb);

await flushPromises();

expect(cb).not.toBeCalled();
});

it('unsubscribes', () => {
const assetDataSource: SiteWiseAssetDataSource = createSiteWiseAssetDataSource(createMockSiteWiseSDK());
const siteWiseAssetModule = new SiteWiseAssetModule(assetDataSource);
const siteWiseAssetModuleSession = siteWiseAssetModule.startSession();
const dataModule = new IotAppKitDataModule();

const unsubscribeSpy = jest.fn();
jest.spyOn(dataModule, 'subscribeToDataStreams').mockImplementation(() => ({
unsubscribe: unsubscribeSpy,
update: () => {},
}));

const subscribe = subscribeToTimeSeriesData(dataModule, siteWiseAssetModuleSession);
const { unsubscribe } = subscribe({ queries: [], request: { viewport: { duration: '5m' } } }, () => {});
unsubscribe();

expect(unsubscribeSpy).toBeCalled();
});

it('provides time series data from iotsitewise', async () => {
const ASSET_ID = 'some-asset-id';
const PROPERTY_ID = 'some-property-id';
const ASSET_MODEL_ID = 'some-asset-model-id';
const PROPERTY_NAME = 'some-property-name';

const getAssetPropertyValueHistory = jest.fn().mockResolvedValue(ASSET_PROPERTY_VALUE_HISTORY);
const describeAsset = jest
.fn()
.mockImplementation(({ assetId }) =>
Promise.resolve(createAssetResponse({ assetId: assetId as string, assetModelId: ASSET_MODEL_ID }))
);
const describeAssetModel = jest.fn().mockImplementation(({ assetModelId }) =>
Promise.resolve(
createAssetModelResponse({
assetModelId: assetModelId as string,
propertyId: PROPERTY_ID,
propertyName: PROPERTY_NAME,
})
)
);

const subscribe = initializeSubscribeToTimeSeriesData(
createMockSiteWiseSDK({
describeAsset,
describeAssetModel,
getAssetPropertyValueHistory,
})
);

const cb = jest.fn();
subscribe(
{
queries: [
{
source: 'site-wise',
assets: [
{
assetId: ASSET_ID,
properties: [{ propertyId: PROPERTY_ID, resolution: '0' }],
},
],
},
],
request: { viewport: { duration: '5m' } },
},
cb
);

await flushPromises();

// fetches the asset summary
expect(describeAsset).toBeCalledTimes(1);
expect(describeAsset).toBeCalledWith({ assetId: ASSET_ID });

// fetches the asset model
expect(describeAssetModel).toBeCalledTimes(1);
expect(describeAssetModel).toBeCalledWith({ assetModelId: ASSET_MODEL_ID });

// fetches historical data
expect(getAssetPropertyValueHistory).toBeCalledTimes(1);
expect(getAssetPropertyValueHistory).toBeCalledWith(
expect.objectContaining({
assetId: ASSET_ID,
propertyId: PROPERTY_ID,
})
);

// provides the time series data
expect(cb).toHaveBeenLastCalledWith(
expect.objectContaining({
dataStreams: [
expect.objectContaining({
id: toDataStreamId({ assetId: ASSET_ID, propertyId: PROPERTY_ID }),
name: PROPERTY_NAME,
data: [
{ x: 1000099, y: 10.123 },
{ x: 2000000, y: 12.01 },
],
dataType: 'NUMBER',
unit: 'm/s',
}),
],
})
);
});

it('provides timeseries data from iotsitewise when subscription is updated', async () => {
const ASSET_ID = 'some-asset-id';
const PROPERTY_ID = 'some-property-id';
const ASSET_MODEL_ID = 'some-asset-model-id';
const PROPERTY_NAME = 'some-property-name';

const getAssetPropertyValueHistory = jest.fn().mockResolvedValue(ASSET_PROPERTY_VALUE_HISTORY);
const describeAsset = jest
.fn()
.mockImplementation(({ assetId }) =>
Promise.resolve(createAssetResponse({ assetId: assetId as string, assetModelId: ASSET_MODEL_ID }))
);
const describeAssetModel = jest.fn().mockImplementation(({ assetModelId }) =>
Promise.resolve(
createAssetModelResponse({
assetModelId: assetModelId as string,
propertyId: PROPERTY_ID,
propertyName: PROPERTY_NAME,
})
)
);

const subscribe = initializeSubscribeToTimeSeriesData(
createMockSiteWiseSDK({
describeAsset,
describeAssetModel,
getAssetPropertyValueHistory,
})
);

const cb = jest.fn();
const { update } = subscribe(
{
queries: [],
request: { viewport: { duration: '5m' } },
},
cb
);

await flushPromises();

update({
queries: [
{
source: 'site-wise',
assets: [
{
assetId: ASSET_ID,
properties: [{ propertyId: PROPERTY_ID, resolution: '0' }],
},
],
},
],
});

await flushPromises();

// fetches the asset summary
expect(describeAsset).toBeCalledTimes(1);
expect(describeAsset).toBeCalledWith({ assetId: ASSET_ID });

// fetches the asset model
expect(describeAssetModel).toBeCalledTimes(1);
expect(describeAssetModel).toBeCalledWith({ assetModelId: ASSET_MODEL_ID });

// fetches historical data
expect(getAssetPropertyValueHistory).toBeCalledTimes(1);
expect(getAssetPropertyValueHistory).toBeCalledWith(
expect.objectContaining({
assetId: ASSET_ID,
propertyId: PROPERTY_ID,
})
);

// provides the time series data
expect(cb).toHaveBeenLastCalledWith(
expect.objectContaining({
dataStreams: [
expect.objectContaining({
id: toDataStreamId({ assetId: ASSET_ID, propertyId: PROPERTY_ID }),
name: PROPERTY_NAME,
data: [
{ x: 1000099, y: 10.123 },
{ x: 2000000, y: 12.01 },
],
dataType: 'NUMBER',
unit: 'm/s',
}),
],
})
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,36 @@ export const subscribeToTimeSeriesData =
emit();
});

queries.forEach((query) => {
query.assets.forEach((asset) => {
assetModuleSession
.fetchAssetSummary({ assetId: asset.assetId })
.then((assetSummary) => {
if (assetSummary && assetSummary.assetModelId != null) {
return assetModuleSession.fetchAssetModel({ assetModelId: assetSummary.assetModelId });
}
})
.then((assetModelResponse) => {
if (assetModelResponse) {
assetModels[asset.assetId] = assetModelResponse;
emit();
}
const fetchResources = ({ queries }: { queries?: SiteWiseDataStreamQuery[] }) => {
if (queries) {
queries.forEach((query) => {
query.assets.forEach((asset) => {
assetModuleSession
.fetchAssetSummary({ assetId: asset.assetId })
.then((assetSummary) => {
if (assetSummary && assetSummary.assetModelId != null) {
return assetModuleSession.fetchAssetModel({ assetModelId: assetSummary.assetModelId });
}
})
.then((assetModelResponse) => {
if (assetModelResponse) {
assetModels[asset.assetId] = assetModelResponse;
emit();
}
});
});
});
});
});
}
};
fetchResources({ queries });

return {
unsubscribe: () => {
unsubscribe();
},
update: (subscriptionUpdate: SubscriptionUpdate<SiteWiseDataStreamQuery>) => {
update(subscriptionUpdate);
fetchResources(subscriptionUpdate);
},
};
};
1 change: 0 additions & 1 deletion packages/core/src/testing/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './wait';
export * from '../asset-modules/mocks';

0 comments on commit 83b100f

Please sign in to comment.