Skip to content

Commit

Permalink
feat: batch API for historical, aggregated, and latest value data (#137)
Browse files Browse the repository at this point in the history
  • Loading branch information
boweihan authored Jul 12, 2022
1 parent a1191ae commit 8523409
Show file tree
Hide file tree
Showing 39 changed files with 2,798 additions and 603 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ __diff_output__
# Cypress screenshots
**/cypress/screenshots
**/cypress/videos
**/cypress/snapshots/All Specs

# Local development hard-coded credentials for use with the AWS SDK.
creds.json
Expand Down
1 change: 1 addition & 0 deletions .stylelintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ dist/
www/
loader/
node_modules/
coverage/
28 changes: 28 additions & 0 deletions docs/AWSIoTSiteWiseSource.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ You can download the AWS IoT SiteWise source from the following location: https:

To set up the AWS IoT SiteWise source, follow the instructions in [Getting started with IoT Application Kit](https://github.com/awslabs/iot-app-kit/tree/main/docs/GettingStarted.md).

---

## Queries

The AWS IoT SiteWise source provides queries that you can use to filter AWS IoT SiteWise data and assets.
Expand All @@ -34,6 +36,8 @@ query.timeSeriesData({

This query for time series data, can then be provided to any of the IoT App Kit components that support time series data.

---

## API

### `timeSeriesData`
Expand Down Expand Up @@ -140,6 +144,7 @@ const { query } = initialize({ iotsitewiseClient });
]}
/>
```
---

### `assetTree`

Expand Down Expand Up @@ -206,3 +211,26 @@ Type: Boolean

Type: Boolean

---

## SiteWiseDataSourceSettings

(Optional) Settings that can be provided when initializing the AWS IoT SiteWise source.

```
import { initialize } from '@iot-app-kit/source-iotsitewise';
const { IoTSiteWiseClient } = require("@aws-sdk/client-iotsitewise");
const iotsitewiseClient = new IoTSiteWiseClient({ region: "REGION" });
const { query } = initialize({ iotsitewiseClient, settings: { batchDuration: 100 } });
```

`batchDuration`

(Optional) Timeframe over which to coalesce time-series data requests before executing a batch request, specified in ms. e.g. a `batchDuration` of 100 will cause the AWS IoT SiteWise source to repeatedly batch all requests that occur within a 100 ms timeframe.

Type: Number

The AWS IoT SiteWise source communicates with SiteWise using batch APIs to reduce network overhead. By default, all individual requests for time-series data that occur within a single frame of execution are coalesced and executed in a batch request. This behaviour is scheduled using the [Job and JobQueue](https://262.ecma-international.org/6.0/#sec-jobs-and-job-queues) concepts. Depending on dashboard configuration, widget configuration, latency, and a multitude of other factors, batching on a single frame of execution might not be desirable.
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { newSpecPage } from '@stencil/core/testing';
import { MinimalLiveViewport } from '@synchro-charts/core';
import flushPromises from 'flush-promises';
import { initialize, createMockSiteWiseSDK } from '@iot-app-kit/source-iotsitewise';
import {
initialize,
createMockSiteWiseSDK,
BATCH_ASSET_PROPERTY_VALUE_HISTORY,
BATCH_ASSET_PROPERTY_DOUBLE_VALUE,
} from '@iot-app-kit/source-iotsitewise';
import { IotTimeSeriesConnector } from './iot-time-series-connector';
import { update } from '../../testing/update';
import { CustomHTMLElement } from '../../testing/types';
Expand Down Expand Up @@ -149,6 +154,8 @@ it('populates the name, unit, and data type from the asset model information fro
Promise.resolve(createAssetResponse({ assetId: assetId as string, assetModelId })),
describeAssetModel: ({ assetModelId }) =>
Promise.resolve(createAssetModelResponse({ assetModelId: assetModelId as string, propertyId: propertyId_1 })),
batchGetAssetPropertyValueHistory: jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_VALUE_HISTORY),
batchGetAssetPropertyValue: jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_DOUBLE_VALUE),
}),
});

Expand Down Expand Up @@ -188,6 +195,8 @@ it('populates the name, unit, and data type from the asset model information fro
Promise.resolve(createAssetResponse({ assetId: assetId as string, assetModelId })),
describeAssetModel: ({ assetModelId }) =>
Promise.resolve(createAssetModelResponse({ assetModelId: assetModelId as string, propertyId: propertyId_1 })),
batchGetAssetPropertyValueHistory: jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_VALUE_HISTORY),
batchGetAssetPropertyValue: jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_DOUBLE_VALUE),
}),
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { renderChart } from '../../testing/renderChart';
import { mockGetAggregatedOrRawResponse } from '../../testing/mocks/mockGetAggregatedOrRawResponse';
import { mockBatchGetAggregatedOrRawResponse } from '../../testing/mocks/mockGetAggregatedOrRawResponse';
import { mockGetAssetSummary } from '../../testing/mocks/mockGetAssetSummaries';
import { ScaleConfig, ScaleType } from '@synchro-charts/core';
import { mockGetAssetModelSummary } from '../../testing/mocks/mockGetAssetModelSummary';
Expand All @@ -14,13 +14,17 @@ describe('bar chart', () => {
const assetId = 'some-asset-id';
const assetModelId = 'some-asset-model-id';

before(() => {
cy.intercept('/properties/aggregates?*', (req) => {
beforeEach(() => {
cy.intercept('/properties/batch/aggregates', (req) => {
const { startDate, endDate, resolution } = req.body.entries[0];
const startDateInMs = startDate * SECOND_IN_MS;
const endDateInMs = endDate * SECOND_IN_MS;

req.reply(
mockGetAggregatedOrRawResponse({
startDate: new Date(req.query.startDate),
endDate: new Date(req.query.endDate),
resolution: req.query.resolution as string,
mockBatchGetAggregatedOrRawResponse({
startDate: new Date(startDateInMs),
endDate: new Date(endDateInMs),
resolution,
})
);
}).as('getAggregates');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { renderChart, testChartContainerClassNameSelector } from '../../testing/renderChart';
import { mockGetAggregatedOrRawResponse } from '../../testing/mocks/mockGetAggregatedOrRawResponse';
import { mockBatchGetAggregatedOrRawResponse } from '../../testing/mocks/mockGetAggregatedOrRawResponse';
import { mockGetAssetSummary } from '../../testing/mocks/mockGetAssetSummaries';
import { mockGetAssetModelSummary } from '../../testing/mocks/mockGetAssetModelSummary';

Expand All @@ -14,39 +14,48 @@ describe('handles gestures', () => {
const assetModelId = 'some-asset-model-id';

before(() => {
cy.intercept('/properties/history?*', (req) => {
if (new Date(req.query.startDate).getUTCFullYear() === 1899) {
cy.intercept('/properties/batch/history', (req) => {
const { startDate, endDate } = req.body.entries[0];
const startDateInMs = startDate * SECOND_IN_MS;
const endDateInMs = endDate * SECOND_IN_MS;

if (new Date(startDateInMs).getUTCFullYear() === 1899) {
req.reply(
mockGetAggregatedOrRawResponse({
startDate: new Date(new Date(req.query.endDate).getTime() - SECOND_IN_MS),
endDate: new Date(req.query.endDate),
mockBatchGetAggregatedOrRawResponse({
startDate: new Date(new Date(endDateInMs).getTime() - SECOND_IN_MS),
endDate: new Date(endDateInMs),
})
);
} else {
req.reply(
mockGetAggregatedOrRawResponse({
startDate: new Date(req.query.startDate),
endDate: new Date(req.query.endDate),
mockBatchGetAggregatedOrRawResponse({
startDate: new Date(startDateInMs),
endDate: new Date(endDateInMs),
entryId: '1-0',
})
);
}
});

cy.intercept('/properties/aggregates?*', (req) => {
if (new Date(req.query.startDate).getUTCFullYear() === 1899) {
cy.intercept('/properties/batch/aggregates', (req) => {
const { startDate, endDate, resolution } = req.body.entries[0];
const startDateInMs = startDate * SECOND_IN_MS;
const endDateInMs = endDate * SECOND_IN_MS;

if (new Date(startDateInMs).getUTCFullYear() === 1899) {
req.reply(
mockGetAggregatedOrRawResponse({
startDate: new Date(new Date(req.query.endDate).getTime() - 60 * SECOND_IN_MS),
endDate: new Date(req.query.endDate),
resolution: req.query.resolution as string,
mockBatchGetAggregatedOrRawResponse({
startDate: new Date(new Date(endDateInMs).getTime() - 60 * SECOND_IN_MS),
endDate: new Date(endDateInMs),
resolution,
})
);
} else {
req.reply(
mockGetAggregatedOrRawResponse({
startDate: new Date(req.query.startDate),
endDate: new Date(req.query.endDate),
resolution: req.query.resolution as string,
mockBatchGetAggregatedOrRawResponse({
startDate: new Date(startDateInMs),
endDate: new Date(endDateInMs),
resolution,
})
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { renderChart } from '../../testing/renderChart';
import { mockLatestValueResponse } from '../../testing/mocks/mockGetAggregatedOrRawResponse';
import {
mockBatchLatestValueResponse,
mockBatchGetAggregatedOrRawResponse,
} from '../../testing/mocks/mockGetAggregatedOrRawResponse';
import { mockGetAssetSummary } from '../../testing/mocks/mockGetAssetSummaries';
import { mockGetAssetModelSummary } from '../../testing/mocks/mockGetAssetModelSummary';

Expand All @@ -13,9 +16,22 @@ describe('kpi', () => {
const assetId = 'some-asset-id';
const assetModelId = 'some-asset-model-id';

before(() => {
cy.intercept('/properties/latest?*', (req) => {
req.reply(mockLatestValueResponse());
beforeEach(() => {
cy.intercept('/properties/batch/history', (req) => {
const { startDate, endDate } = req.body.entries[0];
const startDateInMs = startDate * SECOND_IN_MS;
const endDateInMs = endDate * SECOND_IN_MS;

req.reply(
mockBatchGetAggregatedOrRawResponse({
startDate: new Date(startDateInMs),
endDate: new Date(endDateInMs),
})
);
}).as('getHistory');

cy.intercept('/properties/batch/latest', (req) => {
req.reply(mockBatchLatestValueResponse());
}).as('getAggregates');

cy.intercept(`/assets/${assetId}`, (req) => {
Expand All @@ -30,7 +46,7 @@ describe('kpi', () => {
it('renders', () => {
renderChart({ chartType: 'iot-kpi', settings: { resolution: '0' }, viewport: { duration: '1m' } });

cy.wait(['@getAggregates', '@getAssetSummary', '@getAssetModels']);
cy.wait(['@getAggregates', '@getAssetSummary', '@getAssetModels', '@getHistory']);

cy.matchImageSnapshot(snapshotOptions);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { renderChart } from '../../testing/renderChart';
import { mockGetAggregatedOrRawResponse } from '../../testing/mocks/mockGetAggregatedOrRawResponse';
import { mockBatchGetAggregatedOrRawResponse } from '../../testing/mocks/mockGetAggregatedOrRawResponse';
import { mockGetAssetSummary } from '../../testing/mocks/mockGetAssetSummaries';
import { ScaleConfig, ScaleType } from '@synchro-charts/core';
import { mockGetAssetModelSummary } from '../../testing/mocks/mockGetAssetModelSummary';
Expand All @@ -14,22 +14,26 @@ describe('line chart', () => {
const assetId = 'some-asset-id';
const assetModelId = 'some-asset-model-id';

before(() => {
cy.intercept('/properties/aggregates?*', (req) => {
if (new Date(req.query.startDate).getUTCFullYear() === 1899) {
beforeEach(() => {
cy.intercept('/properties/batch/aggregates', (req) => {
const { startDate, endDate, resolution } = req.body.entries[0];
const startDateInMs = startDate * SECOND_IN_MS;
const endDateInMs = endDate * SECOND_IN_MS;

if (new Date(startDateInMs).getUTCFullYear() === 1899) {
req.reply(
mockGetAggregatedOrRawResponse({
startDate: new Date(new Date(req.query.endDate).getTime() - 60 * SECOND_IN_MS),
endDate: new Date(req.query.endDate),
resolution: req.query.resolution as string,
mockBatchGetAggregatedOrRawResponse({
startDate: new Date(new Date(endDateInMs).getTime() - 60 * SECOND_IN_MS),
endDate: new Date(endDateInMs),
resolution,
})
);
} else {
req.reply(
mockGetAggregatedOrRawResponse({
startDate: new Date(req.query.startDate),
endDate: new Date(req.query.endDate),
resolution: req.query.resolution as string,
mockBatchGetAggregatedOrRawResponse({
startDate: new Date(startDateInMs),
endDate: new Date(endDateInMs),
resolution,
})
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { renderChart } from '../../testing/renderChart';
import { mockGetAggregatedOrRawResponse } from '../../testing/mocks/mockGetAggregatedOrRawResponse';
import { mockBatchGetAggregatedOrRawResponse } from '../../testing/mocks/mockGetAggregatedOrRawResponse';
import { mockGetAssetSummary } from '../../testing/mocks/mockGetAssetSummaries';
import { ScaleConfig, ScaleType } from '@synchro-charts/core';
import { mockGetAssetModelSummary } from '../../testing/mocks/mockGetAssetModelSummary';
Expand All @@ -14,13 +14,17 @@ describe('scatter chart', () => {
const assetId = 'some-asset-id';
const assetModelId = 'some-asset-model-id';

before(() => {
cy.intercept('/properties/aggregates?*', (req) => {
beforeEach(() => {
cy.intercept('/properties/batch/aggregates', (req) => {
const { startDate, endDate, resolution } = req.body.entries[0];
const startDateInMs = startDate * SECOND_IN_MS;
const endDateInMs = endDate * SECOND_IN_MS;

req.reply(
mockGetAggregatedOrRawResponse({
startDate: new Date(req.query.startDate),
endDate: new Date(req.query.endDate),
resolution: req.query.resolution as string,
mockBatchGetAggregatedOrRawResponse({
startDate: new Date(startDateInMs),
endDate: new Date(endDateInMs),
resolution,
})
);
}).as('getAggregates');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { renderChart } from '../../testing/renderChart';
import { mockLatestValueResponse } from '../../testing/mocks/mockGetAggregatedOrRawResponse';
import {
mockBatchLatestValueResponse,
mockBatchGetAggregatedOrRawResponse,
} from '../../testing/mocks/mockGetAggregatedOrRawResponse';
import { mockGetAssetSummary } from '../../testing/mocks/mockGetAssetSummaries';
import { COMPARISON_OPERATOR } from '@synchro-charts/core';
import { mockGetAssetModelSummary } from '../../testing/mocks/mockGetAssetModelSummary';
Expand All @@ -14,9 +17,22 @@ describe('status grid', () => {
const assetId = 'some-asset-id';
const assetModelId = 'some-asset-model-id';

before(() => {
cy.intercept('/properties/latest?*', (req) => {
req.reply(mockLatestValueResponse());
beforeEach(() => {
cy.intercept('/properties/batch/history', (req) => {
const { startDate, endDate } = req.body.entries[0];
const startDateInMs = startDate * SECOND_IN_MS;
const endDateInMs = endDate * SECOND_IN_MS;

req.reply(
mockBatchGetAggregatedOrRawResponse({
startDate: new Date(startDateInMs),
endDate: new Date(endDateInMs),
})
);
});

cy.intercept('/properties/batch/latest', (req) => {
req.reply(mockBatchLatestValueResponse());
}).as('getAggregates');

cy.intercept(`/assets/${assetId}`, (req) => {
Expand Down
Loading

0 comments on commit 8523409

Please sign in to comment.