From 960f53f0bfded51cdb16c38a047df9be0550adb4 Mon Sep 17 00:00:00 2001 From: "Xiao(Bill) Li" Date: Fri, 8 Jul 2022 16:46:26 -0400 Subject: [PATCH 1/3] fix: update @testing-library/react to use create root (#151) Co-authored-by: Norbert Nader --- packages/related-table/package.json | 3 +- .../src/Hooks/useTreeCollection.test.ts | 2 +- yarn.lock | 47 +++++-------------- 3 files changed, 13 insertions(+), 39 deletions(-) diff --git a/packages/related-table/package.json b/packages/related-table/package.json index 5d55d9290..a59cecf51 100644 --- a/packages/related-table/package.json +++ b/packages/related-table/package.json @@ -53,8 +53,7 @@ "@storybook/addons": "^6.2.9", "@storybook/preset-scss": "^1.0.3", "@storybook/react": "^6.2.9", - "@testing-library/react": "^12.0.0", - "@testing-library/react-hooks": "^7.0.0", + "@testing-library/react": "^13.1.1", "@types/react": ">=17.0.2", "@types/react-dom": ">=17.0.2", "@types/styled-components": "^5.1.10", diff --git a/packages/related-table/src/Hooks/useTreeCollection.test.ts b/packages/related-table/src/Hooks/useTreeCollection.test.ts index ee695784e..0e09a62a0 100644 --- a/packages/related-table/src/Hooks/useTreeCollection.test.ts +++ b/packages/related-table/src/Hooks/useTreeCollection.test.ts @@ -1,4 +1,4 @@ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, renderHook } from '@testing-library/react'; import { useTreeCollection } from './useTreeCollection'; const items = [ diff --git a/yarn.lock b/yarn.lock index 39b476c23..83cc28690 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4604,10 +4604,10 @@ lz-string "^1.4.4" pretty-format "^26.6.2" -"@testing-library/dom@^8.0.0": - version "8.13.0" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.13.0.tgz#bc00bdd64c7d8b40841e27a70211399ad3af46f5" - integrity sha512-9VHgfIatKNXQNaZTtLnalIy0jNZzY35a4S3oi08YAt9Hv1VsfZ/DfA45lM8D/UhtHBGJ4/lGwp0PZkVndRkoOQ== +"@testing-library/dom@^8.5.0": + version "8.14.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.14.0.tgz#c9830a21006d87b9ef6e1aae306cf49b0283e28e" + integrity sha512-m8FOdUo77iMTwVRCyzWcqxlEIk+GnopbrRI15a0EaLbpZSCinIVI4kSQzWhkShK83GogvEFJSsHF3Ws0z1vrqA== dependencies: "@babel/code-frame" "^7.10.4" "@babel/runtime" "^7.12.5" @@ -4618,25 +4618,14 @@ lz-string "^1.4.4" pretty-format "^27.0.2" -"@testing-library/react-hooks@^7.0.0": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-7.0.2.tgz#3388d07f562d91e7f2431a4a21b5186062ecfee0" - integrity sha512-dYxpz8u9m4q1TuzfcUApqi8iFfR6R0FaMbr2hjZJy1uC8z+bO/K4v8Gs9eogGKYQop7QsrBTFkv/BCF7MzD2Cg== - dependencies: - "@babel/runtime" "^7.12.5" - "@types/react" ">=16.9.0" - "@types/react-dom" ">=16.9.0" - "@types/react-test-renderer" ">=16.9.0" - react-error-boundary "^3.1.0" - -"@testing-library/react@^12.0.0": - version "12.1.4" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.4.tgz#09674b117e550af713db3f4ec4c0942aa8bbf2c0" - integrity sha512-jiPKOm7vyUw311Hn/HlNQ9P8/lHNtArAx0PisXyFixDDvfl8DbD6EUdbshK5eqauvBSvzZd19itqQ9j3nferJA== +"@testing-library/react@^13.1.1": + version "13.3.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-13.3.0.tgz#bf298bfbc5589326bbcc8052b211f3bb097a97c5" + integrity sha512-DB79aA426+deFgGSjnf5grczDPiL4taK3hFaa+M5q7q20Kcve9eQottOG5kZ74KEr55v0tU2CQormSSDK87zYQ== dependencies: "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^8.0.0" - "@types/react-dom" "*" + "@testing-library/dom" "^8.5.0" + "@types/react-dom" "^18.0.0" "@testing-library/user-event@^12.1.1": version "12.8.3" @@ -5496,7 +5485,7 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== -"@types/react-dom@*", "@types/react-dom@>=16.9.0", "@types/react-dom@>=17.0.2", "@types/react-dom@^17.0.2": +"@types/react-dom@>=16.9.0", "@types/react-dom@>=17.0.2", "@types/react-dom@^17.0.2", "@types/react-dom@^18.0.0": version "17.0.17" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.17.tgz#2e3743277a793a96a99f1bf87614598289da68a1" integrity sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg== @@ -5510,13 +5499,6 @@ dependencies: "@types/react" "*" -"@types/react-test-renderer@>=16.9.0": - version "17.0.1" - resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz#3120f7d1c157fba9df0118dae20cb0297ee0e06b" - integrity sha512-3Fi2O6Zzq/f3QR9dRnlnHso9bMl7weKCviFmfF6B4LS1Uat6Hkm15k0ZAQuDz+UBq6B3+g+NM6IT2nr5QgPzCw== - dependencies: - "@types/react" "*" - "@types/react@*", "@types/react@>=16.9.0", "@types/react@>=17.0.2", "@types/react@^17", "@types/react@^17.0.2": version "17.0.47" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.47.tgz#4ee71aaf4c5a9e290e03aa4d0d313c5d666b3b78" @@ -19457,13 +19439,6 @@ react-element-to-jsx-string@^14.3.4: is-plain-object "5.0.0" react-is "17.0.2" -react-error-boundary@^3.1.0: - version "3.1.4" - resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0" - integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA== - dependencies: - "@babel/runtime" "^7.12.5" - react-fast-compare@^3.0.1, react-fast-compare@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" From a1191ae60010cac1605ba7e6680fac842e240537 Mon Sep 17 00:00:00 2001 From: jmbuss <107281089+jmbuss@users.noreply.github.com> Date: Fri, 8 Jul 2022 15:13:43 -0700 Subject: [PATCH 2/3] feat(props): adapt props for synchro-charts (#133) * feat(props): adapt props for synchro-charts * add testing for new props * fix(tests): cleanup tests from devwork * Update changelog * update tests to wait for intercepts Co-authored-by: Norbert Nader --- lerna.json | 2 +- package.json | 8 +- packages/components/CHANGELOG.md | 72 +++++++++++++++ .../bar chart -- renders.snap.png | Bin 14473 -> 14646 bytes packages/components/package.json | 10 +- packages/components/src/components.d.ts | 86 +++++++++++++++++- .../iot-bar-chart/iot-bar-chart.tsx | 46 ++++++++-- .../src/components/iot-kpi/iot-kpi.tsx | 5 +- .../iot-line-chart/iot-line-chart.tsx | 45 +++++++-- .../iot-scatter-chart/iot-scatter-chart.tsx | 34 ++++++- .../iot-status-grid/iot-status-grid.tsx | 5 +- .../iot-status-timeline.tsx | 30 +++++- .../src/components/iot-table/iot-table.tsx | 14 ++- .../iot-bar-chart.spec.component.ts | 56 +++++++++++- .../iot-connector.spec.component.ts | 13 ++- .../iot-kpi/iot-kpi.spec.component.ts | 36 +++++++- .../iot-line-chart.spec.component.ts | 54 ++++++++++- .../iot-scatter-chart.spec.component.ts | 54 ++++++++++- .../iot-status-grid.spec.component.ts | 39 +++++++- .../iot-status-timeline.spec.component.ts | 39 +++++++- .../testing/mocks/mockGetAssetModelSummary.ts | 23 +++++ .../testing/mocks/mockGetAssetSummaries.ts | 25 +++-- .../components/src/testing/renderChart.tsx | 54 ++++++++++- packages/core/package.json | 4 +- packages/react-components/package.json | 4 +- packages/related-table/package.json | 2 +- packages/source-iotsitewise/CHANGELOG.md | 10 ++ packages/source-iotsitewise/package.json | 6 +- 28 files changed, 692 insertions(+), 84 deletions(-) create mode 100644 packages/components/src/testing/mocks/mockGetAssetModelSummary.ts diff --git a/lerna.json b/lerna.json index ad99ad070..f7622040c 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.4.0", + "version": "1.5.0", "packages": [ "packages/*" ], diff --git a/package.json b/package.json index 3839f33ef..a3e85e104 100755 --- a/package.json +++ b/package.json @@ -28,10 +28,10 @@ "test:unit": "lerna run test --stream --concurrency 1", "test:git": "git diff --exit-code", "pack": "lerna run pack", - "versionup:auto": "lerna version --conventional-commits --no-push --no-git-tag-version --yes", - "versionup:patch": "lerna version patch --conventional-commits --no-push --no-git-tag-version --yes", - "versionup:minor": "lerna version minor --conventional-commits --no-push --no-git-tag-version --yes", - "versionup:major": "lerna version major --conventional-commits --no-push --no-git-tag-version --yes", + "versionup:auto": "lerna version --conventional-commits --no-changelog --no-push --no-git-tag-version --yes", + "versionup:patch": "lerna version patch --conventional-commits --no-changelog --no-push --no-git-tag-version --yes", + "versionup:minor": "lerna version minor --conventional-commits --no-changelog --no-push --no-git-tag-version --yes", + "versionup:major": "lerna version major --conventional-commits --no-changelog --no-push --no-git-tag-version --yes", "publishToNpm": "lerna publish from-package --no-verify-access --yes" }, "devDependencies": { diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 4b0bc06ae..b0ef86638 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -3,6 +3,78 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# 1.5.0 (2022-07-09) + + +### Features + +* synchro-charts version updated to 5.0.0 + +* Added properties to the following components: + +IotBarChart: +* alarms +* axis +* gestures +* layout +* legend +* messageOverrides +* movement +* scale +* size +* trends + +IotKpi: +* messageOverrides + +IotLineChart: +* axis +* gestures +* layout +* legend +* messageOverrides +* movement +* scale +* size +* trends + +IotScatterChart: +* alarms +* axis +* gestures +* layout +* legend +* messageOverrides +* movement +* scale +* size +* trends + +IotStatusGrid: +* labelsConfig + +IotStatusTimeline: +* alarms +* annotations +* axis +* gestures +* layout +* messageOverrides +* movement +* scale +* size + +IotTable: +* messageOverrides +* trends + +### Fixes + +* integration tests asset model and asset summary mocks updated to return current response. + + + + # 1.4.0 (2022-06-09) diff --git a/packages/components/cypress/snapshots/integration/iot-bar-chart/iot-bar-chart.spec.component.ts/bar chart -- renders.snap.png b/packages/components/cypress/snapshots/integration/iot-bar-chart/iot-bar-chart.spec.component.ts/bar chart -- renders.snap.png index 4573852920f64743fcd2fbe10649e4c1c6389455..14f0ea9b1d0662b047938deb69384afd01982937 100644 GIT binary patch literal 14646 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[]; + "scale"?: ScaleConfig; "settings": TimeSeriesDataRequestSettings; + "size"?: MinimalSizeConfig; "styleSettings": StyleSettingsMap | undefined; + "trends": Trend[]; "viewport": Viewport; "widgetId": string; } interface IotKpi { "annotations": Annotations; "isEditing": boolean | undefined; + "messageOverrides"?: MessageOverrides; "queries": TimeQuery[]; "settings": TimeSeriesDataRequestSettings; "styleSettings": StyleSettingsMap | undefined; @@ -33,10 +44,19 @@ export namespace Components { } interface IotLineChart { "annotations": Annotations; + "axis": Axis.Options; + "gestures"?: boolean; "isEditing": boolean | undefined; + "layout"?: LayoutConfig; + "legend"?: LegendConfig; + "messageOverrides": MessageOverrides; + "movement"?: MovementConfig; "queries": TimeQuery[]; + "scale"?: ScaleConfig; "settings": TimeSeriesDataRequestSettings; + "size"?: MinimalSizeConfig; "styleSettings": StyleSettingsMap | undefined; + "trends": Trend[]; "viewport": Viewport; "widgetId": string; } @@ -58,17 +78,28 @@ export namespace Components { interface IotResourceExplorerDemo { } interface IotScatterChart { + "alarms"?: AlarmsConfig; "annotations": Annotations; + "axis"?: Axis.Options; + "gestures"?: boolean; "isEditing": boolean | undefined; + "layout"?: LayoutConfig; + "legend"?: LegendConfig; + "messageOverrides"?: MessageOverrides; + "movement"?: MovementConfig; "queries": TimeQuery[]; + "scale"?: ScaleConfig; "settings": TimeSeriesDataRequestSettings; + "size"?: MinimalSizeConfig; "styleSettings": StyleSettingsMap | undefined; + "trends": Trend[]; "viewport": Viewport; "widgetId": string; } interface IotStatusGrid { "annotations": Annotations; "isEditing": boolean | undefined; + "labelsConfig": LabelsConfig; "queries": TimeQuery[]; "settings": TimeSeriesDataRequestSettings; "styleSettings": StyleSettingsMap | undefined; @@ -76,20 +107,30 @@ export namespace Components { "widgetId": string; } interface IotStatusTimeline { - "annotations": Annotations; + "alarms"?: AlarmsConfig; + "annotations"?: Annotations; + "axis"?: Axis.Options; + "gestures"?: boolean; "isEditing": boolean | undefined; + "layout"?: LayoutConfig; + "messageOverrides"?: MessageOverrides; + "movement"?: MovementConfig; "queries": TimeQuery[]; + "scale"?: ScaleConfig; "settings": TimeSeriesDataRequestSettings; + "size"?: MinimalSizeConfig; "styleSettings": StyleSettingsMap | undefined; "viewport": Viewport; "widgetId": string; } interface IotTable { "annotations": Annotations; + "messageOverrides"?: MessageOverrides; "queries": TimeQuery[]; "settings": TimeSeriesDataRequestSettings; "styleSettings": StyleSettingsMap | undefined; "tableColumns": TableColumn[]; + "trends": Trend[]; "viewport": Viewport; "widgetId": string; } @@ -239,17 +280,28 @@ declare global { } declare namespace LocalJSX { interface IotBarChart { + "alarms"?: AlarmsConfig; "annotations"?: Annotations; + "axis"?: Axis.Options; + "gestures"?: boolean; "isEditing"?: boolean | undefined; + "layout"?: LayoutConfig; + "legend"?: LegendConfig; + "messageOverrides"?: MessageOverrides; + "movement"?: MovementConfig; "queries": TimeQuery[]; + "scale"?: ScaleConfig; "settings"?: TimeSeriesDataRequestSettings; + "size"?: MinimalSizeConfig; "styleSettings"?: StyleSettingsMap | undefined; + "trends"?: Trend[]; "viewport": Viewport; "widgetId"?: string; } interface IotKpi { "annotations"?: Annotations; "isEditing"?: boolean | undefined; + "messageOverrides"?: MessageOverrides; "queries": TimeQuery[]; "settings"?: TimeSeriesDataRequestSettings; "styleSettings"?: StyleSettingsMap | undefined; @@ -258,10 +310,19 @@ declare namespace LocalJSX { } interface IotLineChart { "annotations"?: Annotations; + "axis"?: Axis.Options; + "gestures"?: boolean; "isEditing"?: boolean | undefined; + "layout"?: LayoutConfig; + "legend"?: LegendConfig; + "messageOverrides"?: MessageOverrides; + "movement"?: MovementConfig; "queries": TimeQuery[]; + "scale"?: ScaleConfig; "settings"?: TimeSeriesDataRequestSettings; + "size"?: MinimalSizeConfig; "styleSettings"?: StyleSettingsMap | undefined; + "trends"?: Trend[]; "viewport": Viewport; "widgetId"?: string; } @@ -283,17 +344,28 @@ declare namespace LocalJSX { interface IotResourceExplorerDemo { } interface IotScatterChart { + "alarms"?: AlarmsConfig; "annotations"?: Annotations; + "axis"?: Axis.Options; + "gestures"?: boolean; "isEditing"?: boolean | undefined; + "layout"?: LayoutConfig; + "legend"?: LegendConfig; + "messageOverrides"?: MessageOverrides; + "movement"?: MovementConfig; "queries": TimeQuery[]; + "scale"?: ScaleConfig; "settings"?: TimeSeriesDataRequestSettings; + "size"?: MinimalSizeConfig; "styleSettings"?: StyleSettingsMap | undefined; + "trends"?: Trend[]; "viewport": Viewport; "widgetId"?: string; } interface IotStatusGrid { "annotations"?: Annotations; "isEditing"?: boolean | undefined; + "labelsConfig"?: LabelsConfig; "queries": TimeQuery[]; "settings"?: TimeSeriesDataRequestSettings; "styleSettings"?: StyleSettingsMap | undefined; @@ -301,20 +373,30 @@ declare namespace LocalJSX { "widgetId"?: string; } interface IotStatusTimeline { + "alarms"?: AlarmsConfig; "annotations"?: Annotations; + "axis"?: Axis.Options; + "gestures"?: boolean; "isEditing"?: boolean | undefined; + "layout"?: LayoutConfig; + "messageOverrides"?: MessageOverrides; + "movement"?: MovementConfig; "queries": TimeQuery[]; + "scale"?: ScaleConfig; "settings"?: TimeSeriesDataRequestSettings; + "size"?: MinimalSizeConfig; "styleSettings"?: StyleSettingsMap | undefined; "viewport": Viewport; "widgetId"?: string; } interface IotTable { "annotations"?: Annotations; + "messageOverrides"?: MessageOverrides; "queries": TimeQuery[]; "settings"?: TimeSeriesDataRequestSettings; "styleSettings"?: StyleSettingsMap | undefined; "tableColumns"?: TableColumn[]; + "trends"?: Trend[]; "viewport": Viewport; "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 5c7682e73..0ed113f0f 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,18 @@ import { Component, Prop, h, Listen, State, Watch } from '@stencil/core'; import { v4 as uuidv4 } from 'uuid'; -import { Annotations, DataStream as SynchroChartsDataStream } from '@synchro-charts/core'; +import { + AlarmsConfig, + Annotations, + Axis, + DataStream as SynchroChartsDataStream, + LayoutConfig, + LegendConfig, + MessageOverrides, + MinimalSizeConfig, + MovementConfig, + ScaleConfig, + Trend, +} from '@synchro-charts/core'; import { TimeSeriesDataRequestSettings, StyleSettingsMap, @@ -20,16 +32,23 @@ const DAY_IN_MS = HOUR_IN_MS * 24; shadow: false, }) export class IotBarChart { - @Prop() annotations: Annotations; - - @Prop() queries!: TimeQuery[]; - @Prop() viewport!: Viewport; + @Prop() movement?: MovementConfig; + @Prop() scale?: ScaleConfig; + @Prop() layout?: LayoutConfig; + @Prop() legend?: LegendConfig; + @Prop() size?: MinimalSizeConfig; + @Prop() widgetId: string = uuidv4(); + @Prop() queries!: TimeQuery[]; + @Prop() alarms?: AlarmsConfig; + @Prop() gestures?: boolean; + @Prop() annotations: Annotations; + @Prop() trends: Trend[]; + @Prop() axis?: Axis.Options; + @Prop() messageOverrides?: MessageOverrides; @Prop() settings: TimeSeriesDataRequestSettings = {}; - @Prop() widgetId: string = uuidv4(); - @Prop() isEditing: boolean | undefined; @Prop() styleSettings: StyleSettingsMap | undefined; @@ -84,11 +103,20 @@ export class IotBarChart { assignDefaultColors renderFunc={({ dataStreams }) => ( )} /> diff --git a/packages/components/src/components/iot-kpi/iot-kpi.tsx b/packages/components/src/components/iot-kpi/iot-kpi.tsx index 909183d29..7e16314e3 100644 --- a/packages/components/src/components/iot-kpi/iot-kpi.tsx +++ b/packages/components/src/components/iot-kpi/iot-kpi.tsx @@ -1,5 +1,5 @@ import { Component, Prop, h, State, Listen, Watch } from '@stencil/core'; -import { Annotations, DataStream as SynchroChartsDataStream } from '@synchro-charts/core'; +import { Annotations, DataStream as SynchroChartsDataStream, MessageOverrides } from '@synchro-charts/core'; import { StyleSettingsMap, TimeSeriesDataRequestSettings, @@ -33,6 +33,8 @@ export class IotKpi { @State() provider: ProviderWithViewport; + @Prop() messageOverrides?: MessageOverrides; + private defaultSettings: TimeSeriesDataRequestSettings = { resolution: '0', fetchMostRecentBeforeEnd: true, @@ -81,6 +83,7 @@ export class IotKpi { viewport={this.viewport} isEditing={this.isEditing} widgetId={this.widgetId} + messageOverrides={this.messageOverrides} /> )} /> 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 bba398f26..dee65b86d 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,5 +1,16 @@ import { Component, Prop, h, Listen, State, Watch } from '@stencil/core'; -import { Annotations, DataStream as SynchroChartsDataStream } from '@synchro-charts/core'; +import { + Annotations, + Axis, + DataStream as SynchroChartsDataStream, + LayoutConfig, + LegendConfig, + MessageOverrides, + MinimalSizeConfig, + MovementConfig, + ScaleConfig, + Trend, +} from '@synchro-charts/core'; import { StyleSettingsMap, TimeSeriesDataRequestSettings, @@ -17,18 +28,25 @@ import { v4 as uuidv4 } from 'uuid'; shadow: false, }) export class IotLineChart { + @Prop() widgetId: string = uuidv4(); + @Prop() viewport!: Viewport; + @Prop() size?: MinimalSizeConfig; + @Prop() movement?: MovementConfig; + @Prop() scale?: ScaleConfig; + @Prop() layout?: LayoutConfig; + @Prop() legend?: LegendConfig; @Prop() annotations: Annotations; + @Prop() axis: Axis.Options; + @Prop() messageOverrides: MessageOverrides; + @Prop() trends: Trend[]; - @Prop() queries!: TimeQuery[]; + @Prop() gestures?: boolean; + @Prop() isEditing: boolean | undefined; - @Prop() viewport!: Viewport; + @Prop() queries!: TimeQuery[]; @Prop() settings: TimeSeriesDataRequestSettings = {}; - @Prop() widgetId: string = uuidv4(); - - @Prop() isEditing: boolean | undefined; - @Prop() styleSettings: StyleSettingsMap | undefined; @State() provider: ProviderWithViewport; @@ -78,11 +96,20 @@ export class IotLineChart { renderFunc={({ dataStreams }) => { 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 c853162a6..1c67e71b0 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,5 +1,17 @@ import { Component, Prop, h, Listen, State, Watch } from '@stencil/core'; -import { Annotations, DataStream as SynchroChartsDataStream } from '@synchro-charts/core'; +import { + AlarmsConfig, + Annotations, + Axis, + DataStream as SynchroChartsDataStream, + LayoutConfig, + LegendConfig, + MessageOverrides, + MinimalSizeConfig, + MovementConfig, + ScaleConfig, + Trend, +} from '@synchro-charts/core'; import { StyleSettingsMap, TimeSeriesDataRequestSettings, @@ -18,6 +30,16 @@ import { v4 as uuidv4 } from 'uuid'; }) export class IotScatterChart { @Prop() annotations: Annotations; + @Prop() movement?: MovementConfig; + @Prop() scale?: ScaleConfig; + @Prop() layout?: LayoutConfig; + @Prop() legend?: LegendConfig; + @Prop() size?: MinimalSizeConfig; + @Prop() alarms?: AlarmsConfig; + @Prop() gestures?: boolean; + @Prop() trends: Trend[]; + @Prop() axis?: Axis.Options; + @Prop() messageOverrides?: MessageOverrides; @Prop() queries!: TimeQuery[]; @@ -82,6 +104,16 @@ export class IotScatterChart { viewport={this.viewport} isEditing={this.isEditing} widgetId={this.widgetId} + movement={this.movement} + scale={this.scale} + layout={this.layout} + legend={this.legend} + size={this.size} + alarms={this.alarms} + gestures={this.gestures} + trends={this.trends} + axis={this.axis} + messageOverrides={this.messageOverrides} /> ); }} 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 aa9e3b6d0..df8c989b8 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,5 +1,5 @@ import { Component, Prop, h, State, Listen, Watch } from '@stencil/core'; -import { Annotations, DataStream as SynchroChartsDataStream } from '@synchro-charts/core'; +import { Annotations, DataStream as SynchroChartsDataStream, LabelsConfig } from '@synchro-charts/core'; import { StyleSettingsMap, TimeSeriesDataRequestSettings, @@ -33,6 +33,8 @@ export class IotStatusGrid { @State() provider: ProviderWithViewport; + @Prop() labelsConfig: LabelsConfig; + private defaultSettings: TimeSeriesDataRequestSettings = { resolution: '0', fetchMostRecentBeforeEnd: true, @@ -81,6 +83,7 @@ export class IotStatusGrid { viewport={this.viewport} isEditing={this.isEditing} widgetId={this.widgetId} + labelsConfig={this.labelsConfig} /> )} /> 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 685706120..f7e5cf69c 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,5 +1,15 @@ import { Component, Prop, h, Listen, State, Watch } from '@stencil/core'; -import { Annotations, DataStream as SynchroChartsDataStream } from '@synchro-charts/core'; +import { + AlarmsConfig, + Annotations, + Axis, + DataStream as SynchroChartsDataStream, + LayoutConfig, + MessageOverrides, + MinimalSizeConfig, + MovementConfig, + ScaleConfig, +} from '@synchro-charts/core'; import { StyleSettingsMap, TimeSeriesDataRequestSettings, @@ -17,7 +27,15 @@ import { v4 as uuidv4 } from 'uuid'; shadow: false, }) export class IotStatusTimeline { - @Prop() annotations: Annotations; + @Prop() annotations?: Annotations; + @Prop() axis?: Axis.Options; + @Prop() messageOverrides?: MessageOverrides; + @Prop() alarms?: AlarmsConfig; + @Prop() gestures?: boolean; + @Prop() movement?: MovementConfig; + @Prop() scale?: ScaleConfig; + @Prop() layout?: LayoutConfig; + @Prop() size?: MinimalSizeConfig; @Prop() queries!: TimeQuery[]; @@ -83,6 +101,14 @@ export class IotStatusTimeline { viewport={this.viewport} isEditing={this.isEditing} widgetId={this.widgetId} + gestures={this.gestures} + movement={this.movement} + scale={this.scale} + layout={this.layout} + size={this.size} + axis={this.axis} + messageOverrides={this.messageOverrides} + alarms={this.alarms} /> )} /> diff --git a/packages/components/src/components/iot-table/iot-table.tsx b/packages/components/src/components/iot-table/iot-table.tsx index 881a76a8f..92497c44b 100644 --- a/packages/components/src/components/iot-table/iot-table.tsx +++ b/packages/components/src/components/iot-table/iot-table.tsx @@ -1,5 +1,11 @@ import { Component, Prop, h, State, Listen, Watch } from '@stencil/core'; -import { Annotations, DataStream as SynchroChartsDataStream, TableColumn } from '@synchro-charts/core'; +import { + Annotations, + DataStream as SynchroChartsDataStream, + MessageOverrides, + TableColumn, + Trend, +} from '@synchro-charts/core'; import { StyleSettingsMap, TimeSeriesDataRequestSettings, @@ -19,6 +25,10 @@ import { v4 as uuidv4 } from 'uuid'; export class IotTable { @Prop() annotations: Annotations; + @Prop() messageOverrides?: MessageOverrides; + + @Prop() trends: Trend[]; + @Prop() tableColumns: TableColumn[]; @Prop() queries!: TimeQuery[]; @@ -81,6 +91,8 @@ export class IotTable { annotations={this.annotations} viewport={this.viewport} widgetId={this.widgetId} + messageOverrides={this.messageOverrides} + trends={this.trends} /> )} /> diff --git a/packages/components/src/integration/iot-bar-chart/iot-bar-chart.spec.component.ts b/packages/components/src/integration/iot-bar-chart/iot-bar-chart.spec.component.ts index c7d9feff9..9519310d4 100644 --- a/packages/components/src/integration/iot-bar-chart/iot-bar-chart.spec.component.ts +++ b/packages/components/src/integration/iot-bar-chart/iot-bar-chart.spec.component.ts @@ -1,6 +1,8 @@ import { renderChart } from '../../testing/renderChart'; import { mockGetAggregatedOrRawResponse } from '../../testing/mocks/mockGetAggregatedOrRawResponse'; import { mockGetAssetSummary } from '../../testing/mocks/mockGetAssetSummaries'; +import { ScaleConfig, ScaleType } from '@synchro-charts/core'; +import { mockGetAssetModelSummary } from '../../testing/mocks/mockGetAssetModelSummary'; const SECOND_IN_MS = 1000; @@ -8,7 +10,7 @@ const snapshotOptions = { clip: { x: 0, y: 0, width: 400, height: 500 }, }; -describe.skip('bar chart', () => { +describe('bar chart', () => { const assetId = 'some-asset-id'; const assetModelId = 'some-asset-model-id'; @@ -21,18 +23,62 @@ describe.skip('bar chart', () => { resolution: req.query.resolution as string, }) ); - }); + }).as('getAggregates'); cy.intercept(`/assets/${assetId}`, (req) => { - req.reply(mockGetAssetSummary({ assetModelId, id: assetId })); - }); + req.reply(mockGetAssetSummary({ assetModelId, assetId })); + }).as('getAssetSummary'); + + cy.intercept(`/asset-models/${assetModelId}`, (req) => { + req.reply(mockGetAssetModelSummary({ assetModelId })); + }).as('getAssetModels'); }); it('renders', () => { renderChart({ chartType: 'iot-bar-chart', settings: { resolution: '1m' } }); - cy.wait(SECOND_IN_MS * 2); + cy.wait(['@getAggregates', '@getAssetSummary', '@getAssetModels']); cy.matchImageSnapshot(snapshotOptions); }); + + it('renders passes all props to synchro-charts', () => { + const props = { + widgetId: '123', + viewport: { duration: '5m' }, + size: { width: 10, height: 10 }, + movement: { enableXScroll: true, enableYScroll: false, zoomMax: 10, zoomMin: 0 }, + scale: { + xScaleType: ScaleType.TimeSeries, + yScaleType: ScaleType.TimeSeries, + xScaleSide: 'top', + yScaleSide: 'left', + } as ScaleConfig, + layout: { + xTicksVisible: true, + yTicksVisible: true, + xGridVisible: true, + yGridVisible: true, + }, + gestures: true, + annotations: { show: true, thresholdOptions: true, colorDataAcrossThresholds: true }, + isEditing: false, + trends: [], + messageOverrides: {}, + axis: { labels: { yAxis: { content: 'yAxis' } } }, + }; + + renderChart({ chartType: 'iot-bar-chart', settings: { resolution: '1m' }, ...props }); + + cy.wait(SECOND_IN_MS * 2); + + cy.get('sc-bar-chart').should((e) => { + const [chart] = e.get(); + (Object.keys(props) as Array).forEach((prop) => { + const value = chart[prop as keyof HTMLScBarChartElement]; + const passedInValue = props[prop]; + expect(value).to.deep.equal(passedInValue); + }); + }); + }); }); diff --git a/packages/components/src/integration/iot-connector/iot-connector.spec.component.ts b/packages/components/src/integration/iot-connector/iot-connector.spec.component.ts index ebba42c3f..56c6aeb77 100644 --- a/packages/components/src/integration/iot-connector/iot-connector.spec.component.ts +++ b/packages/components/src/integration/iot-connector/iot-connector.spec.component.ts @@ -1,6 +1,7 @@ import { renderChart, testChartContainerClassNameSelector } from '../../testing/renderChart'; import { mockGetAggregatedOrRawResponse } from '../../testing/mocks/mockGetAggregatedOrRawResponse'; import { mockGetAssetSummary } from '../../testing/mocks/mockGetAssetSummaries'; +import { mockGetAssetModelSummary } from '../../testing/mocks/mockGetAssetModelSummary'; const SECOND_IN_MS = 1000; @@ -49,17 +50,21 @@ describe('handles gestures', () => { }) ); } - }); + }).as('getAggregates'); cy.intercept(`/assets/${assetId}`, (req) => { - req.reply(mockGetAssetSummary({ assetModelId, id: assetId })); - }); + req.reply(mockGetAssetSummary({ assetModelId, assetId })); + }).as('getAssetSummary'); + + cy.intercept(`/asset-models/${assetModelId}`, (req) => { + req.reply(mockGetAssetModelSummary({ assetModelId })); + }).as('getAssetModels'); }); it('zooms in and out', () => { renderChart(); - cy.wait(SECOND_IN_MS * 2); + cy.wait(['@getAggregates', '@getAssetSummary', '@getAssetModels']); cy.get(testChartContainerClassNameSelector).dblclick(); diff --git a/packages/components/src/integration/iot-kpi/iot-kpi.spec.component.ts b/packages/components/src/integration/iot-kpi/iot-kpi.spec.component.ts index 4579c7c03..4234880f5 100644 --- a/packages/components/src/integration/iot-kpi/iot-kpi.spec.component.ts +++ b/packages/components/src/integration/iot-kpi/iot-kpi.spec.component.ts @@ -1,6 +1,7 @@ import { renderChart } from '../../testing/renderChart'; import { mockLatestValueResponse } from '../../testing/mocks/mockGetAggregatedOrRawResponse'; import { mockGetAssetSummary } from '../../testing/mocks/mockGetAssetSummaries'; +import { mockGetAssetModelSummary } from '../../testing/mocks/mockGetAssetModelSummary'; const SECOND_IN_MS = 1000; @@ -15,18 +16,45 @@ describe('kpi', () => { before(() => { cy.intercept('/properties/latest?*', (req) => { req.reply(mockLatestValueResponse()); - }); + }).as('getAggregates'); cy.intercept(`/assets/${assetId}`, (req) => { - req.reply(mockGetAssetSummary({ assetModelId, id: assetId })); - }); + req.reply(mockGetAssetSummary({ assetModelId, assetId })); + }).as('getAssetSummary'); + + cy.intercept(`/asset-models/${assetModelId}`, (req) => { + req.reply(mockGetAssetModelSummary({ assetModelId })); + }).as('getAssetModels'); }); it('renders', () => { renderChart({ chartType: 'iot-kpi', settings: { resolution: '0' }, viewport: { duration: '1m' } }); - cy.wait(SECOND_IN_MS * 2); + cy.wait(['@getAggregates', '@getAssetSummary', '@getAssetModels']); cy.matchImageSnapshot(snapshotOptions); }); + + it('renders passes all props to synchro-charts', () => { + const props = { + widgetId: '123', + viewport: { duration: '5m' }, + annotations: { show: true, thresholdOptions: true, colorDataAcrossThresholds: true }, + isEditing: false, + messageOverrides: {}, + }; + + renderChart({ chartType: 'iot-kpi', settings: { resolution: '0' }, ...props }); + + cy.wait(SECOND_IN_MS * 2); + + cy.get('sc-kpi').should((e) => { + const [chart] = e.get(); + (Object.keys(props) as Array).forEach((prop) => { + const value = chart[prop as keyof HTMLScKpiElement]; + const passedInValue = props[prop]; + expect(value).to.deep.equal(passedInValue); + }); + }); + }); }); diff --git a/packages/components/src/integration/iot-line-chart/iot-line-chart.spec.component.ts b/packages/components/src/integration/iot-line-chart/iot-line-chart.spec.component.ts index 4ca44c163..697d12778 100644 --- a/packages/components/src/integration/iot-line-chart/iot-line-chart.spec.component.ts +++ b/packages/components/src/integration/iot-line-chart/iot-line-chart.spec.component.ts @@ -1,6 +1,8 @@ import { renderChart } from '../../testing/renderChart'; import { mockGetAggregatedOrRawResponse } from '../../testing/mocks/mockGetAggregatedOrRawResponse'; import { mockGetAssetSummary } from '../../testing/mocks/mockGetAssetSummaries'; +import { ScaleConfig, ScaleType } from '@synchro-charts/core'; +import { mockGetAssetModelSummary } from '../../testing/mocks/mockGetAssetModelSummary'; const SECOND_IN_MS = 1000; @@ -31,18 +33,62 @@ describe('line chart', () => { }) ); } - }); + }).as('getAggregates'); cy.intercept(`/assets/${assetId}`, (req) => { - req.reply(mockGetAssetSummary({ assetModelId, id: assetId })); - }); + req.reply(mockGetAssetSummary({ assetModelId, assetId })); + }).as('getAssetSummary'); + + cy.intercept(`/asset-models/${assetModelId}`, (req) => { + req.reply(mockGetAssetModelSummary({ assetModelId })); + }).as('getAssetModels'); }); it('renders', () => { renderChart({ chartType: 'iot-line-chart' }); - cy.wait(SECOND_IN_MS * 2); + cy.wait(['@getAggregates', '@getAssetSummary', '@getAssetModels']); cy.matchImageSnapshot(snapshotOptions); }); + + it('renders passes all props to synchro-charts', () => { + const props = { + widgetId: '123', + viewport: { duration: '5m' }, + size: { width: 10, height: 10 }, + movement: { enableXScroll: true, enableYScroll: false, zoomMax: 10, zoomMin: 0 }, + scale: { + xScaleType: ScaleType.TimeSeries, + yScaleType: ScaleType.TimeSeries, + xScaleSide: 'top', + yScaleSide: 'left', + } as ScaleConfig, + layout: { + xTicksVisible: true, + yTicksVisible: true, + xGridVisible: true, + yGridVisible: true, + }, + gestures: true, + annotations: { show: true, thresholdOptions: true, colorDataAcrossThresholds: true }, + isEditing: false, + trends: [], + messageOverrides: {}, + axis: { labels: { yAxis: { content: 'yAxis' } } }, + }; + + renderChart({ chartType: 'iot-line-chart', ...props }); + + cy.wait(SECOND_IN_MS * 2); + + cy.get('sc-line-chart').should((e) => { + const [chart] = e.get(); + (Object.keys(props) as Array).forEach((prop) => { + const value = chart[prop as keyof HTMLScLineChartElement]; + const passedInValue = props[prop]; + expect(value).to.deep.equal(passedInValue); + }); + }); + }); }); diff --git a/packages/components/src/integration/iot-scatter-chart/iot-scatter-chart.spec.component.ts b/packages/components/src/integration/iot-scatter-chart/iot-scatter-chart.spec.component.ts index 8d55eac48..e5643a166 100644 --- a/packages/components/src/integration/iot-scatter-chart/iot-scatter-chart.spec.component.ts +++ b/packages/components/src/integration/iot-scatter-chart/iot-scatter-chart.spec.component.ts @@ -1,6 +1,8 @@ import { renderChart } from '../../testing/renderChart'; import { mockGetAggregatedOrRawResponse } from '../../testing/mocks/mockGetAggregatedOrRawResponse'; import { mockGetAssetSummary } from '../../testing/mocks/mockGetAssetSummaries'; +import { ScaleConfig, ScaleType } from '@synchro-charts/core'; +import { mockGetAssetModelSummary } from '../../testing/mocks/mockGetAssetModelSummary'; const SECOND_IN_MS = 1000; @@ -21,18 +23,62 @@ describe('scatter chart', () => { resolution: req.query.resolution as string, }) ); - }); + }).as('getAggregates'); cy.intercept(`/assets/${assetId}`, (req) => { - req.reply(mockGetAssetSummary({ assetModelId, id: assetId })); - }); + req.reply(mockGetAssetSummary({ assetModelId, assetId })); + }).as('getAssetSummary'); + + cy.intercept(`/asset-models/${assetModelId}`, (req) => { + req.reply(mockGetAssetModelSummary({ assetModelId })); + }).as('getAssetModels'); }); it('renders', () => { renderChart({ chartType: 'iot-scatter-chart' }); - cy.wait(SECOND_IN_MS * 2); + cy.wait(['@getAggregates', '@getAssetSummary', '@getAssetModels']); cy.matchImageSnapshot(snapshotOptions); }); + + it('renders passes all props to synchro-charts', () => { + const props = { + widgetId: '123', + viewport: { duration: '5m' }, + size: { width: 10, height: 10 }, + movement: { enableXScroll: true, enableYScroll: false, zoomMax: 10, zoomMin: 0 }, + scale: { + xScaleType: ScaleType.TimeSeries, + yScaleType: ScaleType.TimeSeries, + xScaleSide: 'top', + yScaleSide: 'left', + } as ScaleConfig, + layout: { + xTicksVisible: true, + yTicksVisible: true, + xGridVisible: true, + yGridVisible: true, + }, + gestures: true, + annotations: { show: true, thresholdOptions: true, colorDataAcrossThresholds: true }, + isEditing: false, + trends: [], + messageOverrides: {}, + axis: { labels: { yAxis: { content: 'yAxis' } } }, + }; + + renderChart({ chartType: 'iot-scatter-chart', ...props }); + + cy.wait(SECOND_IN_MS * 2); + + cy.get('sc-scatter-chart').should((e) => { + const [chart] = e.get(); + (Object.keys(props) as Array).forEach((prop) => { + const value = chart[prop as keyof HTMLScScatterChartElement]; + const passedInValue = props[prop]; + expect(value).to.deep.equal(passedInValue); + }); + }); + }); }); diff --git a/packages/components/src/integration/iot-status-grid/iot-status-grid.spec.component.ts b/packages/components/src/integration/iot-status-grid/iot-status-grid.spec.component.ts index a3cece237..92b6609a7 100644 --- a/packages/components/src/integration/iot-status-grid/iot-status-grid.spec.component.ts +++ b/packages/components/src/integration/iot-status-grid/iot-status-grid.spec.component.ts @@ -2,6 +2,7 @@ import { renderChart } from '../../testing/renderChart'; import { mockLatestValueResponse } from '../../testing/mocks/mockGetAggregatedOrRawResponse'; import { mockGetAssetSummary } from '../../testing/mocks/mockGetAssetSummaries'; import { COMPARISON_OPERATOR } from '@synchro-charts/core'; +import { mockGetAssetModelSummary } from '../../testing/mocks/mockGetAssetModelSummary'; const SECOND_IN_MS = 1000; @@ -16,11 +17,15 @@ describe('status grid', () => { before(() => { cy.intercept('/properties/latest?*', (req) => { req.reply(mockLatestValueResponse()); - }); + }).as('getAggregates'); cy.intercept(`/assets/${assetId}`, (req) => { - req.reply(mockGetAssetSummary({ assetModelId, id: assetId })); - }); + req.reply(mockGetAssetSummary({ assetModelId, assetId })); + }).as('getAssetSummary'); + + cy.intercept(`/asset-models/${assetModelId}`, (req) => { + req.reply(mockGetAssetModelSummary({ assetModelId })); + }).as('getAssetModels'); }); it('renders', () => { @@ -31,8 +36,34 @@ describe('status grid', () => { annotations: { y: [{ color: '#FF0000', comparisonOperator: COMPARISON_OPERATOR.GREATER_THAN, value: 25 }] }, }); - cy.wait(SECOND_IN_MS * 2); + cy.wait(['@getAggregates', '@getAssetSummary', '@getAssetModels']); cy.matchImageSnapshot(snapshotOptions); }); + + it('renders passes all props to synchro-charts', () => { + const props = { + widgetId: '123', + viewport: { duration: '1m' }, + annotations: { y: [{ color: '#FF0000', comparisonOperator: COMPARISON_OPERATOR.GREATER_THAN, value: 25 }] }, + isEditing: false, + }; + + renderChart({ + chartType: 'iot-status-grid', + settings: { resolution: '0' }, + ...props, + }); + + cy.wait(SECOND_IN_MS * 2); + + cy.get('sc-status-grid').should((e) => { + const [chart] = e.get(); + (Object.keys(props) as Array).forEach((prop) => { + const value = chart[prop as keyof HTMLScStatusGridElement]; + const passedInValue = props[prop]; + expect(value).to.deep.equal(passedInValue); + }); + }); + }); }); diff --git a/packages/components/src/integration/iot-status-timeline/iot-status-timeline.spec.component.ts b/packages/components/src/integration/iot-status-timeline/iot-status-timeline.spec.component.ts index 395b5d1c0..e3d57cf9e 100644 --- a/packages/components/src/integration/iot-status-timeline/iot-status-timeline.spec.component.ts +++ b/packages/components/src/integration/iot-status-timeline/iot-status-timeline.spec.component.ts @@ -2,6 +2,7 @@ import { renderChart } from '../../testing/renderChart'; import { mockGetAggregatedOrRawResponse } from '../../testing/mocks/mockGetAggregatedOrRawResponse'; import { mockGetAssetSummary } from '../../testing/mocks/mockGetAssetSummaries'; import { COMPARISON_OPERATOR } from '@synchro-charts/core'; +import { mockGetAssetModelSummary } from '../../testing/mocks/mockGetAssetModelSummary'; const SECOND_IN_MS = 1000; @@ -32,11 +33,15 @@ describe('status timeline', () => { }) ); } - }); + }).as('getAggregates'); cy.intercept(`/assets/${assetId}`, (req) => { - req.reply(mockGetAssetSummary({ assetModelId, id: assetId })); - }); + req.reply(mockGetAssetSummary({ assetModelId, assetId })); + }).as('getAssetSummary'); + + cy.intercept(`/asset-models/${assetModelId}`, (req) => { + req.reply(mockGetAssetModelSummary({ assetModelId })); + }).as('getAssetModels'); }); it('renders', () => { @@ -46,8 +51,34 @@ describe('status timeline', () => { annotations: { y: [{ color: '#FF0000', comparisonOperator: COMPARISON_OPERATOR.GREATER_THAN, value: 26 }] }, }); - cy.wait(SECOND_IN_MS * 2); + cy.wait(['@getAggregates', '@getAssetSummary', '@getAssetModels']); cy.matchImageSnapshot(snapshotOptions); }); + + it('renders passes all props to synchro-charts', () => { + const props = { + widgetId: '123', + viewport: { duration: '1m' }, + annotations: { y: [{ color: '#FF0000', comparisonOperator: COMPARISON_OPERATOR.GREATER_THAN, value: 26 }] }, + isEditing: false, + }; + + renderChart({ + chartType: 'iot-status-timeline', + settings: { resolution: '0' }, + ...props, + }); + + cy.wait(SECOND_IN_MS * 2); + + cy.get('sc-status-timeline').should((e) => { + const [chart] = e.get(); + (Object.keys(props) as Array).forEach((prop) => { + const value = chart[prop as keyof HTMLScStatusTimelineElement]; + const passedInValue = props[prop]; + expect(value).to.deep.equal(passedInValue); + }); + }); + }); }); diff --git a/packages/components/src/testing/mocks/mockGetAssetModelSummary.ts b/packages/components/src/testing/mocks/mockGetAssetModelSummary.ts new file mode 100644 index 000000000..86de72add --- /dev/null +++ b/packages/components/src/testing/mocks/mockGetAssetModelSummary.ts @@ -0,0 +1,23 @@ +import { DescribeAssetModelResponse } from '@aws-sdk/client-iotsitewise'; + +export const mockGetAssetModelSummary = ( + assetSummary: Partial = {} +): DescribeAssetModelResponse => ({ + assetModelId: 'some-asset-model-id', + assetModelName: 'some-asset-model-name', + assetModelDescription: undefined, + assetModelCreationDate: undefined, + assetModelLastUpdateDate: undefined, + assetModelStatus: undefined, + assetModelHierarchies: [], + assetModelArn: undefined, + assetModelProperties: [ + { + dataType: 'DOUBLE', + id: 'some-property-id', + name: 'Asset Name', + type: undefined, + }, + ], + ...assetSummary, +}); diff --git a/packages/components/src/testing/mocks/mockGetAssetSummaries.ts b/packages/components/src/testing/mocks/mockGetAssetSummaries.ts index 2266e75ed..5fd87dd86 100644 --- a/packages/components/src/testing/mocks/mockGetAssetSummaries.ts +++ b/packages/components/src/testing/mocks/mockGetAssetSummaries.ts @@ -1,13 +1,20 @@ -import { AssetSummary } from '@aws-sdk/client-iotsitewise'; +import { DescribeAssetResponse } from '@aws-sdk/client-iotsitewise'; -export const mockGetAssetSummary = (assetSummary: Partial = {}): AssetSummary => ({ - id: 'some-asset-id', - name: 'some-asset-summary-name', +export const mockGetAssetSummary = (assetSummary: Partial = {}): DescribeAssetResponse => ({ + assetId: 'some-asset-id', + assetName: 'some-asset-summary-name', assetModelId: 'some-asset-model-id', - creationDate: undefined, - lastUpdateDate: undefined, - status: undefined, - hierarchies: [], - arn: undefined, + assetCreationDate: undefined, + assetLastUpdateDate: undefined, + assetStatus: undefined, + assetHierarchies: [], + assetArn: undefined, + assetProperties: [ + { + id: 'some-property-id', + name: 'Asset Name', + dataType: 'DOUBLE', + }, + ], ...assetSummary, }); diff --git a/packages/components/src/testing/renderChart.tsx b/packages/components/src/testing/renderChart.tsx index dce1c80f7..7288fba22 100644 --- a/packages/components/src/testing/renderChart.tsx +++ b/packages/components/src/testing/renderChart.tsx @@ -9,7 +9,18 @@ import { TimeSeriesDataRequestSettings, } from '@iot-app-kit/core'; import { initialize } from '@iot-app-kit/source-iotsitewise'; -import { MinimalViewPortConfig, Annotations } from '@synchro-charts/core'; +import { + MinimalViewPortConfig, + Annotations, + MinimalSizeConfig, + Trend, + ScaleConfig, + MovementConfig, + MessageOverrides, + LayoutConfig, + LegendConfig, + Axis, +} from '@synchro-charts/core'; import { MINUTE_IN_MS } from '@iot-app-kit/core/src/common/time'; // eslint-disable-next-line @typescript-eslint/no-var-requires const { defineCustomElements } = require('@iot-app-kit/components/loader'); @@ -61,6 +72,17 @@ export const renderChart = ( settings = defaultSettings, styleSettings, annotations, + widgetId, + size, + trends, + scale, + movement, + messageOverrides, + layout, + legend, + isEditing, + gestures, + axis, }: { chartType?: string; queries?: TimeQuery[]; @@ -68,6 +90,17 @@ export const renderChart = ( settings?: TimeSeriesDataRequestSettings; styleSettings?: StyleSettingsMap; annotations?: Annotations; + widgetId?: string; + size?: MinimalSizeConfig; + trends?: Trend[]; + scale?: ScaleConfig; + movement?: MovementConfig; + messageOverrides?: MessageOverrides; + layout?: LayoutConfig; + legend?: LegendConfig; + isEditing?: boolean; + gestures?: boolean; + axis?: Axis.Options; } = { chartType: defaultChartType, queries: defaultQueries, @@ -83,7 +116,24 @@ export const renderChart = ( }, render: function () { const containerProps = { class: testChartContainerClassName, style: { width: '400px', height: '500px' } }; - const chartProps: any = { queries, viewport, settings, styleSettings, annotations }; + const chartProps: any = { + queries, + viewport, + settings, + styleSettings, + annotations, + widgetId, + size, + trends, + scale, + movement, + messageOverrides, + layout, + legend, + isEditing, + gestures, + axis, + }; return (
diff --git a/packages/core/package.json b/packages/core/package.json index 32c502f80..404ea6dbf 100755 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -4,7 +4,7 @@ "publishConfig": { "access": "public" }, - "version": "1.4.0", + "version": "1.5.0", "description": "IoT Application Kit core", "main": "./dist/index.cj.js", "module": "./dist/index.js", @@ -34,7 +34,7 @@ "@aws-sdk/client-iotsitewise": "^3.39.0", "@aws-sdk/credential-providers": "^3.39.0", "@rollup/plugin-typescript": "^8.3.0", - "@synchro-charts/core": "^4.0.1", + "@synchro-charts/core": "^5.0.0", "d3-array": "^2.3.2", "flush-promises": "^1.0.2", "intervals-fn": "^3.0.3", diff --git a/packages/react-components/package.json b/packages/react-components/package.json index 9406715d8..af027d79d 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -5,7 +5,7 @@ "access": "public" }, "sideEffects": false, - "version": "1.4.0", + "version": "1.5.0", "description": "React specific wrapper for IoT Application Kit", "author": { "name": "Amazon Web Services", @@ -41,7 +41,7 @@ "typescript": "^3.3.4000" }, "dependencies": { - "@iot-app-kit/components": "^1.4.0" + "@iot-app-kit/components": "^1.5.0" }, "peerDependencies": { "react": "^17.0.2", diff --git a/packages/related-table/package.json b/packages/related-table/package.json index a59cecf51..b800a7d83 100644 --- a/packages/related-table/package.json +++ b/packages/related-table/package.json @@ -3,7 +3,7 @@ "publishConfig": { "access": "public" }, - "version": "1.4.0", + "version": "1.5.0", "description": "IoT Application Kit - Related Table component", "license": "Apache-2.0", "main": "dist/index.js", diff --git a/packages/source-iotsitewise/CHANGELOG.md b/packages/source-iotsitewise/CHANGELOG.md index 8e39fbb2c..e6a6bde06 100644 --- a/packages/source-iotsitewise/CHANGELOG.md +++ b/packages/source-iotsitewise/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# 1.5.0 (2022-07-09) + + +### Features + +* synchro-charts version updated to 5.0.0 + + + + # 1.4.0 (2022-06-09) diff --git a/packages/source-iotsitewise/package.json b/packages/source-iotsitewise/package.json index 46c0b1d14..4c97afe3c 100644 --- a/packages/source-iotsitewise/package.json +++ b/packages/source-iotsitewise/package.json @@ -3,7 +3,7 @@ "publishConfig": { "access": "public" }, - "version": "1.4.0", + "version": "1.5.0", "description": "AWS IoT SiteWise source for IoT Application Kit", "homepage": "https://github.com/awslabs/iot-app-kit#readme", "license": "Apache-2.0", @@ -38,9 +38,9 @@ }, "dependencies": { "@aws-sdk/client-iotsitewise": "^3.39.0", - "@iot-app-kit/core": "^1.4.0", + "@iot-app-kit/core": "^1.5.0", "@rollup/plugin-typescript": "^8.3.0", - "@synchro-charts/core": "^4.0.1", + "@synchro-charts/core": "^5.0.0", "flush-promises": "^1.0.2", "rxjs": "^7.4.0", "typescript": "4.4.4" From 85234095736001578df2ab565375ef3edac72ce3 Mon Sep 17 00:00:00 2001 From: Bowei Han Date: Tue, 12 Jul 2022 09:55:51 -0400 Subject: [PATCH 3/3] feat: batch API for historical, aggregated, and latest value data (#137) --- .gitignore | 1 + .stylelintignore | 1 + docs/AWSIoTSiteWiseSource.md | 28 + .../iot-time-series-connector.spec.ts | 11 +- .../iot-bar-chart.spec.component.ts | 18 +- .../iot-connector.spec.component.ts | 47 +- .../iot-kpi/iot-kpi.spec.component.ts | 26 +- .../iot-line-chart.spec.component.ts | 28 +- .../iot-scatter-chart.spec.component.ts | 18 +- .../iot-status-grid.spec.component.ts | 24 +- .../iot-status-timeline.spec.component.ts | 27 +- .../mocks/mockGetAggregatedOrRawResponse.ts | 125 ++++ .../src/testing/mocks/siteWiseSDK.ts | 12 +- .../testing/testing-ground/siteWiseQueries.ts | 28 +- .../testing/testing-ground/testing-ground.tsx | 135 +++- packages/core/package.json | 2 +- packages/core/src/__mocks__/iotsitewiseSDK.ts | 26 +- .../data-cache/dataReducer.spec.ts | 124 +++- .../src/data-module/data-cache/dataReducer.ts | 5 +- packages/source-iotsitewise/jest.config.js | 2 +- packages/source-iotsitewise/package.json | 3 +- .../src/__mocks__/assetPropertyValue.ts | 141 ++++ .../src/__mocks__/iotsitewiseSDK.ts | 26 +- packages/source-iotsitewise/src/initialize.ts | 13 +- .../src/time-series-data/client/batch.spec.ts | 149 +++++ .../src/time-series-data/client/batch.ts | 82 +++ .../batchGetAggregatedPropertyDataPoints.ts | 184 ++++++ .../batchGetHistoricalPropertyDataPoints.ts | 177 +++++ .../batchGetLatestPropertyDataPoints.ts | 158 +++++ .../time-series-data/client/client.spec.ts | 613 ++++++++++++++---- .../src/time-series-data/client/client.ts | 132 +++- .../getAggregatedPropertyDataPoints.ts | 12 +- .../getHistoricalPropertyDataPoints.ts | 10 +- .../getLatestPropertyDataPoint.ts | 8 +- .../src/time-series-data/data-source.spec.ts | 437 +++++-------- .../src/time-series-data/data-source.ts | 9 +- .../subscribeToTimeSeriesData.spec.ts | 34 +- .../src/time-series-data/types.ts | 5 + yarn.lock | 520 +++++++++++++-- 39 files changed, 2798 insertions(+), 603 deletions(-) create mode 100644 packages/source-iotsitewise/src/time-series-data/client/batch.spec.ts create mode 100644 packages/source-iotsitewise/src/time-series-data/client/batch.ts create mode 100644 packages/source-iotsitewise/src/time-series-data/client/batchGetAggregatedPropertyDataPoints.ts create mode 100644 packages/source-iotsitewise/src/time-series-data/client/batchGetHistoricalPropertyDataPoints.ts create mode 100644 packages/source-iotsitewise/src/time-series-data/client/batchGetLatestPropertyDataPoints.ts rename packages/source-iotsitewise/src/time-series-data/client/{ => legacy}/getAggregatedPropertyDataPoints.ts (90%) rename packages/source-iotsitewise/src/time-series-data/client/{ => legacy}/getHistoricalPropertyDataPoints.ts (91%) rename packages/source-iotsitewise/src/time-series-data/client/{ => legacy}/getLatestPropertyDataPoint.ts (88%) diff --git a/.gitignore b/.gitignore index ad363f719..c8da6a841 100755 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/.stylelintignore b/.stylelintignore index 8b12bbfae..9e455a0da 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -3,3 +3,4 @@ dist/ www/ loader/ node_modules/ +coverage/ diff --git a/docs/AWSIoTSiteWiseSource.md b/docs/AWSIoTSiteWiseSource.md index 85f58cbf7..5cc70f4c6 100644 --- a/docs/AWSIoTSiteWiseSource.md +++ b/docs/AWSIoTSiteWiseSource.md @@ -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. @@ -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` @@ -140,6 +144,7 @@ const { query } = initialize({ iotsitewiseClient }); ]} /> ``` +--- ### `assetTree` @@ -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. diff --git a/packages/components/src/components/iot-time-series-connector/iot-time-series-connector.spec.ts b/packages/components/src/components/iot-time-series-connector/iot-time-series-connector.spec.ts index 0f6264c0e..4297eb624 100644 --- a/packages/components/src/components/iot-time-series-connector/iot-time-series-connector.spec.ts +++ b/packages/components/src/components/iot-time-series-connector/iot-time-series-connector.spec.ts @@ -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'; @@ -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), }), }); @@ -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), }), }); diff --git a/packages/components/src/integration/iot-bar-chart/iot-bar-chart.spec.component.ts b/packages/components/src/integration/iot-bar-chart/iot-bar-chart.spec.component.ts index 9519310d4..02de4a73b 100644 --- a/packages/components/src/integration/iot-bar-chart/iot-bar-chart.spec.component.ts +++ b/packages/components/src/integration/iot-bar-chart/iot-bar-chart.spec.component.ts @@ -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'; @@ -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'); diff --git a/packages/components/src/integration/iot-connector/iot-connector.spec.component.ts b/packages/components/src/integration/iot-connector/iot-connector.spec.component.ts index 56c6aeb77..9fe9d24f5 100644 --- a/packages/components/src/integration/iot-connector/iot-connector.spec.component.ts +++ b/packages/components/src/integration/iot-connector/iot-connector.spec.component.ts @@ -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'; @@ -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, }) ); } diff --git a/packages/components/src/integration/iot-kpi/iot-kpi.spec.component.ts b/packages/components/src/integration/iot-kpi/iot-kpi.spec.component.ts index 4234880f5..316cbcb67 100644 --- a/packages/components/src/integration/iot-kpi/iot-kpi.spec.component.ts +++ b/packages/components/src/integration/iot-kpi/iot-kpi.spec.component.ts @@ -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'; @@ -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) => { @@ -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); }); diff --git a/packages/components/src/integration/iot-line-chart/iot-line-chart.spec.component.ts b/packages/components/src/integration/iot-line-chart/iot-line-chart.spec.component.ts index 697d12778..c372ef45a 100644 --- a/packages/components/src/integration/iot-line-chart/iot-line-chart.spec.component.ts +++ b/packages/components/src/integration/iot-line-chart/iot-line-chart.spec.component.ts @@ -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'; @@ -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, }) ); } diff --git a/packages/components/src/integration/iot-scatter-chart/iot-scatter-chart.spec.component.ts b/packages/components/src/integration/iot-scatter-chart/iot-scatter-chart.spec.component.ts index e5643a166..32a66df11 100644 --- a/packages/components/src/integration/iot-scatter-chart/iot-scatter-chart.spec.component.ts +++ b/packages/components/src/integration/iot-scatter-chart/iot-scatter-chart.spec.component.ts @@ -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'; @@ -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'); diff --git a/packages/components/src/integration/iot-status-grid/iot-status-grid.spec.component.ts b/packages/components/src/integration/iot-status-grid/iot-status-grid.spec.component.ts index 92b6609a7..06319a554 100644 --- a/packages/components/src/integration/iot-status-grid/iot-status-grid.spec.component.ts +++ b/packages/components/src/integration/iot-status-grid/iot-status-grid.spec.component.ts @@ -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'; @@ -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) => { diff --git a/packages/components/src/integration/iot-status-timeline/iot-status-timeline.spec.component.ts b/packages/components/src/integration/iot-status-timeline/iot-status-timeline.spec.component.ts index e3d57cf9e..621c6bd58 100644 --- a/packages/components/src/integration/iot-status-timeline/iot-status-timeline.spec.component.ts +++ b/packages/components/src/integration/iot-status-timeline/iot-status-timeline.spec.component.ts @@ -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 { COMPARISON_OPERATOR } from '@synchro-charts/core'; import { mockGetAssetModelSummary } from '../../testing/mocks/mockGetAssetModelSummary'; @@ -14,22 +14,25 @@ describe('status timeline', () => { const assetId = 'some-asset-id'; const assetModelId = 'some-asset-model-id'; - before(() => { - cy.intercept('/properties/history?*', (req) => { - if (new Date(req.query.startDate).getUTCFullYear() === 1899) { + 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; + + 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), - resolution: req.query.resolution as string, + 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), - resolution: req.query.resolution as string, + mockBatchGetAggregatedOrRawResponse({ + startDate: new Date(startDateInMs), + endDate: new Date(endDateInMs), + entryId: '1-0', }) ); } diff --git a/packages/components/src/testing/mocks/mockGetAggregatedOrRawResponse.ts b/packages/components/src/testing/mocks/mockGetAggregatedOrRawResponse.ts index ed13eaad8..e54b55718 100644 --- a/packages/components/src/testing/mocks/mockGetAggregatedOrRawResponse.ts +++ b/packages/components/src/testing/mocks/mockGetAggregatedOrRawResponse.ts @@ -4,6 +4,9 @@ import { GetAssetPropertyAggregatesResponse, GetAssetPropertyValueHistoryResponse, GetAssetPropertyValueResponse, + BatchGetAssetPropertyValueHistoryResponse, + BatchGetAssetPropertyAggregatesResponse, + BatchGetAssetPropertyValueResponse, } from '@aws-sdk/client-iotsitewise'; import { RAW_DATA, MINUTE_AGGREGATED_DATA, HOUR_AGGREGATED_DATA, DAY_AGGREGATED_DATA } from './data'; import { MINUTE_IN_MS, HOUR_IN_MS, DAY_IN_MS, SECOND_IN_MS } from '@iot-app-kit/core/src/common/time'; @@ -20,6 +23,21 @@ export const mockLatestValueResponse = (): { body: GetAssetPropertyValueResponse }; }; +export const mockBatchLatestValueResponse = (): { body: BatchGetAssetPropertyValueResponse } => { + return { + body: { + successEntries: [ + { + entryId: '0-0', + assetPropertyValue: RAW_DATA[RAW_DATA.length - 1], + }, + ], + errorEntries: [], + skippedEntries: [], + }, + }; +}; + /** * Returns exactly what the parsed JSON response from the SDK returns. * @@ -97,3 +115,110 @@ export const mockGetAggregatedOrRawResponse = ({ }; } }; + +/** + * Returns exactly what the parsed JSON response from the SDK returns for batch calls + * + * There's slight deviations from the specified types: + * 1. The timestamps are converted from number to date for the times that don't have a nanosecond component, so we must 'cast' them to allow the types to pass + * 2. The `undefined` is turned into a null, so we must cast those. + */ +export const mockBatchGetAggregatedOrRawResponse = ({ + startDate, + endDate, + resolution, + entryId = '0-0', +}: { + startDate: Date; + endDate: Date; + resolution?: string; + entryId?: string; +}): { body: BatchGetAssetPropertyValueHistoryResponse | BatchGetAssetPropertyAggregatesResponse } => { + const startTimestampInSeconds = Math.round(startDate.getTime()) / 1000; + const endTimestampInSeconds = Math.round(endDate.getTime()) / 1000; + + if (resolution === '1m') { + const data: AggregatedValue[] = []; + for (let timestamp = startTimestampInSeconds; timestamp <= endTimestampInSeconds; timestamp += MINUTE_IN_S) { + data.push({ + ...MINUTE_AGGREGATED_DATA[timestamp % MINUTE_AGGREGATED_DATA.length], + timestamp: timestamp as unknown as Date, + }); + } + + return { + body: { + successEntries: [ + { + entryId, + aggregatedValues: data, + }, + ], + errorEntries: [], + skippedEntries: [], + }, + }; + } else if (resolution === '1h') { + const data: AggregatedValue[] = []; + for (let timestamp = startTimestampInSeconds; timestamp <= endTimestampInSeconds; timestamp += HOUR_IN_S) { + data.push({ + ...HOUR_AGGREGATED_DATA[timestamp % HOUR_AGGREGATED_DATA.length], + timestamp: timestamp as unknown as Date, + }); + } + + return { + body: { + successEntries: [ + { + entryId, + aggregatedValues: data, + }, + ], + errorEntries: [], + skippedEntries: [], + }, + }; + } else if (resolution === '1d') { + const data: AggregatedValue[] = []; + for (let timestamp = startTimestampInSeconds; timestamp <= endTimestampInSeconds; timestamp += DAY_IN_S) { + data.push({ + ...DAY_AGGREGATED_DATA[timestamp % DAY_AGGREGATED_DATA.length], + timestamp: timestamp as unknown as Date, + }); + } + + return { + body: { + successEntries: [ + { + entryId, + aggregatedValues: data, + }, + ], + errorEntries: [], + skippedEntries: [], + }, + }; + } else { + const data: AssetPropertyValue[] = []; + for (let timeInSeconds = startTimestampInSeconds; timeInSeconds <= endTimestampInSeconds; timeInSeconds++) { + data.push({ + ...RAW_DATA[timeInSeconds % RAW_DATA.length], + timestamp: { offsetInNanos: 0, timeInSeconds }, + }); + } + return { + body: { + successEntries: [ + { + entryId, + assetPropertyValueHistory: data, + }, + ], + errorEntries: [], + skippedEntries: [], + }, + }; + } +}; diff --git a/packages/components/src/testing/mocks/siteWiseSDK.ts b/packages/components/src/testing/mocks/siteWiseSDK.ts index 7ffd172da..3dbfd123e 100644 --- a/packages/components/src/testing/mocks/siteWiseSDK.ts +++ b/packages/components/src/testing/mocks/siteWiseSDK.ts @@ -2,14 +2,18 @@ import { createMockSiteWiseSDK, createAssetResponse, createAssetModelResponse, - ASSET_PROPERTY_VALUE_HISTORY, + BATCH_ASSET_PROPERTY_VALUE_HISTORY, + BATCH_ASSET_PROPERTY_AGGREGATES, + BATCH_ASSET_PROPERTY_DOUBLE_VALUE, } from '@iot-app-kit/source-iotsitewise'; 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 batchGetAssetPropertyValueHistory = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_VALUE_HISTORY); +const batchGetAssetPropertyAggregates = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_AGGREGATES); +const batchGetAssetPropertyValue = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_DOUBLE_VALUE); const describeAsset = jest .fn() .mockImplementation(({ assetId }) => @@ -28,5 +32,7 @@ const describeAssetModel = jest.fn().mockImplementation(({ assetModelId }) => export const mockSiteWiseSDK = createMockSiteWiseSDK({ describeAsset, describeAssetModel, - getAssetPropertyValueHistory, + batchGetAssetPropertyValueHistory, + batchGetAssetPropertyAggregates, + batchGetAssetPropertyValue, }); diff --git a/packages/components/src/testing/testing-ground/siteWiseQueries.ts b/packages/components/src/testing/testing-ground/siteWiseQueries.ts index edc19c3ec..1ec32b90d 100644 --- a/packages/components/src/testing/testing-ground/siteWiseQueries.ts +++ b/packages/components/src/testing/testing-ground/siteWiseQueries.ts @@ -1,18 +1,30 @@ -const STRING_ASSET_ID = 'f2f74fa8-625a-435f-b89c-d27b2d84f45b'; +const STRING_ASSET_ID = 'fa94ab3e-d02f-4c50-88e1-f017c9069c4d'; -export const DEMO_TURBINE_ASSET_1 = '08654543-acb0-403d-96d4-eee30b89d7b4'; -export const DEMO_TURBINE_ASSET_1_PROPERTY_1 = 'e07aed33-334a-41bf-9589-db1e6b2655ca'; -export const DEMO_TURBINE_ASSET_1_PROPERTY_2 = 'a94f4a39-4fd1-4c5b-a466-5e1c7a8c0a53'; -export const DEMO_TURBINE_ASSET_1_PROPERTY_3 = 'f167c24f-3e35-42b4-b493-3b13fe4fbf79'; -export const DEMO_TURBINE_ASSET_1_PROPERTY_4 = 'd8937b65-5f03-4e40-93ac-c5513420ade7'; +export const DEMO_TURBINE_ASSET_1 = 'fa94ab3e-d02f-4c50-88e1-f017c9069c4d'; +export const DEMO_TURBINE_ASSET_1_PROPERTY_1 = 'f63861af-251e-4d0b-a32e-4d2089d35b1c'; +export const DEMO_TURBINE_ASSET_1_PROPERTY_2 = 'ea55054e-55d0-4eda-8359-2cbc19d52a3d'; +export const DEMO_TURBINE_ASSET_1_PROPERTY_3 = '18faca18-a8e5-4e48-9153-462c54980869'; +export const DEMO_TURBINE_ASSET_1_PROPERTY_4 = '95648016-38d0-45e7-8f53-872404b9b471'; + +export const DEMO_TURBINE_ASSET_2 = 'e6fb533c-4919-4981-a71f-7764ccc10867'; +export const DEMO_TURBINE_ASSET_2_PROPERTY_1 = 'f63861af-251e-4d0b-a32e-4d2089d35b1c'; +export const DEMO_TURBINE_ASSET_2_PROPERTY_2 = 'ea55054e-55d0-4eda-8359-2cbc19d52a3d'; +export const DEMO_TURBINE_ASSET_2_PROPERTY_3 = '18faca18-a8e5-4e48-9153-462c54980869'; +export const DEMO_TURBINE_ASSET_2_PROPERTY_4 = '95648016-38d0-45e7-8f53-872404b9b471'; + +export const DEMO_TURBINE_ASSET_3 = 'bc86bf78-c506-460c-8602-0c534356892a'; +export const DEMO_TURBINE_ASSET_3_PROPERTY_1 = 'f63861af-251e-4d0b-a32e-4d2089d35b1c'; +export const DEMO_TURBINE_ASSET_3_PROPERTY_2 = 'ea55054e-55d0-4eda-8359-2cbc19d52a3d'; +export const DEMO_TURBINE_ASSET_3_PROPERTY_3 = '18faca18-a8e5-4e48-9153-462c54980869'; +export const DEMO_TURBINE_ASSET_3_PROPERTY_4 = '95648016-38d0-45e7-8f53-872404b9b471'; export const ASSET_DETAILS_QUERY = { assetId: STRING_ASSET_ID, }; const AGGREGATED_DATA_ASSET = STRING_ASSET_ID; -const AGGREGATED_DATA_PROPERTY = 'd0dc79be-0dc2-418c-ac23-26f33cdb4b8b'; -const AGGREGATED_DATA_PROPERTY_2 = '69607dc2-5fbe-416d-aac2-0382018626e4'; +const AGGREGATED_DATA_PROPERTY = 'b1616ab4-7526-4c0a-85e2-a137cf57d668'; +const AGGREGATED_DATA_PROPERTY_2 = '729479fb-8720-4a70-bd55-2a9f9eaaa32e4'; export const AGGREGATED_DATA_QUERY = { assets: [ diff --git a/packages/components/src/testing/testing-ground/testing-ground.tsx b/packages/components/src/testing/testing-ground/testing-ground.tsx index d167dfdb7..5f2293996 100755 --- a/packages/components/src/testing/testing-ground/testing-ground.tsx +++ b/packages/components/src/testing/testing-ground/testing-ground.tsx @@ -5,6 +5,18 @@ import { DEMO_TURBINE_ASSET_1, DEMO_TURBINE_ASSET_1_PROPERTY_1, DEMO_TURBINE_ASSET_1_PROPERTY_2, + DEMO_TURBINE_ASSET_1_PROPERTY_3, + DEMO_TURBINE_ASSET_1_PROPERTY_4, + DEMO_TURBINE_ASSET_2, + DEMO_TURBINE_ASSET_2_PROPERTY_1, + DEMO_TURBINE_ASSET_2_PROPERTY_2, + DEMO_TURBINE_ASSET_2_PROPERTY_3, + DEMO_TURBINE_ASSET_2_PROPERTY_4, + DEMO_TURBINE_ASSET_3, + DEMO_TURBINE_ASSET_3_PROPERTY_1, + DEMO_TURBINE_ASSET_3_PROPERTY_2, + DEMO_TURBINE_ASSET_3_PROPERTY_3, + DEMO_TURBINE_ASSET_3_PROPERTY_4, } from './siteWiseQueries'; import { getEnvCredentials } from './getEnvCredentials'; @@ -26,28 +38,14 @@ export class TestingGround { private query: SiteWiseQuery; componentWillLoad() { - const { query } = initialize({ awsCredentials: getEnvCredentials(), awsRegion: 'us-west-2' }); + const { query } = initialize({ + awsCredentials: getEnvCredentials(), + awsRegion: 'us-west-2', + settings: { batchDuration: undefined, legacyAPI: false }, + }); this.query = query; } - private changeResolution = (ev: Event) => { - const resolution = (ev.target as HTMLSelectElement)?.value; - - 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 (
@@ -56,32 +54,111 @@ export class TestingGround {

- - + Promise.reject(new Error('Mock method not override.')); @@ -29,6 +35,9 @@ export const createMockSiteWiseSDK = ({ getAssetPropertyAggregates = nonOverriddenMock, getAssetPropertyValueHistory = nonOverriddenMock, getInterpolatedAssetPropertyValues = nonOverriddenMock, + batchGetAssetPropertyValueHistory = nonOverriddenMock, + batchGetAssetPropertyAggregates = nonOverriddenMock, + batchGetAssetPropertyValue = nonOverriddenMock, }: { listAssets?: (input: ListAssetsCommandInput) => Promise; listAssociatedAssets?: (input: ListAssociatedAssetsCommandInput) => Promise; @@ -44,6 +53,15 @@ export const createMockSiteWiseSDK = ({ getInterpolatedAssetPropertyValues?: ( input: GetInterpolatedAssetPropertyValuesCommandInput ) => Promise; + batchGetAssetPropertyValueHistory?: ( + input: BatchGetAssetPropertyValueHistoryCommandInput + ) => Promise; + batchGetAssetPropertyAggregates?: ( + input: BatchGetAssetPropertyAggregatesCommandInput + ) => Promise; + batchGetAssetPropertyValue?: ( + input: BatchGetAssetPropertyValueCommandInput + ) => Promise; } = {}) => ({ send: (command: { input: any }) => { @@ -68,6 +86,12 @@ export const createMockSiteWiseSDK = ({ return getAssetPropertyValueHistory(command.input); case 'GetInterpolatedAssetPropertyValuesCommand': return getInterpolatedAssetPropertyValues(command.input); + case 'BatchGetAssetPropertyValueHistoryCommand': + return batchGetAssetPropertyValueHistory(command.input); + case 'BatchGetAssetPropertyAggregatesCommand': + return batchGetAssetPropertyAggregates(command.input); + case 'BatchGetAssetPropertyValueCommand': + return batchGetAssetPropertyValue(command.input); default: throw new Error( `missing mock implementation for command name ${commandName}. Add a new command within the mock SiteWise SDK.` diff --git a/packages/core/src/data-module/data-cache/dataReducer.spec.ts b/packages/core/src/data-module/data-cache/dataReducer.spec.ts index 194202af7..59b5ffc8c 100755 --- a/packages/core/src/data-module/data-cache/dataReducer.spec.ts +++ b/packages/core/src/data-module/data-cache/dataReducer.spec.ts @@ -482,6 +482,70 @@ it('sets the data with the correct cache intervals when a success action occurs ); }); +it('sets the data with the correct cache intervals when a success action occurs with fetchMostRecentBeforeEnd', () => { + const ID = 'my-id'; + const RESOLUTION = SECOND_IN_MS; + + const INITIAL_STATE = { + [ID]: { + [RESOLUTION]: { + id: ID, + resolution: RESOLUTION, + isLoading: true, + isRefreshing: true, + requestHistory: [], + dataCache: EMPTY_CACHE, + requestCache: EMPTY_CACHE, + }, + }, + }; + + const newDataPoints = [{ x: DATE_BEFORE.getTime(), y: 100 }]; + + const DATA: DataStream = { + id: ID, + name: 'some name', + resolution: RESOLUTION, + aggregates: { + [RESOLUTION]: newDataPoints, + }, + data: [], + dataType: DataType.NUMBER, + }; + const newState = dataReducer( + INITIAL_STATE, + onSuccessAction(ID, DATA, FIRST_DATE, LAST_DATE, { + id: ID, + resolution: '1s', + start: FIRST_DATE, + end: LAST_DATE, + fetchMostRecentBeforeEnd: true, + }) + ); + expect(newState?.[ID]?.[RESOLUTION]).toEqual( + expect.objectContaining({ + id: ID, + resolution: RESOLUTION, + error: undefined, + isLoading: false, + requestHistory: [ + expect.objectContaining({ + end: expect.any(Date), + requestedAt: expect.any(Date), + start: expect.any(Date), + }), + ], + dataCache: { + intervals: [[DATE_BEFORE.getTime(), LAST_DATE.getTime()]], + items: [newDataPoints], + }, + requestCache: expect.objectContaining({ + intervals: [[DATE_BEFORE.getTime(), LAST_DATE.getTime()]], + }), + }) + ); +}); + it('sets the data with the correct cache intervals when a success action occurs with fetchMostRecentBeforeStart if no data is returned', () => { const ID = 'my-id'; const RESOLUTION = SECOND_IN_MS; @@ -541,6 +605,65 @@ it('sets the data with the correct cache intervals when a success action occurs ); }); +it('sets the data with the correct cache intervals when a success action occurs with fetchMostRecentBeforeEnd if no data is returned', () => { + const ID = 'my-id'; + const RESOLUTION = SECOND_IN_MS; + + const INITIAL_STATE = { + [ID]: { + [RESOLUTION]: { + id: ID, + resolution: RESOLUTION, + isLoading: true, + isRefreshing: true, + requestHistory: [], + dataCache: EMPTY_CACHE, + requestCache: EMPTY_CACHE, + }, + }, + }; + + const DATA: DataStream = { + id: ID, + name: 'some name', + resolution: RESOLUTION, + data: [], + dataType: DataType.NUMBER, + }; + const newState = dataReducer( + INITIAL_STATE, + onSuccessAction(ID, DATA, FIRST_DATE, LAST_DATE, { + id: ID, + resolution: '1s', + start: FIRST_DATE, + end: LAST_DATE, + fetchMostRecentBeforeEnd: true, + }) + ); + expect(newState?.[ID]?.[RESOLUTION]).toEqual( + expect.objectContaining({ + id: ID, + resolution: RESOLUTION, + error: undefined, + isLoading: false, + requestHistory: [ + expect.objectContaining({ + end: expect.any(Date), + requestedAt: expect.any(Date), + start: expect.any(Date), + }), + ], + dataCache: { + intervals: [[FIRST_DATE.getTime(), LAST_DATE.getTime()]], + items: [[]], + }, + requestCache: expect.objectContaining({ + intervals: [[FIRST_DATE.getTime(), LAST_DATE.getTime()]], + }), + }) + ); +}); + it('merges data into existing data cache', () => { const ID = 'my-id'; @@ -614,7 +737,6 @@ it('merges data into existing data cache', () => { resolution: '1s', start: START_DATE_1, end: END_DATE_1, - fetchMostRecentBeforeEnd: true, }) ); diff --git a/packages/core/src/data-module/data-cache/dataReducer.ts b/packages/core/src/data-module/data-cache/dataReducer.ts index 5920177fd..1222db58e 100755 --- a/packages/core/src/data-module/data-cache/dataReducer.ts +++ b/packages/core/src/data-module/data-cache/dataReducer.ts @@ -82,7 +82,10 @@ export const dataReducer: Reducer = ( // start the interval from the returned data point to avoid over-caching // if there is no data point it's fine to cache the entire interval - if (requestInformation.fetchMostRecentBeforeStart && sortedData.length > 0) { + if ( + (requestInformation.fetchMostRecentBeforeStart || requestInformation.fetchMostRecentBeforeEnd) && + sortedData.length > 0 + ) { intervalStart = new Date(sortedData[0].x); } diff --git a/packages/source-iotsitewise/jest.config.js b/packages/source-iotsitewise/jest.config.js index 6cf8ff5d8..86eed58a7 100644 --- a/packages/source-iotsitewise/jest.config.js +++ b/packages/source-iotsitewise/jest.config.js @@ -4,7 +4,7 @@ module.exports = { testEnvironment: 'node', setupFilesAfterEnv: ['jest-extended/all'], collectCoverageFrom: ['src/**/*.{ts,tsx}'], - coveragePathIgnorePatterns: ['/src/__mocks__'], + coveragePathIgnorePatterns: ['/src/__mocks__', 'src/time-series-data/client/legacy'], testPathIgnorePatterns: ['/dist'], coverageReporters: ['text-summary', 'cobertura', 'html', 'json', 'json-summary'], moduleNameMapper: { diff --git a/packages/source-iotsitewise/package.json b/packages/source-iotsitewise/package.json index 4c97afe3c..b2be470c8 100644 --- a/packages/source-iotsitewise/package.json +++ b/packages/source-iotsitewise/package.json @@ -37,10 +37,11 @@ "pack": "yarn pack" }, "dependencies": { - "@aws-sdk/client-iotsitewise": "^3.39.0", + "@aws-sdk/client-iotsitewise": "^3.87.0", "@iot-app-kit/core": "^1.5.0", "@rollup/plugin-typescript": "^8.3.0", "@synchro-charts/core": "^5.0.0", + "dataloader": "^2.1.0", "flush-promises": "^1.0.2", "rxjs": "^7.4.0", "typescript": "4.4.4" diff --git a/packages/source-iotsitewise/src/__mocks__/assetPropertyValue.ts b/packages/source-iotsitewise/src/__mocks__/assetPropertyValue.ts index f0b899db4..71ec5e643 100644 --- a/packages/source-iotsitewise/src/__mocks__/assetPropertyValue.ts +++ b/packages/source-iotsitewise/src/__mocks__/assetPropertyValue.ts @@ -3,6 +3,9 @@ import { GetAssetPropertyAggregatesResponse, GetAssetPropertyValueHistoryResponse, GetAssetPropertyValueResponse, + BatchGetAssetPropertyValueHistoryResponse, + BatchGetAssetPropertyAggregatesResponse, + BatchGetAssetPropertyValueResponse, Quality, } from '@aws-sdk/client-iotsitewise'; @@ -48,6 +51,90 @@ export const ASSET_PROPERTY_VALUE_HISTORY: GetAssetPropertyValueHistoryResponse ], }; +export const BATCH_ASSET_PROPERTY_VALUE_HISTORY: BatchGetAssetPropertyValueHistoryResponse = { + successEntries: [ + { + entryId: '0-0', + assetPropertyValueHistory: [ + { + value: { + doubleValue: 10.123, + }, + timestamp: { + timeInSeconds: 1000, + offsetInNanos: 99000004, + }, + }, + { + value: { + doubleValue: 12.01, + }, + timestamp: { + timeInSeconds: 2000, + offsetInNanos: 0, + }, + }, + ], + }, + { + entryId: '0-1', + assetPropertyValueHistory: [ + { + value: { + doubleValue: 10.123, + }, + timestamp: { + timeInSeconds: 1000, + offsetInNanos: 99000004, + }, + }, + ], + }, + { + entryId: '1-0', + assetPropertyValueHistory: [ + { + value: { + doubleValue: 10.123, + }, + timestamp: { + timeInSeconds: 1000, + offsetInNanos: 99000004, + }, + }, + ], + }, + { + entryId: '1-1', + assetPropertyValueHistory: [ + { + value: { + doubleValue: 10.123, + }, + timestamp: { + timeInSeconds: 1000, + offsetInNanos: 99000004, + }, + }, + ], + }, + ], + errorEntries: [], + skippedEntries: [], +}; + +export const BATCH_ASSET_PROPERTY_ERROR_ENTRY = { + entryId: '0-0', + errorMessage: 'assetId 1 not found', + errorCode: '404', +}; + +export const BATCH_ASSET_PROPERTY_ERROR: BatchGetAssetPropertyValueHistoryResponse = { + successEntries: [], + errorEntries: [BATCH_ASSET_PROPERTY_ERROR_ENTRY], + skippedEntries: [], +}; + export const AGGREGATE_VALUES: GetAssetPropertyAggregatesResponse = { aggregatedValues: [ { @@ -71,6 +158,60 @@ export const AGGREGATE_VALUES: GetAssetPropertyAggregatesResponse = { ], }; +export const BATCH_ASSET_PROPERTY_AGGREGATES: BatchGetAssetPropertyAggregatesResponse = { + successEntries: [ + { + entryId: '0-0', + ...AGGREGATE_VALUES, + }, + { + entryId: '0-1', + ...AGGREGATE_VALUES, + }, + { + entryId: '1-0', + ...AGGREGATE_VALUES, + }, + { + entryId: '1-1', + ...AGGREGATE_VALUES, + }, + ], + errorEntries: [], + skippedEntries: [], +}; + +export const BATCH_ASSET_PROPERTY_DOUBLE_VALUE: BatchGetAssetPropertyValueResponse = { + successEntries: [ + { + entryId: '0-0', + assetPropertyValue: { + value: { + doubleValue: 10.123, + }, + timestamp: { + timeInSeconds: 1000, + offsetInNanos: 99000004, + }, + }, + }, + { + entryId: '0-1', + assetPropertyValue: { + value: { + doubleValue: 10.123, + }, + timestamp: { + timeInSeconds: 1000, + offsetInNanos: 99000004, + }, + }, + }, + ], + errorEntries: [], + skippedEntries: [], +}; + export const ASSET_PROPERTY_DOUBLE_VALUE: GetAssetPropertyValueResponse = { propertyValue: { value: { diff --git a/packages/source-iotsitewise/src/__mocks__/iotsitewiseSDK.ts b/packages/source-iotsitewise/src/__mocks__/iotsitewiseSDK.ts index cf2d8503b..1af909fa0 100644 --- a/packages/source-iotsitewise/src/__mocks__/iotsitewiseSDK.ts +++ b/packages/source-iotsitewise/src/__mocks__/iotsitewiseSDK.ts @@ -11,11 +11,17 @@ import { GetAssetPropertyValueResponse, GetInterpolatedAssetPropertyValuesCommandInput, GetInterpolatedAssetPropertyValuesResponse, + BatchGetAssetPropertyValueHistoryCommandInput, + BatchGetAssetPropertyValueHistoryResponse, + BatchGetAssetPropertyAggregatesCommandInput, + BatchGetAssetPropertyAggregatesResponse, + BatchGetAssetPropertyValueCommandInput, + BatchGetAssetPropertyValueResponse, IoTSiteWiseClient, ListAssetsCommandInput, ListAssociatedAssetsCommandInput, ListAssociatedAssetsResponse, - ListAssetsResponse + ListAssetsResponse, } from '@aws-sdk/client-iotsitewise'; const nonOverriddenMock = () => Promise.reject(new Error('Mock method not override.')); @@ -29,6 +35,9 @@ export const createMockSiteWiseSDK = ({ getAssetPropertyAggregates = nonOverriddenMock, getAssetPropertyValueHistory = nonOverriddenMock, getInterpolatedAssetPropertyValues = nonOverriddenMock, + batchGetAssetPropertyValueHistory = nonOverriddenMock, + batchGetAssetPropertyAggregates = nonOverriddenMock, + batchGetAssetPropertyValue = nonOverriddenMock, }: { listAssets?: (input: ListAssetsCommandInput) => Promise; listAssociatedAssets?: (input: ListAssociatedAssetsCommandInput) => Promise; @@ -44,6 +53,15 @@ export const createMockSiteWiseSDK = ({ getInterpolatedAssetPropertyValues?: ( input: GetInterpolatedAssetPropertyValuesCommandInput ) => Promise; + batchGetAssetPropertyValueHistory?: ( + input: BatchGetAssetPropertyValueHistoryCommandInput + ) => Promise; + batchGetAssetPropertyAggregates?: ( + input: BatchGetAssetPropertyAggregatesCommandInput + ) => Promise; + batchGetAssetPropertyValue?: ( + input: BatchGetAssetPropertyValueCommandInput + ) => Promise; } = {}) => ({ send: (command: { input: any }) => { @@ -68,6 +86,12 @@ export const createMockSiteWiseSDK = ({ return getAssetPropertyValueHistory(command.input); case 'GetInterpolatedAssetPropertyValuesCommand': return getInterpolatedAssetPropertyValues(command.input); + case 'BatchGetAssetPropertyValueHistoryCommand': + return batchGetAssetPropertyValueHistory(command.input); + case 'BatchGetAssetPropertyAggregatesCommand': + return batchGetAssetPropertyAggregates(command.input); + case 'BatchGetAssetPropertyValueCommand': + return batchGetAssetPropertyValue(command.input); default: throw new Error( `missing mock implementation for command name ${commandName}. Add a new command within the mock SiteWise SDK.` diff --git a/packages/source-iotsitewise/src/initialize.ts b/packages/source-iotsitewise/src/initialize.ts index 37f6a6854..1b9dff427 100644 --- a/packages/source-iotsitewise/src/initialize.ts +++ b/packages/source-iotsitewise/src/initialize.ts @@ -1,6 +1,6 @@ import { SiteWiseTimeSeriesDataProvider } from './time-series-data/provider'; import { IotAppKitDataModule, TreeQuery, TimeQuery, TimeSeriesData, TimeSeriesDataRequest } from '@iot-app-kit/core'; -import { SiteWiseAssetQuery } from './time-series-data/types'; +import { SiteWiseAssetQuery, SiteWiseDataSourceSettings } from './time-series-data/types'; import { BranchReference, RootedSiteWiseAssetTreeQueryArguments, @@ -18,7 +18,7 @@ import { Credentials, Provider as AWSCredentialsProvider } from '@aws-sdk/types' import { IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; import { assetSession } from './sessions'; -type IoTAppKitInitInputs = +type SiteWiseDataSourceInitInputs = ( | { registerDataSources?: boolean; iotSiteWiseClient: IoTSiteWiseClient; @@ -27,7 +27,10 @@ type IoTAppKitInitInputs = registerDataSources?: boolean; awsCredentials: Credentials | AWSCredentialsProvider; awsRegion: string; - }; + } +) & { + settings?: SiteWiseDataSourceSettings; +}; export type SiteWiseQuery = { timeSeriesData: (query: SiteWiseAssetQuery) => TimeQuery; @@ -43,7 +46,7 @@ export type SiteWiseQuery = { * @param awsCredentials - https://www.npmjs.com/package/@aws-sdk/credential-providers * @param awsRegion - Region for AWS based data sources to point towards, i.e. us-east-1 */ -export const initialize = (input: IoTAppKitInitInputs) => { +export const initialize = (input: SiteWiseDataSourceInitInputs) => { const siteWiseTimeSeriesModule = new IotAppKitDataModule(); const siteWiseSdk = 'iotSiteWiseClient' in input ? input.iotSiteWiseClient : sitewiseSdk(input.awsCredentials, input.awsRegion); @@ -53,7 +56,7 @@ export const initialize = (input: IoTAppKitInitInputs) => { if (input.registerDataSources !== false) { /** Automatically registered data sources */ - siteWiseTimeSeriesModule.registerDataSource(createDataSource(siteWiseSdk)); + siteWiseTimeSeriesModule.registerDataSource(createDataSource(siteWiseSdk, input.settings)); } return { diff --git a/packages/source-iotsitewise/src/time-series-data/client/batch.spec.ts b/packages/source-iotsitewise/src/time-series-data/client/batch.spec.ts new file mode 100644 index 000000000..bcf6c3ed3 --- /dev/null +++ b/packages/source-iotsitewise/src/time-series-data/client/batch.spec.ts @@ -0,0 +1,149 @@ +import { + createEntryBatches, + calculateNextBatchSize, + shouldFetchNextBatch, + MAX_BATCH_RESULTS, + MAX_BATCH_ENTRIES, + NO_LIMIT_BATCH, +} from './batch'; + +describe('createEntryBatches', () => { + it('buckets entries by maxResults for a given batch', () => { + const batches = createEntryBatches([ + { + id: '1', + maxResults: undefined, + }, + { + id: '2', + maxResults: 2000, + }, + { + id: '3', + maxResults: 2000, + }, + { + id: '4', + maxResults: 10, + }, + { + id: '5', + maxResults: 2000, + }, + ]); + + expect(batches).toEqual( + expect.arrayContaining([ + [ + [ + { + id: '1', + maxResults: undefined, + }, + ], + NO_LIMIT_BATCH, + ], + [ + [ + { + id: '2', + maxResults: 2000, + }, + { + id: '3', + maxResults: 2000, + }, + { + id: '5', + maxResults: 2000, + }, + ], + 6000, + ], + [ + [ + { + id: '4', + maxResults: 10, + }, + ], + 10, + ], + ]) + ); + }); + + it('chunks batches that exceed max entry size (16)', () => { + const entrySize = 2000; + + const entries = [ + ...[...Array(MAX_BATCH_ENTRIES * 3)].map((args, index) => ({ + id: String(index), + maxResults: entrySize, + })), + { + id: 'abc', + maxResults: 10, + }, + ]; + + const batches = createEntryBatches(entries); + + expect(batches).toEqual( + expect.arrayContaining([ + [entries.slice(0, MAX_BATCH_ENTRIES), MAX_BATCH_ENTRIES * entrySize], + [entries.slice(MAX_BATCH_ENTRIES, 2 * MAX_BATCH_ENTRIES), MAX_BATCH_ENTRIES * entrySize], + [entries.slice(MAX_BATCH_ENTRIES * 2, MAX_BATCH_ENTRIES * 3), MAX_BATCH_ENTRIES * entrySize], + [[entries[MAX_BATCH_ENTRIES * 3]], 10], + ]) + ); + }); + + it('handles empty input', () => { + const batches = createEntryBatches([]); + expect(batches).toEqual([]); + }); +}); + +describe('calculateNextBatchSize', () => { + it('returns the correct max batch size for no limit batches', () => { + expect(calculateNextBatchSize({ maxResults: NO_LIMIT_BATCH, dataPointsFetched: 0 })).toBe(MAX_BATCH_RESULTS); + expect(calculateNextBatchSize({ maxResults: NO_LIMIT_BATCH, dataPointsFetched: 100000 })).toBe(MAX_BATCH_RESULTS); + }); + + it('returns the correct max batch size when specified and need to fetch more than MAX_BATCH_SIZE', () => { + expect(calculateNextBatchSize({ maxResults: MAX_BATCH_RESULTS * 3, dataPointsFetched: 0 })).toBe(MAX_BATCH_RESULTS); + }); + + it('returns the correct max batch size when specified and need to fetch less than MAX_BATCH SIZE', () => { + expect(calculateNextBatchSize({ maxResults: MAX_BATCH_RESULTS / 2, dataPointsFetched: 0 })).toBe( + MAX_BATCH_RESULTS / 2 + ); + expect(calculateNextBatchSize({ maxResults: MAX_BATCH_RESULTS, dataPointsFetched: MAX_BATCH_RESULTS / 2 })).toBe( + MAX_BATCH_RESULTS / 2 + ); + }); +}); + +describe('shouldFetchNextBatch', () => { + it('returns true if next token exists and batch has no limit', () => { + expect(shouldFetchNextBatch({ nextToken: '123', maxResults: NO_LIMIT_BATCH, dataPointsFetched: 0 })).toBe(true); + expect(shouldFetchNextBatch({ nextToken: '123', maxResults: NO_LIMIT_BATCH, dataPointsFetched: 500000 })).toBe( + true + ); + }); + + it('returns true if next token exists and there is still data that needs to be fetched', () => { + expect(shouldFetchNextBatch({ nextToken: '123', maxResults: 3000, dataPointsFetched: 0 })).toBe(true); + expect(shouldFetchNextBatch({ nextToken: '123', maxResults: 10000, dataPointsFetched: 9999 })).toBe(true); + }); + + it('returns false if next token exists but data points have already been fetched', () => { + expect(shouldFetchNextBatch({ nextToken: '123', maxResults: 3000, dataPointsFetched: 3000 })).toBe(false); + expect(shouldFetchNextBatch({ nextToken: '123', maxResults: 0, dataPointsFetched: 0 })).toBe(false); + }); + + it('returns false if next token does not exist', () => { + expect(shouldFetchNextBatch({ nextToken: undefined, maxResults: 3000, dataPointsFetched: 0 })).toBe(false); + }); +}); diff --git a/packages/source-iotsitewise/src/time-series-data/client/batch.ts b/packages/source-iotsitewise/src/time-series-data/client/batch.ts new file mode 100644 index 000000000..28e7ec3e7 --- /dev/null +++ b/packages/source-iotsitewise/src/time-series-data/client/batch.ts @@ -0,0 +1,82 @@ +// current maximum batch size when using batch APIs +export const MAX_BATCH_RESULTS = 4000; + +// current batch API entry limit +export const MAX_BATCH_ENTRIES = 16; + +// use -1 to represent a batch with no max result limit +export const NO_LIMIT_BATCH = -1; + +/** + * bucket entries by maxResults, chunk buckets if required. + * entries[] => [[entries, -1], [entries, 1000], [entries, 16]] + * + * @param entries + * @returns buckets: [BatchHistoricalEntry[], number | undefined][] + */ +export const createEntryBatches = (entries: T[]): [T[], number][] => { + const buckets: { [key: number]: T[] } = {}; + + entries.forEach((entry) => { + const maxEntryResults = entry.maxResults || NO_LIMIT_BATCH; + + if (buckets[maxEntryResults]) { + buckets[maxEntryResults] = buckets[maxEntryResults].concat([entry]); + } else { + buckets[maxEntryResults] = [entry]; + } + }); + + // chunk buckets that are larger than MAX_BATCH_ENTRIES + return Object.keys(buckets) + .map((key) => { + const maxEntryResults = Number(key); + const bucket = buckets[maxEntryResults]; + + return chunkBatch(bucket).map((chunk): [T[], number] => [ + chunk, + maxEntryResults === NO_LIMIT_BATCH ? NO_LIMIT_BATCH : chunk.length * maxEntryResults, + ]); + }) + .flat(); +}; + +/** + * calculate the required size of the next batch + */ +export const calculateNextBatchSize = ({ + maxResults, + dataPointsFetched, +}: { + maxResults: number; + dataPointsFetched: number; +}) => (maxResults === NO_LIMIT_BATCH ? MAX_BATCH_RESULTS : Math.min(maxResults - dataPointsFetched, MAX_BATCH_RESULTS)); + +/** + * check if batch still needs to be paginated. + */ +export const shouldFetchNextBatch = ({ + nextToken, + maxResults, + dataPointsFetched, +}: { + nextToken: string | undefined; + maxResults: number; + dataPointsFetched?: number; +}) => + !!nextToken && + (maxResults === NO_LIMIT_BATCH || + (dataPointsFetched !== null && dataPointsFetched !== undefined && dataPointsFetched < maxResults)); + +/** + * chunk batches by MAX_BATCH_ENTRIES + */ +const chunkBatch = (batch: T[]): T[][] => { + const chunks = []; + + for (let i = 0; i < batch.length; i += MAX_BATCH_ENTRIES) { + chunks.push(batch.slice(i, i + MAX_BATCH_ENTRIES)); + } + + return chunks; +}; diff --git a/packages/source-iotsitewise/src/time-series-data/client/batchGetAggregatedPropertyDataPoints.ts b/packages/source-iotsitewise/src/time-series-data/client/batchGetAggregatedPropertyDataPoints.ts new file mode 100644 index 000000000..00ea89192 --- /dev/null +++ b/packages/source-iotsitewise/src/time-series-data/client/batchGetAggregatedPropertyDataPoints.ts @@ -0,0 +1,184 @@ +import { + AggregateType, + BatchGetAssetPropertyAggregatesCommand, + BatchGetAssetPropertyAggregatesErrorEntry, + BatchGetAssetPropertyAggregatesSuccessEntry, + IoTSiteWiseClient, + TimeOrdering, +} from '@aws-sdk/client-iotsitewise'; +import { aggregateToDataPoint } from '../util/toDataPoint'; +import { dataStreamFromSiteWise } from '../dataStreamFromSiteWise'; +import { OnSuccessCallback, ErrorCallback, RequestInformationAndRange, parseDuration } from '@iot-app-kit/core'; +import { toSiteWiseAssetProperty } from '../util/dataStreamId'; +import { isDefined } from '../../common/predicates'; +import { AggregatedPropertyParams } from './client'; +import { createEntryBatches, calculateNextBatchSize, shouldFetchNextBatch } from './batch'; +import { RESOLUTION_TO_MS_MAPPING } from '../util/resolution'; + +type BatchAggregatedEntry = { + requestInformation: RequestInformationAndRange; + aggregateTypes: AggregateType[]; + maxResults?: number; + onError: ErrorCallback; + onSuccess: OnSuccessCallback; + requestStart: Date; + requestEnd: Date; +}; + +type BatchEntryCallbackCache = { + [key: string]: { + onError: (entry: BatchGetAssetPropertyAggregatesErrorEntry) => void; + onSuccess: (entry: BatchGetAssetPropertyAggregatesSuccessEntry) => void; + }; +}; + +const sendRequest = ({ + client, + batch, + maxResults, + requestIndex, // used to create and regenerate (for paginating) a unique entryId + nextToken: prevToken, + dataPointsFetched = 0, // track number of data points fetched so far +}: { + client: IoTSiteWiseClient; + batch: BatchAggregatedEntry[]; + maxResults: number; + requestIndex: number; + nextToken?: string; + dataPointsFetched?: number; +}) => { + // callback cache makes it convenient to capture request data in a closure. + // the cache exposes methods that only require batch response entry as an argument. + const callbackCache: BatchEntryCallbackCache = {}; + + const batchSize = calculateNextBatchSize({ maxResults, dataPointsFetched }); + + client + .send( + new BatchGetAssetPropertyAggregatesCommand({ + entries: batch.map((entry, entryIndex) => { + const { requestInformation, aggregateTypes, onError, onSuccess, requestStart, requestEnd } = entry; + const { id, resolution } = requestInformation; + + // use 2D array indices as entryIDs to guarantee uniqueness + // entryId is used to map batch entries with the appropriate callback + const entryId = String(`${requestIndex}-${entryIndex}`); + + // save request entry data in functional closure. + callbackCache[entryId] = { + onError: ({ errorMessage: msg = 'batch aggregate error', errorCode: status }) => { + onError({ + id, + resolution: parseDuration(resolution), + error: { msg, status }, + }); + }, + onSuccess: ({ aggregatedValues }) => { + if (aggregatedValues) { + onSuccess( + [ + dataStreamFromSiteWise({ + ...toSiteWiseAssetProperty(id), + dataPoints: aggregatedValues + .map((aggregatedValue) => aggregateToDataPoint(aggregatedValue)) + .filter(isDefined), + resolution: RESOLUTION_TO_MS_MAPPING[resolution], + }), + ], + requestInformation, + requestStart, + requestEnd + ); + } + }, + }; + + // BatchGetAssetPropertyValueAggregatesEntry + return { + ...toSiteWiseAssetProperty(requestInformation.id), + aggregateTypes, + resolution, + startDate: requestStart, + endDate: requestEnd, + entryId, + timeOrdering: TimeOrdering.DESCENDING, + }; + }), + maxResults: batchSize, + nextToken: prevToken, + }) + ) + .then((response) => { + const { errorEntries, successEntries, nextToken } = response; + + // execute the correct callback for each entry + // empty entries and entries that don't exist in the cache are ignored. + // TODO: implement retries for retry-able batch errors + errorEntries?.forEach((entry) => entry.entryId && callbackCache[entry.entryId]?.onError(entry)); + successEntries?.forEach((entry) => entry.entryId && callbackCache[entry.entryId]?.onSuccess(entry)); + + // increment number of data points fetched + dataPointsFetched += batchSize; + + if (shouldFetchNextBatch({ nextToken, maxResults, dataPointsFetched })) { + sendRequest({ + client, + batch, + maxResults, + requestIndex, + nextToken, + dataPointsFetched, + }); + } + }); +}; + +const batchGetAggregatedPropertyDataPointsForProperty = ({ + client, + entries, +}: { + client: IoTSiteWiseClient; + entries: BatchAggregatedEntry[]; +}) => + createEntryBatches(entries) + .filter((batch) => batch.length > 0) // filter out empty batches + .map(([batch, maxResults], requestIndex) => sendRequest({ client, batch, maxResults, requestIndex })); + +export const batchGetAggregatedPropertyDataPoints = ({ + params, + client, +}: { + params: AggregatedPropertyParams[]; + client: IoTSiteWiseClient; +}) => { + const entries: BatchAggregatedEntry[] = []; + + // fan out params into individual entries, handling fetchMostRecentBeforeStart + params.forEach(({ requestInformations, maxResults, onSuccess, onError, aggregateTypes }) => { + requestInformations + .filter(({ resolution }) => resolution !== '0') + .forEach((requestInformation) => { + const { fetchMostRecentBeforeStart, start, end } = requestInformation; + + entries.push({ + requestInformation, + aggregateTypes, + maxResults: fetchMostRecentBeforeStart ? 1 : maxResults, + onSuccess, + onError, + requestStart: fetchMostRecentBeforeStart ? new Date(0, 0, 0) : start, + requestEnd: fetchMostRecentBeforeStart ? start : end, + }); + }); + }); + + // sort entries to ensure earliest data is fetched first because batch API has a property limit + entries.sort((a, b) => b.requestInformation.start.getTime() - a.requestInformation.start.getTime()); + + if (entries.length > 0) { + batchGetAggregatedPropertyDataPointsForProperty({ + entries, + client, + }); + } +}; diff --git a/packages/source-iotsitewise/src/time-series-data/client/batchGetHistoricalPropertyDataPoints.ts b/packages/source-iotsitewise/src/time-series-data/client/batchGetHistoricalPropertyDataPoints.ts new file mode 100644 index 000000000..357e4c68f --- /dev/null +++ b/packages/source-iotsitewise/src/time-series-data/client/batchGetHistoricalPropertyDataPoints.ts @@ -0,0 +1,177 @@ +import { + BatchGetAssetPropertyValueHistoryCommand, + BatchGetAssetPropertyValueHistoryErrorEntry, + BatchGetAssetPropertyValueHistorySuccessEntry, + IoTSiteWiseClient, + TimeOrdering, +} from '@aws-sdk/client-iotsitewise'; +import { toDataPoint } from '../util/toDataPoint'; +import { dataStreamFromSiteWise } from '../dataStreamFromSiteWise'; +import { OnSuccessCallback, ErrorCallback, RequestInformationAndRange } from '@iot-app-kit/core'; +import { toSiteWiseAssetProperty } from '../util/dataStreamId'; +import { isDefined } from '../../common/predicates'; +import { HistoricalPropertyParams } from './client'; +import { createEntryBatches, calculateNextBatchSize, shouldFetchNextBatch } from './batch'; + +type BatchHistoricalEntry = { + requestInformation: RequestInformationAndRange; + maxResults?: number; + onError: ErrorCallback; + onSuccess: OnSuccessCallback; + requestStart: Date; + requestEnd: Date; +}; + +type BatchEntryCallbackCache = { + [key: string]: { + onError: (entry: BatchGetAssetPropertyValueHistoryErrorEntry) => void; + onSuccess: (entry: BatchGetAssetPropertyValueHistorySuccessEntry) => void; + }; +}; + +const sendRequest = ({ + client, + batch, + maxResults, + requestIndex, // used to create and regenerate (for paginating) a unique entryId + nextToken: prevToken, + dataPointsFetched = 0, // track number of data points fetched so far +}: { + client: IoTSiteWiseClient; + batch: BatchHistoricalEntry[]; + maxResults: number; + requestIndex: number; + nextToken?: string; + dataPointsFetched?: number; +}) => { + // callback cache makes it convenient to capture request data in a closure. + // the cache exposes methods that only require batch response entry as an argument. + const callbackCache: BatchEntryCallbackCache = {}; + + const batchSize = calculateNextBatchSize({ maxResults, dataPointsFetched }); + + client + .send( + new BatchGetAssetPropertyValueHistoryCommand({ + entries: batch.map((entry, entryIndex) => { + const { requestInformation, onError, onSuccess, requestStart, requestEnd } = entry; + const { id } = requestInformation; + + // use 2D array indices as entryIDs to guarantee uniqueness + // entryId is used to map batch entries with the appropriate callback + const entryId = String(`${requestIndex}-${entryIndex}`); + + // save request entry data in functional closure. + callbackCache[entryId] = { + onError: ({ errorMessage: msg = 'batch historical error', errorCode: status }) => { + onError({ + id, + resolution: 0, + error: { msg, status }, + }); + }, + onSuccess: ({ assetPropertyValueHistory }) => { + if (assetPropertyValueHistory) { + onSuccess( + [ + dataStreamFromSiteWise({ + ...toSiteWiseAssetProperty(id), + dataPoints: assetPropertyValueHistory + .map((assetPropertyValue) => toDataPoint(assetPropertyValue)) + .filter(isDefined), + }), + ], + requestInformation, + requestStart, + requestEnd + ); + } + }, + }; + + // BatchGetAssetPropertyValueHistoryEntry + return { + ...toSiteWiseAssetProperty(requestInformation.id), + startDate: requestStart, + endDate: requestEnd, + entryId, + timeOrdering: TimeOrdering.DESCENDING, + }; + }), + maxResults: batchSize, + nextToken: prevToken, + }) + ) + .then((response) => { + const { errorEntries, successEntries, nextToken } = response; + + // execute the correct callback for each entry + // empty entries and entries that don't exist in the cache are ignored. + // TODO: implement retries for retry-able batch errors + errorEntries?.forEach((entry) => entry.entryId && callbackCache[entry.entryId]?.onError(entry)); + successEntries?.forEach((entry) => entry.entryId && callbackCache[entry.entryId]?.onSuccess(entry)); + + // increment number of data points fetched + dataPointsFetched += batchSize; + + if (shouldFetchNextBatch({ nextToken, maxResults, dataPointsFetched })) { + sendRequest({ + client, + batch, + maxResults, + requestIndex, + nextToken, + dataPointsFetched, + }); + } + }); +}; + +const batchGetHistoricalPropertyDataPointsForProperty = ({ + client, + entries, +}: { + client: IoTSiteWiseClient; + entries: BatchHistoricalEntry[]; +}) => + createEntryBatches(entries) + .filter((batch) => batch.length > 0) // filter out empty batches + .map(([batch, maxResults], requestIndex) => sendRequest({ client, batch, maxResults, requestIndex })); + +export const batchGetHistoricalPropertyDataPoints = ({ + params, + client, +}: { + params: HistoricalPropertyParams[]; + client: IoTSiteWiseClient; +}) => { + const entries: BatchHistoricalEntry[] = []; + + // fan out params into individual entries, handling fetchMostRecentBeforeStart + params.forEach(({ requestInformations, maxResults, onSuccess, onError }) => { + requestInformations + .filter(({ resolution }) => resolution === '0') + .forEach((requestInformation) => { + const { fetchMostRecentBeforeStart, start, end } = requestInformation; + + entries.push({ + requestInformation, + maxResults: fetchMostRecentBeforeStart ? 1 : maxResults, + onSuccess, + onError, + requestStart: fetchMostRecentBeforeStart ? new Date(0, 0, 0) : start, + requestEnd: fetchMostRecentBeforeStart ? start : end, + }); + }); + }); + + // sort entries to ensure earliest data is fetched first because batch API has a property limit + entries.sort((a, b) => b.requestInformation.start.getTime() - a.requestInformation.start.getTime()); + + if (entries.length > 0) { + batchGetHistoricalPropertyDataPointsForProperty({ + entries, + client, + }); + } +}; diff --git a/packages/source-iotsitewise/src/time-series-data/client/batchGetLatestPropertyDataPoints.ts b/packages/source-iotsitewise/src/time-series-data/client/batchGetLatestPropertyDataPoints.ts new file mode 100644 index 000000000..d732070a4 --- /dev/null +++ b/packages/source-iotsitewise/src/time-series-data/client/batchGetLatestPropertyDataPoints.ts @@ -0,0 +1,158 @@ +import { + BatchGetAssetPropertyValueCommand, + BatchGetAssetPropertyValueErrorEntry, + BatchGetAssetPropertyValueSuccessEntry, + IoTSiteWiseClient, +} from '@aws-sdk/client-iotsitewise'; +import { toDataPoint } from '../util/toDataPoint'; +import { dataStreamFromSiteWise } from '../dataStreamFromSiteWise'; +import { OnSuccessCallback, ErrorCallback, RequestInformationAndRange } from '@iot-app-kit/core'; +import { toSiteWiseAssetProperty } from '../util/dataStreamId'; +import { isDefined } from '../../common/predicates'; +import { LatestPropertyParams } from './client'; +import { createEntryBatches, shouldFetchNextBatch, NO_LIMIT_BATCH } from './batch'; + +type BatchLatestEntry = { + requestInformation: RequestInformationAndRange; + onError: ErrorCallback; + onSuccess: OnSuccessCallback; + requestStart: Date; + requestEnd: Date; + maxResults?: number; +}; + +type BatchEntryCallbackCache = { + [key: string]: { + onError: (entry: BatchGetAssetPropertyValueErrorEntry) => void; + onSuccess: (entry: BatchGetAssetPropertyValueSuccessEntry) => void; + }; +}; + +/** + * This API currently does not paginate and nextToken will always be null. However, once + * Hybrid Query (hot/cold) is supported it's possible that the API will not return + * all entries in a single API request. Supporting nextToken future-proofs this implementation. + */ +const sendRequest = ({ + client, + batch, + requestIndex, // used to create and regenerate (for paginating) a unique entryId + nextToken: prevToken, +}: { + client: IoTSiteWiseClient; + batch: BatchLatestEntry[]; + requestIndex: number; + nextToken?: string; +}) => { + // callback cache makes it convenient to capture request data in a closure. + // the cache exposes methods that only require batch response entry as an argument. + const callbackCache: BatchEntryCallbackCache = {}; + + client + .send( + new BatchGetAssetPropertyValueCommand({ + entries: batch.map((entry, entryIndex) => { + const { requestInformation, onError, onSuccess, requestStart, requestEnd } = entry; + const { id } = requestInformation; + + // use 2D array indices as entryIDs to guarantee uniqueness + // entryId is used to map batch entries with the appropriate callback + const entryId = String(`${requestIndex}-${entryIndex}`); + + // save request entry data in functional closure. + callbackCache[entryId] = { + onError: ({ errorMessage: msg = 'batch latest value error', errorCode: status }) => { + onError({ + id, + resolution: 0, // currently supports raw data only + error: { msg, status }, + }); + }, + onSuccess: ({ assetPropertyValue }) => { + if (assetPropertyValue) { + const dataStream = dataStreamFromSiteWise({ + ...toSiteWiseAssetProperty(id), + dataPoints: [toDataPoint(assetPropertyValue)].filter(isDefined), + }); + + onSuccess([dataStream], requestInformation, requestStart, requestEnd); + } + }, + }; + + // BatchGetAssetPropertyValueEntry + return { + ...toSiteWiseAssetProperty(requestInformation.id), + entryId, + }; + }), + nextToken: prevToken, + }) + ) + .then((response) => { + const { errorEntries, successEntries, nextToken } = response; + + // execute the correct callback for each entry + // empty entries and entries that don't exist in the cache are ignored. + // TODO: implement retries for retry-able batch errors + errorEntries?.forEach((entry) => entry.entryId && callbackCache[entry.entryId]?.onError(entry)); + successEntries?.forEach((entry) => entry.entryId && callbackCache[entry.entryId]?.onSuccess(entry)); + + if (shouldFetchNextBatch({ nextToken, maxResults: NO_LIMIT_BATCH })) { + sendRequest({ + client, + batch, + requestIndex, + nextToken, + }); + } + }); +}; + +const batchGetLatestPropertyDataPointsForProperty = ({ + client, + entries, +}: { + client: IoTSiteWiseClient; + entries: BatchLatestEntry[]; +}) => + createEntryBatches(entries) + .filter((batch) => batch.length > 0) // filter out empty batches + .map(([batch], requestIndex) => sendRequest({ client, batch, requestIndex })); + +export const batchGetLatestPropertyDataPoints = ({ + params, + client, +}: { + params: LatestPropertyParams[]; + client: IoTSiteWiseClient; +}) => { + const entries: BatchLatestEntry[] = []; + + // fan out params into individual entries, handling fetchMostRecentBeforeStart + params.forEach(({ requestInformations, onSuccess, onError }) => { + requestInformations + .filter(({ resolution, fetchMostRecentBeforeEnd }) => resolution === '0' && fetchMostRecentBeforeEnd) + .forEach((requestInformation) => { + const { end } = requestInformation; + + entries.push({ + requestInformation, + onSuccess, + onError, + requestStart: new Date(0, 0, 0), // caching will be adjusted based on the returned data point + requestEnd: end, + }); + }); + }); + + // sort entries to ensure earliest data is fetched first because batch API has a property limit + entries.sort((a, b) => b.requestInformation.start.getTime() - a.requestInformation.start.getTime()); + + if (entries.length > 0) { + batchGetLatestPropertyDataPointsForProperty({ + entries, + client, + }); + } +}; diff --git a/packages/source-iotsitewise/src/time-series-data/client/client.spec.ts b/packages/source-iotsitewise/src/time-series-data/client/client.spec.ts index 24e6243d7..72ca91841 100644 --- a/packages/source-iotsitewise/src/time-series-data/client/client.spec.ts +++ b/packages/source-iotsitewise/src/time-series-data/client/client.spec.ts @@ -1,13 +1,17 @@ -import { AggregateType, ResourceNotFoundException } from '@aws-sdk/client-iotsitewise'; +import { AggregateType } from '@aws-sdk/client-iotsitewise'; import { SiteWiseClient } from './client'; import { createMockSiteWiseSDK } from '../../__mocks__/iotsitewiseSDK'; import { - ASSET_PROPERTY_DOUBLE_VALUE, - ASSET_PROPERTY_VALUE_HISTORY, - AGGREGATE_VALUES, + BATCH_ASSET_PROPERTY_DOUBLE_VALUE, + BATCH_ASSET_PROPERTY_VALUE_HISTORY, + BATCH_ASSET_PROPERTY_ERROR, + BATCH_ASSET_PROPERTY_ERROR_ENTRY, + BATCH_ASSET_PROPERTY_AGGREGATES, } from '../../__mocks__/assetPropertyValue'; import { toId } from '../util/dataStreamId'; import { HOUR_IN_MS } from '@iot-app-kit/core'; +import { MAX_BATCH_RESULTS } from './batch'; +import flushPromises from 'flush-promises'; it('initializes', () => { expect(() => new SiteWiseClient(createMockSiteWiseSDK({}))).not.toThrowError(); @@ -15,21 +19,14 @@ it('initializes', () => { describe('getHistoricalPropertyDataPoints', () => { it('calls onError on failure', async () => { - const ERR: Partial = { - name: 'ResourceNotFoundException', - message: 'assetId 1 not found', - $metadata: { - httpStatusCode: 404, - }, - }; - const getAssetPropertyValueHistory = jest.fn().mockRejectedValue(ERR); + const batchGetAssetPropertyValueHistory = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_ERROR); const assetId = 'some-asset-id'; const propertyId = 'some-property-id'; const onSuccess = jest.fn(); const onError = jest.fn(); - const client = new SiteWiseClient(createMockSiteWiseSDK({ getAssetPropertyValueHistory })); + const client = new SiteWiseClient(createMockSiteWiseSDK({ batchGetAssetPropertyValueHistory })); const startDate = new Date(2000, 0, 0); const endDate = new Date(2001, 0, 0); @@ -49,49 +46,93 @@ describe('getHistoricalPropertyDataPoints', () => { expect(onError).toBeCalledWith( expect.objectContaining({ error: { - msg: ERR.message, - type: ERR.name, - status: ERR.$metadata?.httpStatusCode, + msg: BATCH_ASSET_PROPERTY_ERROR_ENTRY.errorMessage, + status: BATCH_ASSET_PROPERTY_ERROR_ENTRY.errorCode, }, }) ); }); - it('returns data point on success', async () => { - const getAssetPropertyValueHistory = jest.fn().mockResolvedValue(ASSET_PROPERTY_VALUE_HISTORY); - const assetId = 'some-asset-id'; - const propertyId = 'some-property-id'; + it('batches and paginates', async () => { + const batchGetAssetPropertyValueHistory = jest + .fn() + .mockResolvedValue({ ...BATCH_ASSET_PROPERTY_VALUE_HISTORY, nextToken: 'nextToken' }); + const assetId1 = 'some-asset-id-1'; + const propertyId1 = 'some-property-id-1'; + + const assetId2 = 'some-asset-id-2'; + const propertyId2 = 'some-property-id-2'; const onSuccess = jest.fn(); const onError = jest.fn(); - const client = new SiteWiseClient(createMockSiteWiseSDK({ getAssetPropertyValueHistory })); + const client = new SiteWiseClient(createMockSiteWiseSDK({ batchGetAssetPropertyValueHistory })); const startDate = new Date(2000, 0, 0); const endDate = new Date(2001, 0, 0); - const requestInformations = [ - { - id: toId({ assetId, propertyId }), - start: startDate, - end: endDate, - resolution: '0', - fetchFromStartToEnd: true, - }, - ]; + const requestInformation1 = { + id: toId({ assetId: assetId1, propertyId: propertyId1 }), + start: startDate, + end: endDate, + resolution: '0', + fetchFromStartToEnd: true, + }; - await client.getHistoricalPropertyDataPoints({ requestInformations, onSuccess, onError }); + const requestInformation2 = { + id: toId({ assetId: assetId2, propertyId: propertyId2 }), + start: startDate, + end: endDate, + resolution: '0', + fetchFromStartToEnd: true, + }; - expect(getAssetPropertyValueHistory).toBeCalledWith( - expect.objectContaining({ assetId, propertyId, startDate, endDate }) - ); + // batches requests that are sent on a single frame + client.getHistoricalPropertyDataPoints({ + requestInformations: [requestInformation1], + onSuccess, + onError, + maxResults: MAX_BATCH_RESULTS, // ensure pagination happens exactly once + }); + client.getHistoricalPropertyDataPoints({ + requestInformations: [requestInformation2], + onSuccess, + onError, + maxResults: MAX_BATCH_RESULTS, // ensure pagination happens exactly once + }); + + await flushPromises(); + + // process the batch and paginate once + expect(batchGetAssetPropertyValueHistory).toBeCalledTimes(2); + + const batchHistoryParams = [ + expect.objectContaining({ + entries: expect.arrayContaining([ + expect.objectContaining({ + assetId: assetId1, + propertyId: propertyId1, + startDate, + endDate, + }), + expect.objectContaining({ + assetId: assetId2, + propertyId: propertyId2, + startDate, + endDate, + }), + ]), + }), + ]; + + expect(batchGetAssetPropertyValueHistory.mock.calls).toEqual([batchHistoryParams, batchHistoryParams]); expect(onError).not.toBeCalled(); - expect(onSuccess).toBeCalledWith( - [ + const onSuccessParams1 = [ + expect.arrayContaining([ expect.objectContaining({ - id: toId({ assetId, propertyId }), + id: toId({ assetId: assetId1, propertyId: propertyId1 }), data: [ { x: 1000099, @@ -103,112 +144,213 @@ describe('getHistoricalPropertyDataPoints', () => { }, ], }), - ], + ]), expect.objectContaining({ - id: toId({ assetId, propertyId }), + id: toId({ assetId: assetId1, propertyId: propertyId1 }), start: startDate, end: endDate, resolution: '0', fetchFromStartToEnd: true, }), startDate, - endDate - ); + endDate, + ]; + + const onSuccessParams2 = [ + expect.arrayContaining([ + expect.objectContaining({ + id: toId({ assetId: assetId2, propertyId: propertyId2 }), + data: [ + { + x: 1000099, + y: 10.123, + }, + ], + }), + ]), + expect.objectContaining({ + id: toId({ assetId: assetId2, propertyId: propertyId2 }), + start: startDate, + end: endDate, + resolution: '0', + fetchFromStartToEnd: true, + }), + startDate, + endDate, + ]; + + // call onSuccess for each entry in each batch + expect(onSuccess).toBeCalledTimes(4); + expect(onSuccess.mock.calls).toEqual([onSuccessParams1, onSuccessParams2, onSuccessParams1, onSuccessParams2]); }); }); describe('getLatestPropertyDataPoint', () => { - it.skip('returns data point on success', async () => { - const getAssetPropertyValue = jest.fn().mockResolvedValue(ASSET_PROPERTY_DOUBLE_VALUE); + it('calls onError when error occurs', async () => { + const batchGetAssetPropertyValue = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_ERROR); const assetId = 'some-asset-id'; const propertyId = 'some-property-id'; + const client = new SiteWiseClient(createMockSiteWiseSDK({ batchGetAssetPropertyValue })); + const onSuccess = jest.fn(); const onError = jest.fn(); - const start = new Date(1000099); - const end = new Date(); - const requestInformations = [ { id: toId({ assetId, propertyId }), - start, - end, + start: new Date(), + end: new Date(), resolution: '0', fetchMostRecentBeforeEnd: true, }, ]; - const client = new SiteWiseClient(createMockSiteWiseSDK({ getAssetPropertyValue })); - await client.getLatestPropertyDataPoint({ onSuccess, onError, requestInformations }); - expect(getAssetPropertyValue).toBeCalledWith({ assetId, propertyId }); + + expect(onError).toBeCalledWith( + expect.objectContaining({ + error: { + msg: BATCH_ASSET_PROPERTY_ERROR_ENTRY.errorMessage, + status: BATCH_ASSET_PROPERTY_ERROR_ENTRY.errorCode, + }, + }) + ); + }); + + it('batches', async () => { + const batchGetAssetPropertyValue = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_DOUBLE_VALUE); + const assetId1 = 'some-asset-id-1'; + const propertyId1 = 'some-property-id-1'; + + const assetId2 = 'some-asset-id-2'; + const propertyId2 = 'some-property-id-2'; + + const onSuccess = jest.fn(); + const onError = jest.fn(); + + const client = new SiteWiseClient(createMockSiteWiseSDK({ batchGetAssetPropertyValue })); + + const startDate = new Date(2000, 0, 0); + const endDate = new Date(2001, 0, 0); + const resolution = '0'; + + const requestInformation1 = { + id: toId({ assetId: assetId1, propertyId: propertyId1 }), + start: startDate, + end: endDate, + resolution, + fetchMostRecentBeforeEnd: true, + }; + + const requestInformation2 = { + id: toId({ assetId: assetId2, propertyId: propertyId2 }), + start: startDate, + end: endDate, + resolution, + fetchMostRecentBeforeEnd: true, + }; + + // batches requests that are sent on a single frame + client.getLatestPropertyDataPoint({ + requestInformations: [requestInformation1], + onSuccess, + onError, + }); + client.getLatestPropertyDataPoint({ + requestInformations: [requestInformation2], + onSuccess, + onError, + }); + + await flushPromises(); + + // process the batch and paginate once + expect(batchGetAssetPropertyValue).toBeCalledTimes(1); + + const batchLatestParams = [ + expect.objectContaining({ + entries: expect.arrayContaining([ + expect.objectContaining({ + assetId: assetId1, + propertyId: propertyId1, + }), + expect.objectContaining({ + assetId: assetId2, + propertyId: propertyId2, + }), + ]), + }), + ]; + + expect(batchGetAssetPropertyValue.mock.calls).toEqual([batchLatestParams]); expect(onError).not.toBeCalled(); - expect(onSuccess).toBeCalledWith( - [ + const onSuccessParams1 = [ + expect.arrayContaining([ expect.objectContaining({ - id: toId({ assetId, propertyId }), + id: toId({ assetId: assetId1, propertyId: propertyId1 }), data: [ { - y: ASSET_PROPERTY_DOUBLE_VALUE.propertyValue?.value?.doubleValue, x: 1000099, + y: 10.123, }, ], + resolution: 0, }), - ], + ]), expect.objectContaining({ - id: toId({ assetId, propertyId }), - start, - end, - resolution: '0', + id: toId({ assetId: assetId1, propertyId: propertyId1 }), + start: startDate, + end: endDate, + resolution, fetchMostRecentBeforeEnd: true, }), - start, - end - ); - }); - - it('calls onError when error occurs', async () => { - const ERR = new Error('some scary error'); - const getAssetPropertyValue = jest.fn().mockRejectedValue(ERR); - const assetId = 'some-asset-id'; - const propertyId = 'some-property-id'; - - const client = new SiteWiseClient(createMockSiteWiseSDK({ getAssetPropertyValue })); - - const onSuccess = jest.fn(); - const onError = jest.fn(); + new Date(0, 0, 0), + endDate, + ]; - const requestInformations = [ - { - id: toId({ assetId, propertyId }), - start: new Date(), - end: new Date(), - resolution: '0', + const onSuccessParams2 = [ + expect.arrayContaining([ + expect.objectContaining({ + id: toId({ assetId: assetId2, propertyId: propertyId2 }), + data: [ + { + x: 1000099, + y: 10.123, + }, + ], + resolution: 0, + }), + ]), + expect.objectContaining({ + id: toId({ assetId: assetId2, propertyId: propertyId2 }), + start: startDate, + end: endDate, + resolution, fetchMostRecentBeforeEnd: true, - }, + }), + new Date(0, 0, 0), + endDate, ]; - await client.getLatestPropertyDataPoint({ onSuccess, onError, requestInformations }); - - expect(onSuccess).not.toBeCalled(); - expect(onError).toBeCalled(); + // call onSuccess for each entry in the batch + expect(onSuccess).toBeCalledTimes(2); + expect(onSuccess.mock.calls).toEqual([onSuccessParams1, onSuccessParams2]); }); }); describe('getAggregatedPropertyDataPoints', () => { it('calls onError on failure', async () => { - const ERR = new Error('some error'); - const getAssetPropertyAggregates = jest.fn().mockRejectedValue(ERR); + const batchGetAssetPropertyAggregates = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_ERROR); const assetId = 'some-asset-id'; const propertyId = 'some-property-id'; const onSuccess = jest.fn(); const onError = jest.fn(); - const client = new SiteWiseClient(createMockSiteWiseSDK({ getAssetPropertyAggregates })); + const client = new SiteWiseClient(createMockSiteWiseSDK({ batchGetAssetPropertyAggregates })); const startDate = new Date(2000, 0, 0); const endDate = new Date(2001, 0, 0); @@ -225,59 +367,137 @@ describe('getAggregatedPropertyDataPoints', () => { }, ]; - await client.getAggregatedPropertyDataPoints({ - requestInformations, - onSuccess, - onError, - aggregateTypes, - }); + await client.getAggregatedPropertyDataPoints({ requestInformations, onSuccess, onError, aggregateTypes }); - expect(onError).toBeCalled(); + expect(onError).toBeCalledWith( + expect.objectContaining({ + error: { + msg: BATCH_ASSET_PROPERTY_ERROR_ENTRY.errorMessage, + status: BATCH_ASSET_PROPERTY_ERROR_ENTRY.errorCode, + }, + }) + ); }); - it('returns data point on success', async () => { - const assetId = 'some-asset-id'; - const propertyId = 'some-property-id'; + it('batches and paginates', async () => { + const batchGetAssetPropertyAggregates = jest + .fn() + .mockResolvedValue({ ...BATCH_ASSET_PROPERTY_AGGREGATES, nextToken: 'nextToken' }); + const assetId1 = 'some-asset-id-1'; + const propertyId1 = 'some-property-id-1'; + + const assetId2 = 'some-asset-id-2'; + const propertyId2 = 'some-property-id-2'; const onSuccess = jest.fn(); const onError = jest.fn(); - const getAssetPropertyAggregates = jest.fn().mockResolvedValue(AGGREGATE_VALUES); - const client = new SiteWiseClient(createMockSiteWiseSDK({ getAssetPropertyAggregates })); + const client = new SiteWiseClient(createMockSiteWiseSDK({ batchGetAssetPropertyAggregates })); const startDate = new Date(2000, 0, 0); const endDate = new Date(2001, 0, 0); const resolution = '1h'; const aggregateTypes = [AggregateType.AVERAGE]; - const requestInformations = [ - { - id: toId({ assetId, propertyId }), - start: startDate, - end: endDate, - resolution, - fetchFromStartToEnd: true, - }, - ]; + const requestInformation1 = { + id: toId({ assetId: assetId1, propertyId: propertyId1 }), + start: startDate, + end: endDate, + resolution, + fetchFromStartToEnd: true, + }; - await client.getAggregatedPropertyDataPoints({ - requestInformations, + const requestInformation2 = { + id: toId({ assetId: assetId2, propertyId: propertyId2 }), + start: startDate, + end: endDate, + resolution, + fetchFromStartToEnd: true, + }; + + // batches requests that are sent on a single frame + client.getAggregatedPropertyDataPoints({ + requestInformations: [requestInformation1], onSuccess, onError, aggregateTypes, + maxResults: MAX_BATCH_RESULTS, // ensure pagination happens exactly once + }); + client.getAggregatedPropertyDataPoints({ + requestInformations: [requestInformation2], + onSuccess, + onError, + aggregateTypes, + maxResults: MAX_BATCH_RESULTS, // ensure pagination happens exactly once }); - expect(getAssetPropertyAggregates).toBeCalledWith( - expect.objectContaining({ assetId, propertyId, startDate, endDate, resolution, aggregateTypes }) - ); + await flushPromises(); + + // process the batch and paginate once + expect(batchGetAssetPropertyAggregates).toBeCalledTimes(2); + + const batchHistoryParams = [ + expect.objectContaining({ + entries: expect.arrayContaining([ + expect.objectContaining({ + assetId: assetId1, + propertyId: propertyId1, + startDate, + endDate, + }), + expect.objectContaining({ + assetId: assetId2, + propertyId: propertyId2, + startDate, + endDate, + }), + ]), + }), + ]; + + expect(batchGetAssetPropertyAggregates.mock.calls).toEqual([batchHistoryParams, batchHistoryParams]); expect(onError).not.toBeCalled(); - expect(onSuccess).toBeCalledWith( - [ + const onSuccessParams1 = [ + expect.arrayContaining([ expect.objectContaining({ - id: toId({ assetId, propertyId }), + id: toId({ assetId: assetId1, propertyId: propertyId1 }), + aggregates: { + [HOUR_IN_MS]: [ + { + x: 946602000000, + y: 5, + }, + { + x: 946605600000, + y: 7, + }, + { + x: 946609200000, + y: 10, + }, + ], + }, data: [], + resolution: HOUR_IN_MS, + }), + ]), + expect.objectContaining({ + id: toId({ assetId: assetId1, propertyId: propertyId1 }), + start: startDate, + end: endDate, + resolution, + fetchFromStartToEnd: true, + }), + startDate, + endDate, + ]; + + const onSuccessParams2 = [ + expect.arrayContaining([ + expect.objectContaining({ + id: toId({ assetId: assetId2, propertyId: propertyId2 }), aggregates: { [HOUR_IN_MS]: [ { @@ -294,17 +514,166 @@ describe('getAggregatedPropertyDataPoints', () => { }, ], }, + data: [], + resolution: HOUR_IN_MS, }), - ], + ]), expect.objectContaining({ - id: toId({ assetId, propertyId }), + id: toId({ assetId: assetId2, propertyId: propertyId2 }), start: startDate, end: endDate, resolution, fetchFromStartToEnd: true, }), startDate, - endDate - ); + endDate, + ]; + + // call onSuccess for each entry in each batch + expect(onSuccess).toBeCalledTimes(4); + expect(onSuccess.mock.calls).toEqual([onSuccessParams1, onSuccessParams2, onSuccessParams1, onSuccessParams2]); + }); +}); + +describe('batch duration', () => { + beforeAll(() => { + jest.useFakeTimers('modern'); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + it('batches requests over a single frame', async () => { + const batchGetAssetPropertyValue = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_DOUBLE_VALUE); + const assetId = 'some-asset-id'; + const propertyId = 'some-property-id'; + + const onSuccess = jest.fn(); + const onError = jest.fn(); + + const client = new SiteWiseClient(createMockSiteWiseSDK({ batchGetAssetPropertyValue })); + + const startDate = new Date(2000, 0, 0); + const endDate = new Date(2001, 0, 0); + const resolution = '0'; + + const requestInformation = { + id: toId({ assetId, propertyId }), + start: startDate, + end: endDate, + resolution, + fetchMostRecentBeforeEnd: true, + }; + + // single frame + client.getLatestPropertyDataPoint({ + requestInformations: [requestInformation], + onSuccess, + onError, + }); + client.getLatestPropertyDataPoint({ + requestInformations: [requestInformation], + onSuccess, + onError, + }); + + await flushPromises(); // clear promise queue + jest.advanceTimersByTime(0); // ensure latest requests are enqueued + + // process the batch + expect(batchGetAssetPropertyValue).toBeCalledTimes(1); + + // now split into two frames + batchGetAssetPropertyValue.mockClear(); + + client.getLatestPropertyDataPoint({ + requestInformations: [requestInformation], + onSuccess, + onError, + }); + + await flushPromises(); // clear promise queue + jest.advanceTimersByTime(0); // ensure latest requests are enqueued + + client.getLatestPropertyDataPoint({ + requestInformations: [requestInformation], + onSuccess, + onError, + }); + + await flushPromises(); // clear promise queue + jest.advanceTimersByTime(0); // ensure latest requests are enqueued + + expect(batchGetAssetPropertyValue).toBeCalledTimes(2); + }); + + it('batches requests over a specified duration', async () => { + const batchGetAssetPropertyValue = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_DOUBLE_VALUE); + const assetId = 'some-asset-id'; + const propertyId = 'some-property-id'; + + const onSuccess = jest.fn(); + const onError = jest.fn(); + + const client = new SiteWiseClient(createMockSiteWiseSDK({ batchGetAssetPropertyValue }), { batchDuration: 100 }); + + const startDate = new Date(2000, 0, 0); + const endDate = new Date(2001, 0, 0); + const resolution = '0'; + + const requestInformation = { + id: toId({ assetId, propertyId }), + start: startDate, + end: endDate, + resolution, + fetchMostRecentBeforeEnd: true, + }; + + client.getLatestPropertyDataPoint({ + requestInformations: [requestInformation], + onSuccess, + onError, + }); + + await flushPromises(); // clear promise queue + jest.advanceTimersByTime(50); // ensure latest requests are enqueued but not outside of batch window + + client.getLatestPropertyDataPoint({ + requestInformations: [requestInformation], + onSuccess, + onError, + }); + + await flushPromises(); // clear promise queue + jest.advanceTimersByTime(100); // ensure latest requests are enqueued and outside of batch window + + await flushPromises(); + + // process the batch and paginate once + expect(batchGetAssetPropertyValue).toBeCalledTimes(1); + + // now split into two separate batch windows + batchGetAssetPropertyValue.mockClear(); + + client.getLatestPropertyDataPoint({ + requestInformations: [requestInformation], + onSuccess, + onError, + }); + + await flushPromises(); + jest.advanceTimersByTime(150); // ensure latest requests are enqueued and outside of batch window + + client.getLatestPropertyDataPoint({ + requestInformations: [requestInformation], + onSuccess, + onError, + }); + + await flushPromises(); + jest.advanceTimersByTime(150); // ensure latest requests are enqueued and outside of batch window + + expect(batchGetAssetPropertyValue).toBeCalledTimes(2); }); }); diff --git a/packages/source-iotsitewise/src/time-series-data/client/client.ts b/packages/source-iotsitewise/src/time-series-data/client/client.ts index a4a2c178c..f65880bab 100644 --- a/packages/source-iotsitewise/src/time-series-data/client/client.ts +++ b/packages/source-iotsitewise/src/time-series-data/client/client.ts @@ -1,46 +1,116 @@ +import DataLoader from 'dataloader'; import { IoTSiteWiseClient, AggregateType } from '@aws-sdk/client-iotsitewise'; -import { getLatestPropertyDataPoint } from './getLatestPropertyDataPoint'; -import { getHistoricalPropertyDataPoints } from './getHistoricalPropertyDataPoints'; -import { getAggregatedPropertyDataPoints } from './getAggregatedPropertyDataPoints'; +import { batchGetHistoricalPropertyDataPoints } from './batchGetHistoricalPropertyDataPoints'; import { OnSuccessCallback, ErrorCallback, RequestInformationAndRange } from '@iot-app-kit/core'; +import { batchGetAggregatedPropertyDataPoints } from './batchGetAggregatedPropertyDataPoints'; +import { batchGetLatestPropertyDataPoints } from './batchGetLatestPropertyDataPoints'; +import { SiteWiseDataSourceSettings } from '../types'; +import { getHistoricalPropertyDataPoints } from './legacy/getHistoricalPropertyDataPoints'; +import { getAggregatedPropertyDataPoints } from './legacy/getAggregatedPropertyDataPoints'; +import { getLatestPropertyDataPoint } from './legacy/getLatestPropertyDataPoint'; + +export type LatestPropertyParams = { + requestInformations: RequestInformationAndRange[]; + onError: ErrorCallback; + onSuccess: OnSuccessCallback; +}; + +export type HistoricalPropertyParams = { + requestInformations: RequestInformationAndRange[]; + maxResults?: number; + onError: ErrorCallback; + onSuccess: OnSuccessCallback; +}; + +export type AggregatedPropertyParams = { + requestInformations: RequestInformationAndRange[]; + aggregateTypes: AggregateType[]; + maxResults?: number; + onError: ErrorCallback; + onSuccess: OnSuccessCallback; +}; export class SiteWiseClient { private siteWiseSdk: IoTSiteWiseClient; + private settings: SiteWiseDataSourceSettings; + + private latestPropertyDataLoader: DataLoader; + private historicalPropertyDataLoader: DataLoader; + private aggregatedPropertyDataLoader: DataLoader; - constructor(siteWiseSdk: IoTSiteWiseClient) { + constructor(siteWiseSdk: IoTSiteWiseClient, settings: SiteWiseDataSourceSettings = {}) { this.siteWiseSdk = siteWiseSdk; + this.settings = settings; + this.instantiateDataLoaders(); + } + + /** + * Instantiate batch data loaders for latest, historical, and aggregated data. + * by default, data loaders will schedule batches for each frame of execution which ensures + * no additional latency when capturing many related requests in a single batch. + */ + private instantiateDataLoaders() { + this.latestPropertyDataLoader = new DataLoader( + async (keys) => { + batchGetLatestPropertyDataPoints({ params: keys.flat(), client: this.siteWiseSdk }); + return keys.map(() => undefined); // values are updated in data cache and don't need to be rebroadcast + }, + { + batchScheduleFn: this.settings.batchDuration + ? (callback) => setTimeout(callback, this.settings.batchDuration) + : undefined, + } + ); + + this.historicalPropertyDataLoader = new DataLoader( + async (keys) => { + batchGetHistoricalPropertyDataPoints({ params: keys.flat(), client: this.siteWiseSdk }); + return keys.map(() => undefined); + }, + { + batchScheduleFn: this.settings.batchDuration + ? (callback) => setTimeout(callback, this.settings.batchDuration) + : undefined, + } + ); + + this.aggregatedPropertyDataLoader = new DataLoader( + async (keys) => { + batchGetAggregatedPropertyDataPoints({ params: keys.flat(), client: this.siteWiseSdk }); + return keys.map(() => undefined); + }, + { + batchScheduleFn: this.settings.batchDuration + ? (callback) => setTimeout(callback, this.settings.batchDuration) + : undefined, + } + ); } - getLatestPropertyDataPoint(options: { - requestInformations: RequestInformationAndRange[]; - onSuccess: OnSuccessCallback; - onError: ErrorCallback; - }): Promise { - return getLatestPropertyDataPoint({ client: this.siteWiseSdk, ...options }); + getLatestPropertyDataPoint(params: LatestPropertyParams): Promise { + if (this.settings.legacyAPI) { + return getLatestPropertyDataPoint({ client: this.siteWiseSdk, ...params }); + } + return this.latestPropertyDataLoader.load(params); } - getHistoricalPropertyDataPoints(options: { - requestInformations: RequestInformationAndRange[]; - maxResults?: number; - onError: ErrorCallback; - onSuccess: OnSuccessCallback; - }): Promise { - return getHistoricalPropertyDataPoints({ - client: this.siteWiseSdk, - ...options, - }); + getHistoricalPropertyDataPoints(params: HistoricalPropertyParams): Promise { + if (this.settings.legacyAPI) { + return getHistoricalPropertyDataPoints({ + client: this.siteWiseSdk, + ...params, + }); + } + return this.historicalPropertyDataLoader.load(params); } - getAggregatedPropertyDataPoints(options: { - requestInformations: RequestInformationAndRange[]; - aggregateTypes: AggregateType[]; - maxResults?: number; - onError: ErrorCallback; - onSuccess: OnSuccessCallback; - }): Promise { - return getAggregatedPropertyDataPoints({ - client: this.siteWiseSdk, - ...options, - }); + getAggregatedPropertyDataPoints(params: AggregatedPropertyParams): Promise { + if (this.settings.legacyAPI) { + return getAggregatedPropertyDataPoints({ + client: this.siteWiseSdk, + ...params, + }); + } + return this.aggregatedPropertyDataLoader.load(params); } } diff --git a/packages/source-iotsitewise/src/time-series-data/client/getAggregatedPropertyDataPoints.ts b/packages/source-iotsitewise/src/time-series-data/client/legacy/getAggregatedPropertyDataPoints.ts similarity index 90% rename from packages/source-iotsitewise/src/time-series-data/client/getAggregatedPropertyDataPoints.ts rename to packages/source-iotsitewise/src/time-series-data/client/legacy/getAggregatedPropertyDataPoints.ts index 50095b91f..8c1899082 100644 --- a/packages/source-iotsitewise/src/time-series-data/client/getAggregatedPropertyDataPoints.ts +++ b/packages/source-iotsitewise/src/time-series-data/client/legacy/getAggregatedPropertyDataPoints.ts @@ -4,13 +4,13 @@ import { TimeOrdering, AggregateType, } from '@aws-sdk/client-iotsitewise'; -import { AssetId, AssetPropertyId } from '../types'; -import { aggregateToDataPoint } from '../util/toDataPoint'; -import { RESOLUTION_TO_MS_MAPPING } from '../util/resolution'; -import { toId, toSiteWiseAssetProperty } from '../util/dataStreamId'; +import { AssetId, AssetPropertyId } from '../../types'; +import { aggregateToDataPoint } from '../../util/toDataPoint'; +import { RESOLUTION_TO_MS_MAPPING } from '../../util/resolution'; +import { toId, toSiteWiseAssetProperty } from '../../util/dataStreamId'; import { parseDuration, OnSuccessCallback, ErrorCallback, RequestInformationAndRange } from '@iot-app-kit/core'; -import { isDefined } from '../../common/predicates'; -import { dataStreamFromSiteWise } from '../dataStreamFromSiteWise'; +import { isDefined } from '../../../common/predicates'; +import { dataStreamFromSiteWise } from '../../dataStreamFromSiteWise'; const getAggregatedPropertyDataPointsForProperty = ({ requestInformation, diff --git a/packages/source-iotsitewise/src/time-series-data/client/getHistoricalPropertyDataPoints.ts b/packages/source-iotsitewise/src/time-series-data/client/legacy/getHistoricalPropertyDataPoints.ts similarity index 91% rename from packages/source-iotsitewise/src/time-series-data/client/getHistoricalPropertyDataPoints.ts rename to packages/source-iotsitewise/src/time-series-data/client/legacy/getHistoricalPropertyDataPoints.ts index 57dbac67b..feb15323c 100644 --- a/packages/source-iotsitewise/src/time-series-data/client/getHistoricalPropertyDataPoints.ts +++ b/packages/source-iotsitewise/src/time-series-data/client/legacy/getHistoricalPropertyDataPoints.ts @@ -1,10 +1,10 @@ import { GetAssetPropertyValueHistoryCommand, IoTSiteWiseClient, TimeOrdering } from '@aws-sdk/client-iotsitewise'; -import { AssetId, AssetPropertyId } from '../types'; -import { toDataPoint } from '../util/toDataPoint'; -import { dataStreamFromSiteWise } from '../dataStreamFromSiteWise'; +import { AssetId, AssetPropertyId } from '../../types'; +import { toDataPoint } from '../../util/toDataPoint'; +import { dataStreamFromSiteWise } from '../../dataStreamFromSiteWise'; import { OnSuccessCallback, ErrorCallback, RequestInformationAndRange } from '@iot-app-kit/core'; -import { toId, toSiteWiseAssetProperty } from '../util/dataStreamId'; -import { isDefined } from '../../common/predicates'; +import { toId, toSiteWiseAssetProperty } from '../../util/dataStreamId'; +import { isDefined } from '../../../common/predicates'; const getHistoricalPropertyDataPointsForProperty = ({ requestInformation, diff --git a/packages/source-iotsitewise/src/time-series-data/client/getLatestPropertyDataPoint.ts b/packages/source-iotsitewise/src/time-series-data/client/legacy/getLatestPropertyDataPoint.ts similarity index 88% rename from packages/source-iotsitewise/src/time-series-data/client/getLatestPropertyDataPoint.ts rename to packages/source-iotsitewise/src/time-series-data/client/legacy/getLatestPropertyDataPoint.ts index 0e10bc3d9..52ed0a82a 100644 --- a/packages/source-iotsitewise/src/time-series-data/client/getLatestPropertyDataPoint.ts +++ b/packages/source-iotsitewise/src/time-series-data/client/legacy/getLatestPropertyDataPoint.ts @@ -1,9 +1,9 @@ import { GetAssetPropertyValueCommand, IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; -import { toDataPoint } from '../util/toDataPoint'; -import { dataStreamFromSiteWise } from '../dataStreamFromSiteWise'; +import { toDataPoint } from '../../util/toDataPoint'; +import { dataStreamFromSiteWise } from '../../dataStreamFromSiteWise'; import { OnSuccessCallback, ErrorCallback, RequestInformationAndRange } from '@iot-app-kit/core'; -import { toId, toSiteWiseAssetProperty } from '../util/dataStreamId'; -import { isDefined } from '../../common/predicates'; +import { toId, toSiteWiseAssetProperty } from '../../util/dataStreamId'; +import { isDefined } from '../../../common/predicates'; export const getLatestPropertyDataPoint = async ({ onSuccess, diff --git a/packages/source-iotsitewise/src/time-series-data/data-source.spec.ts b/packages/source-iotsitewise/src/time-series-data/data-source.spec.ts index 5b34eb9b7..62ad793e7 100644 --- a/packages/source-iotsitewise/src/time-series-data/data-source.spec.ts +++ b/packages/source-iotsitewise/src/time-series-data/data-source.spec.ts @@ -1,12 +1,15 @@ import flushPromises from 'flush-promises'; -import { IoTSiteWiseClient, ResourceNotFoundException } from '@aws-sdk/client-iotsitewise'; +import { IoTSiteWiseClient } from '@aws-sdk/client-iotsitewise'; import { createDataSource, SITEWISE_DATA_SOURCE } from './data-source'; import { MINUTE_IN_MS, HOUR_IN_MS, MONTH_IN_MS, IotAppKitDataModule, TimeSeriesDataRequest } from '@iot-app-kit/core'; import { SiteWiseDataStreamQuery } from './types'; import { - ASSET_PROPERTY_DOUBLE_VALUE, AGGREGATE_VALUES, ASSET_PROPERTY_VALUE_HISTORY, + BATCH_ASSET_PROPERTY_VALUE_HISTORY, + BATCH_ASSET_PROPERTY_AGGREGATES, + BATCH_ASSET_PROPERTY_DOUBLE_VALUE, + BATCH_ASSET_PROPERTY_ERROR, } from '../__mocks__/assetPropertyValue'; import { createMockSiteWiseSDK } from '../__mocks__/iotsitewiseSDK'; import { toId } from './util/dataStreamId'; @@ -52,15 +55,15 @@ const HISTORICAL_REQUEST: TimeSeriesDataRequest = { describe('initiateRequest', () => { it('does not call SDK when query contains no assets', () => { - const getAssetPropertyValue = jest.fn(); - const getAssetPropertyAggregates = jest.fn(); - const getAssetPropertyValueHistory = jest.fn(); + const batchGetAssetPropertyValue = jest.fn(); + const batchGetAssetPropertyAggregates = jest.fn(); + const batchGetAssetPropertyValueHistory = jest.fn(); const getInterpolatedAssetPropertyValues = jest.fn(); const mockSDK = createMockSiteWiseSDK({ - getAssetPropertyValue, - getAssetPropertyValueHistory, - getAssetPropertyAggregates, + batchGetAssetPropertyValue, + batchGetAssetPropertyValueHistory, + batchGetAssetPropertyAggregates, getInterpolatedAssetPropertyValues, }); @@ -79,141 +82,18 @@ describe('initiateRequest', () => { [] ); - expect(getAssetPropertyAggregates).not.toBeCalled(); - expect(getAssetPropertyValue).not.toBeCalled(); - expect(getAssetPropertyValueHistory).not.toBeCalled(); + expect(batchGetAssetPropertyAggregates).not.toBeCalled(); + expect(batchGetAssetPropertyValue).not.toBeCalled(); + expect(batchGetAssetPropertyValueHistory).not.toBeCalled(); expect(getInterpolatedAssetPropertyValues).not.toBeCalled(); }); describe('fetch latest before end', () => { - describe('on error', () => { - it.skip('calls `onError` callback', async () => { - const ERR: Partial = { - name: 'ResourceNotFoundException', - message: 'assetId 1 not found', - $metadata: { - httpStatusCode: 404, - }, - }; - const getAssetPropertyValue = jest.fn().mockRejectedValue(ERR); - - const mockSDK = createMockSiteWiseSDK({ getAssetPropertyValue }); + it('gets latest value for multiple properties', async () => { + const batchGetAssetPropertyValueHistory = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_VALUE_HISTORY); + const batchGetAssetPropertyValue = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_DOUBLE_VALUE); - const dataSource = createDataSource(mockSDK); - - const ASSET_1 = 'asset-1'; - const PROPERTY_1 = 'prop-1'; - - const query: SiteWiseDataStreamQuery = { - source: SITEWISE_DATA_SOURCE, - assets: [{ assetId: ASSET_1, properties: [{ propertyId: PROPERTY_1 }] }], - }; - - const onError = jest.fn(); - const onSuccess = jest.fn(); - - dataSource.initiateRequest( - { - onError, - onSuccess, - query, - request: LAST_MINUTE_REQUEST, - }, - [ - { - id: toId({ assetId: ASSET_1, propertyId: PROPERTY_1 }), - start: new Date(), - end: new Date(), - resolution: '0', - fetchMostRecentBeforeEnd: true, - }, - ] - ); - - await flushPromises(); - - expect(onSuccess).not.toBeCalled(); - expect(onError).toBeCalledWith({ - id: toId({ assetId: ASSET_1, propertyId: PROPERTY_1 }), - resolution: '0', - error: { - msg: ERR.message, - type: ERR.name, - status: ERR.$metadata?.httpStatusCode, - }, - }); - }); - }); - - it.skip('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(); - const getInterpolatedAssetPropertyValues = jest.fn(); - - const mockSDK = createMockSiteWiseSDK({ - getAssetPropertyValue, - getAssetPropertyValueHistory, - getAssetPropertyAggregates, - getInterpolatedAssetPropertyValues, - }); - - const dataSource = createDataSource(mockSDK); - - const query: SiteWiseDataStreamQuery = { - source: SITEWISE_DATA_SOURCE, - assets: [{ assetId: 'some-asset-id', properties: [{ propertyId: 'some-property-id' }] }], - }; - - const onError = jest.fn(); - const onSuccess = jest.fn(); - - dataSource.initiateRequest( - { - onError, - onSuccess, - query, - request: LAST_MINUTE_REQUEST, - }, - [ - { - id: toId({ assetId: 'some-asset-id', propertyId: 'some-property-id' }), - start: new Date(), - end: new Date(), - resolution: '0', - fetchMostRecentBeforeEnd: true, - }, - ] - ); - - await flushPromises(); - - expect(getAssetPropertyAggregates).not.toBeCalled(); - expect(getAssetPropertyValueHistory).not.toBeCalled(); - expect(getInterpolatedAssetPropertyValues).not.toBeCalled(); - - expect(getAssetPropertyValue).toBeCalledTimes(1); - expect(getAssetPropertyValue).toBeCalledWith({ - assetId: query.assets[0].assetId, - propertyId: query.assets[0].properties[0].propertyId, - }); - - expect(onError).not.toBeCalled(); - - expect(onSuccess).toBeCalledTimes(1); - expect(onSuccess).toBeCalledWith([ - expect.objectContaining({ - id: toId({ assetId: 'some-asset-id', propertyId: 'some-property-id' }), - data: [{ x: 1000099, y: 10.123 }], - resolution: '0', - }), - ]); - }); - - it('gets latest value for multiple properties', () => { - const getAssetPropertyValue = jest.fn().mockResolvedValue(ASSET_PROPERTY_DOUBLE_VALUE); - - const mockSDK = createMockSiteWiseSDK({ getAssetPropertyValue }); + const mockSDK = createMockSiteWiseSDK({ batchGetAssetPropertyValueHistory, batchGetAssetPropertyValue }); const dataSource = createDataSource(mockSDK); @@ -251,23 +131,48 @@ describe('initiateRequest', () => { ] ); - expect(getAssetPropertyValue).toBeCalledTimes(2); + await flushPromises(); - expect(getAssetPropertyValue).toBeCalledWith({ - assetId: ASSET_ID, - propertyId: PROPERTY_1, - }); + expect(batchGetAssetPropertyValueHistory).toBeCalledTimes(1); - expect(getAssetPropertyValue).toBeCalledWith({ - assetId: ASSET_ID, - propertyId: PROPERTY_2, - }); + expect(batchGetAssetPropertyValueHistory).toBeCalledWith( + expect.objectContaining({ + entries: expect.arrayContaining([ + expect.objectContaining({ + assetId: ASSET_ID, + propertyId: PROPERTY_2, + }), + expect.objectContaining({ + assetId: ASSET_ID, + propertyId: PROPERTY_2, + }), + ]), + }) + ); + + expect(batchGetAssetPropertyValue).toBeCalledTimes(1); + + expect(batchGetAssetPropertyValue).toBeCalledWith( + expect.objectContaining({ + entries: expect.arrayContaining([ + expect.objectContaining({ + assetId: ASSET_ID, + propertyId: PROPERTY_2, + }), + expect.objectContaining({ + assetId: ASSET_ID, + propertyId: PROPERTY_2, + }), + ]), + }) + ); }); - it('gets latest value for multiple assets', () => { - const getAssetPropertyValue = jest.fn().mockResolvedValue(ASSET_PROPERTY_DOUBLE_VALUE); + it('gets latest value for multiple assets', async () => { + const batchGetAssetPropertyValueHistory = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_VALUE_HISTORY); + const batchGetAssetPropertyValue = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_DOUBLE_VALUE); - const mockSDK = createMockSiteWiseSDK({ getAssetPropertyValue }); + const mockSDK = createMockSiteWiseSDK({ batchGetAssetPropertyValueHistory, batchGetAssetPropertyValue }); const dataSource = createDataSource(mockSDK); @@ -309,25 +214,49 @@ describe('initiateRequest', () => { ] ); - expect(getAssetPropertyValue).toBeCalledTimes(2); + await flushPromises(); + + expect(batchGetAssetPropertyValueHistory).toBeCalledTimes(1); + + expect(batchGetAssetPropertyValueHistory).toBeCalledWith( + expect.objectContaining({ + entries: expect.arrayContaining([ + expect.objectContaining({ + assetId: ASSET_1, + propertyId: PROPERTY_1, + }), + expect.objectContaining({ + assetId: ASSET_2, + propertyId: PROPERTY_2, + }), + ]), + }) + ); - expect(getAssetPropertyValue).toBeCalledWith({ - assetId: ASSET_1, - propertyId: PROPERTY_1, - }); + expect(batchGetAssetPropertyValue).toBeCalledTimes(1); - expect(getAssetPropertyValue).toBeCalledWith({ - assetId: ASSET_2, - propertyId: PROPERTY_2, - }); + expect(batchGetAssetPropertyValue).toBeCalledWith( + expect.objectContaining({ + entries: expect.arrayContaining([ + expect.objectContaining({ + assetId: ASSET_1, + propertyId: PROPERTY_1, + }), + expect.objectContaining({ + assetId: ASSET_2, + propertyId: PROPERTY_2, + }), + ]), + }) + ); }); }); describe('fetch latest before start', () => { - it('gets latest value before start for multiple properties', () => { - const getAssetPropertyValueHistory = jest.fn().mockResolvedValue(ASSET_PROPERTY_DOUBLE_VALUE); + it('gets latest value before start for multiple properties', async () => { + const batchGetAssetPropertyValueHistory = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_VALUE_HISTORY); - const mockSDK = createMockSiteWiseSDK({ getAssetPropertyValueHistory }); + const mockSDK = createMockSiteWiseSDK({ batchGetAssetPropertyValueHistory }); const dataSource = createDataSource(mockSDK); @@ -365,31 +294,34 @@ describe('initiateRequest', () => { ] ); - expect(getAssetPropertyValueHistory).toBeCalledTimes(2); + await flushPromises(); - expect(getAssetPropertyValueHistory).toBeCalledWith( - expect.objectContaining({ - assetId: ASSET_ID, - propertyId: PROPERTY_1, - startDate: new Date(0, 0, 0), - endDate: historicalRequestStart, - }) - ); + expect(batchGetAssetPropertyValueHistory).toBeCalledTimes(1); - expect(getAssetPropertyValueHistory).toBeCalledWith( + expect(batchGetAssetPropertyValueHistory).toBeCalledWith( expect.objectContaining({ - assetId: ASSET_ID, - propertyId: PROPERTY_2, - startDate: new Date(0, 0, 0), - endDate: historicalRequestStart, + entries: expect.arrayContaining([ + expect.objectContaining({ + assetId: ASSET_ID, + propertyId: PROPERTY_1, + startDate: new Date(0, 0, 0), + endDate: historicalRequestStart, + }), + expect.objectContaining({ + assetId: ASSET_ID, + propertyId: PROPERTY_2, + startDate: new Date(0, 0, 0), + endDate: historicalRequestStart, + }), + ]), }) ); }); - it('gets latest value before start for multiple assets', () => { - const getAssetPropertyValueHistory = jest.fn().mockResolvedValue(ASSET_PROPERTY_DOUBLE_VALUE); + it('gets latest value before start for multiple assets', async () => { + const batchGetAssetPropertyValueHistory = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_VALUE_HISTORY); - const mockSDK = createMockSiteWiseSDK({ getAssetPropertyValueHistory }); + const mockSDK = createMockSiteWiseSDK({ batchGetAssetPropertyValueHistory }); const dataSource = createDataSource(mockSDK); @@ -431,31 +363,34 @@ describe('initiateRequest', () => { ] ); - expect(getAssetPropertyValueHistory).toBeCalledTimes(2); + await flushPromises(); - expect(getAssetPropertyValueHistory).toBeCalledWith( - expect.objectContaining({ - assetId: ASSET_1, - propertyId: PROPERTY_1, - startDate: new Date(0, 0, 0), - endDate: historicalRequestStart, - }) - ); + expect(batchGetAssetPropertyValueHistory).toBeCalledTimes(1); - expect(getAssetPropertyValueHistory).toBeCalledWith( + expect(batchGetAssetPropertyValueHistory).toBeCalledWith( expect.objectContaining({ - assetId: ASSET_2, - propertyId: PROPERTY_2, - startDate: new Date(0, 0, 0), - endDate: historicalRequestStart, + entries: expect.arrayContaining([ + expect.objectContaining({ + assetId: ASSET_1, + propertyId: PROPERTY_1, + startDate: new Date(0, 0, 0), + endDate: historicalRequestStart, + }), + expect.objectContaining({ + assetId: ASSET_2, + propertyId: PROPERTY_2, + startDate: new Date(0, 0, 0), + endDate: historicalRequestStart, + }), + ]), }) ); }); - it('gets latest value before start for aggregates', () => { - const getAssetPropertyAggregates = jest.fn().mockResolvedValue(ASSET_PROPERTY_DOUBLE_VALUE); + it('gets latest value before start for aggregates', async () => { + const batchGetAssetPropertyAggregates = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_AGGREGATES); - const mockSDK = createMockSiteWiseSDK({ getAssetPropertyAggregates }); + const mockSDK = createMockSiteWiseSDK({ batchGetAssetPropertyAggregates }); const dataSource = createDataSource(mockSDK); @@ -493,23 +428,26 @@ describe('initiateRequest', () => { ] ); - expect(getAssetPropertyAggregates).toBeCalledTimes(2); + await flushPromises(); - expect(getAssetPropertyAggregates).toBeCalledWith( - expect.objectContaining({ - assetId: ASSET_ID, - propertyId: PROPERTY_1, - startDate: new Date(0, 0, 0), - endDate: historicalRequestStart, - }) - ); + expect(batchGetAssetPropertyAggregates).toBeCalledTimes(1); - expect(getAssetPropertyAggregates).toBeCalledWith( + expect(batchGetAssetPropertyAggregates).toBeCalledWith( expect.objectContaining({ - assetId: ASSET_ID, - propertyId: PROPERTY_2, - startDate: new Date(0, 0, 0), - endDate: historicalRequestStart, + entries: expect.arrayContaining([ + expect.objectContaining({ + assetId: ASSET_ID, + propertyId: PROPERTY_1, + startDate: new Date(0, 0, 0), + endDate: historicalRequestStart, + }), + expect.objectContaining({ + assetId: ASSET_ID, + propertyId: PROPERTY_2, + startDate: new Date(0, 0, 0), + endDate: historicalRequestStart, + }), + ]), }) ); }); @@ -517,15 +455,15 @@ describe('initiateRequest', () => { }); it('requests raw data if specified per asset property', async () => { - const getAssetPropertyValue = jest.fn(); - const getAssetPropertyAggregates = jest.fn(); - const getAssetPropertyValueHistory = jest.fn().mockResolvedValue(ASSET_PROPERTY_VALUE_HISTORY); + const batchGetAssetPropertyValue = jest.fn(); + const batchGetAssetPropertyAggregates = jest.fn(); + const batchGetAssetPropertyValueHistory = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_VALUE_HISTORY); const getInterpolatedAssetPropertyValues = jest.fn(); const mockSDK = createMockSiteWiseSDK({ - getAssetPropertyValue, - getAssetPropertyValueHistory, - getAssetPropertyAggregates, + batchGetAssetPropertyValue, + batchGetAssetPropertyValueHistory, + batchGetAssetPropertyAggregates, getInterpolatedAssetPropertyValues, }); @@ -575,16 +513,20 @@ it('requests raw data if specified per asset property', async () => { await flushPromises(); - expect(getAssetPropertyValue).not.toBeCalled(); + expect(batchGetAssetPropertyValue).not.toBeCalled(); expect(getInterpolatedAssetPropertyValues).not.toBeCalled(); - expect(getAssetPropertyAggregates).not.toBeCalled(); + expect(batchGetAssetPropertyAggregates).not.toBeCalled(); - expect(getAssetPropertyValueHistory).toBeCalledTimes(1); - expect(getAssetPropertyValueHistory).toBeCalledWith( + expect(batchGetAssetPropertyValueHistory).toBeCalledTimes(1); + expect(batchGetAssetPropertyValueHistory).toBeCalledWith( expect.objectContaining({ - assetId: query.assets[0].assetId, - propertyId: query.assets[0].properties[0].propertyId, + entries: expect.arrayContaining([ + expect.objectContaining({ + assetId: query.assets[0].assetId, + propertyId: query.assets[0].properties[0].propertyId, + }), + ]), }) ); @@ -615,21 +557,14 @@ it('requests raw data if specified per asset property', async () => { ); }); -describe.skip('e2e through data-module', () => { +describe('e2e through data-module', () => { describe('fetching range of historical data', () => { it('reports error occurred on request initiation', async () => { const dataModule = new IotAppKitDataModule(); - const ERR: Partial = { - name: 'ResourceNotFoundException', - message: 'assetId 1 not found', - $metadata: { - httpStatusCode: 404, - }, - }; - const getAssetPropertyValueHistory = jest.fn().mockRejectedValue(ERR); + const batchGetAssetPropertyAggregates = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_ERROR); - const mockSDK = createMockSiteWiseSDK({ getAssetPropertyValueHistory }); + const mockSDK = createMockSiteWiseSDK({ batchGetAssetPropertyAggregates }); const dataSource = createDataSource(mockSDK); dataModule.registerDataSource(dataSource); @@ -658,14 +593,7 @@ describe.skip('e2e through data-module', () => { expect.objectContaining({ dataStreams: [ expect.objectContaining({ - id: toId({ assetId, propertyId }), - error: { - msg: ERR.message, - type: ERR.name, - status: ERR.$metadata?.httpStatusCode, - }, - isLoading: false, - isRefreshing: false, + error: { msg: 'assetId 1 not found', status: '404' }, }), ], }) @@ -679,16 +607,10 @@ describe.skip('e2e through data-module', () => { it('reports error occurred on request initiation', async () => { const dataModule = new IotAppKitDataModule(); - const ERR: Partial = { - name: 'ResourceNotFoundException', - message: 'assetId 1 not found', - $metadata: { - httpStatusCode: 404, - }, - }; - const getAssetPropertyValue = jest.fn().mockRejectedValue(ERR); + const batchGetAssetPropertyValueHistory = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_VALUE_HISTORY); + const batchGetAssetPropertyValue = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_ERROR); - const mockSDK = createMockSiteWiseSDK({ getAssetPropertyValue }); + const mockSDK = createMockSiteWiseSDK({ batchGetAssetPropertyValueHistory, batchGetAssetPropertyValue }); const dataSource = createDataSource(mockSDK); dataModule.registerDataSource(dataSource); @@ -706,8 +628,8 @@ describe.skip('e2e through data-module', () => { } as SiteWiseDataStreamQuery, ], request: { - viewport: { start: new Date(2000, 0, 0), end: new Date() }, - settings: { fetchMostRecentBeforeEnd: true }, + viewport: { start: new Date(2000, 0, 0), end: new Date(2000, 0, 0, 1) }, + settings: { fetchMostRecentBeforeEnd: true, resolution: '0' }, }, }, timeSeriesCallback @@ -715,19 +637,12 @@ describe.skip('e2e through data-module', () => { await flushPromises(); - expect(timeSeriesCallback).toBeCalledTimes(2); - expect(timeSeriesCallback).toHaveBeenLastCalledWith( + expect(timeSeriesCallback).toBeCalledTimes(3); + expect(timeSeriesCallback).toHaveBeenCalledWith( expect.objectContaining({ dataStreams: [ expect.objectContaining({ - id: toId({ assetId, propertyId }), - error: { - msg: ERR.message, - type: ERR.name, - status: ERR.$metadata?.httpStatusCode, - }, - isLoading: false, - isRefreshing: false, + error: { msg: 'assetId 1 not found', status: '404' }, }), ], }) diff --git a/packages/source-iotsitewise/src/time-series-data/data-source.ts b/packages/source-iotsitewise/src/time-series-data/data-source.ts index b5d8719df..402784d47 100644 --- a/packages/source-iotsitewise/src/time-series-data/data-source.ts +++ b/packages/source-iotsitewise/src/time-series-data/data-source.ts @@ -1,5 +1,5 @@ import { IoTSiteWiseClient, AggregateType } from '@aws-sdk/client-iotsitewise'; -import { SiteWiseDataStreamQuery } from './types'; +import { SiteWiseDataSourceSettings, SiteWiseDataStreamQuery } from './types'; import { SiteWiseClient } from './client/client'; import { toId } from './util/dataStreamId'; import { @@ -60,8 +60,11 @@ export const determineResolution = ({ } }; -export const createDataSource = (siteWise: IoTSiteWiseClient): DataSource => { - const client = new SiteWiseClient(siteWise); +export const createDataSource = ( + siteWise: IoTSiteWiseClient, + settings?: SiteWiseDataSourceSettings +): DataSource => { + const client = new SiteWiseClient(siteWise, settings); return { name: SITEWISE_DATA_SOURCE, initiateRequest: ({ onSuccess, onError }, requestInformations) => diff --git a/packages/source-iotsitewise/src/time-series-data/subscribeToTimeSeriesData.spec.ts b/packages/source-iotsitewise/src/time-series-data/subscribeToTimeSeriesData.spec.ts index 484652913..dea0a258f 100644 --- a/packages/source-iotsitewise/src/time-series-data/subscribeToTimeSeriesData.spec.ts +++ b/packages/source-iotsitewise/src/time-series-data/subscribeToTimeSeriesData.spec.ts @@ -7,7 +7,7 @@ import flushPromises from 'flush-promises'; import { createDataSource } from './data-source'; import { createAssetModelResponse, createAssetResponse } from '../__mocks__/asset'; import { toId } from './util/dataStreamId'; -import { ASSET_PROPERTY_VALUE_HISTORY } from '../__mocks__/assetPropertyValue'; +import { BATCH_ASSET_PROPERTY_VALUE_HISTORY } from '../__mocks__/assetPropertyValue'; import { SiteWiseAssetDataSource, SiteWiseAssetModule } from '../asset-modules'; const initializeSubscribeToTimeSeriesData = (client: IoTSiteWiseClient) => { @@ -56,7 +56,7 @@ it('provides time series data from iotsitewise', async () => { const ASSET_MODEL_ID = 'some-asset-model-id'; const PROPERTY_NAME = 'some-property-name'; - const getAssetPropertyValueHistory = jest.fn().mockResolvedValue(ASSET_PROPERTY_VALUE_HISTORY); + const batchGetAssetPropertyValueHistory = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_VALUE_HISTORY); const describeAsset = jest .fn() .mockImplementation(({ assetId }) => @@ -76,7 +76,7 @@ it('provides time series data from iotsitewise', async () => { createMockSiteWiseSDK({ describeAsset, describeAssetModel, - getAssetPropertyValueHistory, + batchGetAssetPropertyValueHistory, }) ); @@ -110,11 +110,15 @@ it('provides time series data from iotsitewise', async () => { expect(describeAssetModel).toBeCalledWith({ assetModelId: ASSET_MODEL_ID }); // fetches historical data - expect(getAssetPropertyValueHistory).toBeCalledTimes(1); - expect(getAssetPropertyValueHistory).toBeCalledWith( + expect(batchGetAssetPropertyValueHistory).toBeCalledTimes(1); + expect(batchGetAssetPropertyValueHistory).toBeCalledWith( expect.objectContaining({ - assetId: ASSET_ID, - propertyId: PROPERTY_ID, + entries: expect.arrayContaining([ + expect.objectContaining({ + assetId: ASSET_ID, + propertyId: PROPERTY_ID, + }), + ]), }) ); @@ -145,7 +149,7 @@ it('provides timeseries data from iotsitewise when subscription is updated', asy const ASSET_MODEL_ID = 'some-asset-model-id'; const PROPERTY_NAME = 'some-property-name'; - const getAssetPropertyValueHistory = jest.fn().mockResolvedValue(ASSET_PROPERTY_VALUE_HISTORY); + const batchGetAssetPropertyValueHistory = jest.fn().mockResolvedValue(BATCH_ASSET_PROPERTY_VALUE_HISTORY); const describeAsset = jest .fn() .mockImplementation(({ assetId }) => @@ -165,7 +169,7 @@ it('provides timeseries data from iotsitewise when subscription is updated', asy createMockSiteWiseSDK({ describeAsset, describeAssetModel, - getAssetPropertyValueHistory, + batchGetAssetPropertyValueHistory, }) ); @@ -205,11 +209,15 @@ it('provides timeseries data from iotsitewise when subscription is updated', asy expect(describeAssetModel).toBeCalledWith({ assetModelId: ASSET_MODEL_ID }); // fetches historical data - expect(getAssetPropertyValueHistory).toBeCalledTimes(1); - expect(getAssetPropertyValueHistory).toBeCalledWith( + expect(batchGetAssetPropertyValueHistory).toBeCalledTimes(1); + expect(batchGetAssetPropertyValueHistory).toBeCalledWith( expect.objectContaining({ - assetId: ASSET_ID, - propertyId: PROPERTY_ID, + entries: expect.arrayContaining([ + expect.objectContaining({ + assetId: ASSET_ID, + propertyId: PROPERTY_ID, + }), + ]), }) ); diff --git a/packages/source-iotsitewise/src/time-series-data/types.ts b/packages/source-iotsitewise/src/time-series-data/types.ts index 682738ab3..f74de6c20 100644 --- a/packages/source-iotsitewise/src/time-series-data/types.ts +++ b/packages/source-iotsitewise/src/time-series-data/types.ts @@ -27,3 +27,8 @@ export type SiteWiseAssetQuery = { export type SiteWiseAssetDataStreamQuery = DataStreamQuery & SiteWiseAssetQuery; export type SiteWiseDataStreamQuery = SiteWiseAssetDataStreamQuery; + +export type SiteWiseDataSourceSettings = { + batchDuration?: number; + legacyAPI?: boolean; +}; diff --git a/yarn.lock b/yarn.lock index 83cc28690..3a7f6c320 100644 --- a/yarn.lock +++ b/yarn.lock @@ -81,6 +81,14 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/abort-controller@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/abort-controller/-/abort-controller-3.78.0.tgz#f2b0f8d63954afe51136254f389a18dd24a8f6f3" + integrity sha512-iz1YLwM2feJUj/y97yO4XmDeTxs+yZ1XJwQgoawKuc8IDBKUutnJNCHL5jL04WUKU7Nrlq+Hr2fCTScFh2z9zg== + dependencies: + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/client-cognito-identity@3.67.0": version "3.67.0" resolved "https://registry.yarnpkg.com/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.67.0.tgz#fea86530eef63b9004c0113aff0ec91ae4ecd483" @@ -120,44 +128,44 @@ "@aws-sdk/util-utf8-node" "3.55.0" tslib "^2.3.1" -"@aws-sdk/client-iotsitewise@^3.39.0": - version "3.67.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-iotsitewise/-/client-iotsitewise-3.67.0.tgz#53aa9762c412bfdc3767c11a86f623623f2f5150" - integrity sha512-6UEsA1XioAdteg8zZKXS8xH6pnS8sZRuCkeVzu8x6E9kocja13xNg1VQPw62U3xm5uEv3bdRW3a/vNWBy51v7w== +"@aws-sdk/client-iotsitewise@^3.87.0": + version "3.87.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-iotsitewise/-/client-iotsitewise-3.87.0.tgz#768c32da2de8547d2a48559654c52606539438a7" + integrity sha512-OoprJQ86fzJnXAkhLnqADMlhZJ2sjNbaDeLXi7Hoiqnjsq+joG3rkFfMfKqKIyXVHBjTxl2jywJYPung3q2M5A== dependencies: "@aws-crypto/sha256-browser" "2.0.0" "@aws-crypto/sha256-js" "2.0.0" - "@aws-sdk/client-sts" "3.67.0" - "@aws-sdk/config-resolver" "3.58.0" - "@aws-sdk/credential-provider-node" "3.67.0" - "@aws-sdk/fetch-http-handler" "3.58.0" - "@aws-sdk/hash-node" "3.55.0" - "@aws-sdk/invalid-dependency" "3.55.0" - "@aws-sdk/middleware-content-length" "3.58.0" - "@aws-sdk/middleware-host-header" "3.58.0" - "@aws-sdk/middleware-logger" "3.55.0" - "@aws-sdk/middleware-retry" "3.58.0" - "@aws-sdk/middleware-serde" "3.55.0" - "@aws-sdk/middleware-signing" "3.58.0" - "@aws-sdk/middleware-stack" "3.55.0" - "@aws-sdk/middleware-user-agent" "3.58.0" - "@aws-sdk/node-config-provider" "3.58.0" - "@aws-sdk/node-http-handler" "3.58.0" - "@aws-sdk/protocol-http" "3.58.0" - "@aws-sdk/smithy-client" "3.55.0" - "@aws-sdk/types" "3.55.0" - "@aws-sdk/url-parser" "3.55.0" + "@aws-sdk/client-sts" "3.87.0" + "@aws-sdk/config-resolver" "3.80.0" + "@aws-sdk/credential-provider-node" "3.87.0" + "@aws-sdk/fetch-http-handler" "3.78.0" + "@aws-sdk/hash-node" "3.78.0" + "@aws-sdk/invalid-dependency" "3.78.0" + "@aws-sdk/middleware-content-length" "3.78.0" + "@aws-sdk/middleware-host-header" "3.78.0" + "@aws-sdk/middleware-logger" "3.78.0" + "@aws-sdk/middleware-retry" "3.80.0" + "@aws-sdk/middleware-serde" "3.78.0" + "@aws-sdk/middleware-signing" "3.78.0" + "@aws-sdk/middleware-stack" "3.78.0" + "@aws-sdk/middleware-user-agent" "3.78.0" + "@aws-sdk/node-config-provider" "3.80.0" + "@aws-sdk/node-http-handler" "3.82.0" + "@aws-sdk/protocol-http" "3.78.0" + "@aws-sdk/smithy-client" "3.85.0" + "@aws-sdk/types" "3.78.0" + "@aws-sdk/url-parser" "3.78.0" "@aws-sdk/util-base64-browser" "3.58.0" "@aws-sdk/util-base64-node" "3.55.0" "@aws-sdk/util-body-length-browser" "3.55.0" "@aws-sdk/util-body-length-node" "3.55.0" - "@aws-sdk/util-defaults-mode-browser" "3.55.0" - "@aws-sdk/util-defaults-mode-node" "3.58.0" - "@aws-sdk/util-user-agent-browser" "3.58.0" - "@aws-sdk/util-user-agent-node" "3.58.0" + "@aws-sdk/util-defaults-mode-browser" "3.85.0" + "@aws-sdk/util-defaults-mode-node" "3.85.0" + "@aws-sdk/util-user-agent-browser" "3.78.0" + "@aws-sdk/util-user-agent-node" "3.80.0" "@aws-sdk/util-utf8-browser" "3.55.0" "@aws-sdk/util-utf8-node" "3.55.0" - "@aws-sdk/util-waiter" "3.55.0" + "@aws-sdk/util-waiter" "3.78.0" tslib "^2.3.1" uuid "^8.3.2" @@ -197,6 +205,42 @@ "@aws-sdk/util-utf8-node" "3.55.0" tslib "^2.3.1" +"@aws-sdk/client-sso@3.85.0": + version "3.85.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.85.0.tgz#4e5cf2b9e9898ff23c0aed1af0bac8d46ceed229" + integrity sha512-JMW0NzFpo99oE6O9M/kgLela73p4vmhe/5TIcdrqUvP9XUV9nANl5nSXh3rqLz0ubmliedz9kdYYhwMC3ntoXg== + dependencies: + "@aws-crypto/sha256-browser" "2.0.0" + "@aws-crypto/sha256-js" "2.0.0" + "@aws-sdk/config-resolver" "3.80.0" + "@aws-sdk/fetch-http-handler" "3.78.0" + "@aws-sdk/hash-node" "3.78.0" + "@aws-sdk/invalid-dependency" "3.78.0" + "@aws-sdk/middleware-content-length" "3.78.0" + "@aws-sdk/middleware-host-header" "3.78.0" + "@aws-sdk/middleware-logger" "3.78.0" + "@aws-sdk/middleware-retry" "3.80.0" + "@aws-sdk/middleware-serde" "3.78.0" + "@aws-sdk/middleware-stack" "3.78.0" + "@aws-sdk/middleware-user-agent" "3.78.0" + "@aws-sdk/node-config-provider" "3.80.0" + "@aws-sdk/node-http-handler" "3.82.0" + "@aws-sdk/protocol-http" "3.78.0" + "@aws-sdk/smithy-client" "3.85.0" + "@aws-sdk/types" "3.78.0" + "@aws-sdk/url-parser" "3.78.0" + "@aws-sdk/util-base64-browser" "3.58.0" + "@aws-sdk/util-base64-node" "3.55.0" + "@aws-sdk/util-body-length-browser" "3.55.0" + "@aws-sdk/util-body-length-node" "3.55.0" + "@aws-sdk/util-defaults-mode-browser" "3.85.0" + "@aws-sdk/util-defaults-mode-node" "3.85.0" + "@aws-sdk/util-user-agent-browser" "3.78.0" + "@aws-sdk/util-user-agent-node" "3.80.0" + "@aws-sdk/util-utf8-browser" "3.55.0" + "@aws-sdk/util-utf8-node" "3.55.0" + tslib "^2.3.1" + "@aws-sdk/client-sts@3.67.0": version "3.67.0" resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.67.0.tgz#d45ac0b270eac749b74477458d6980e77303bb1b" @@ -238,6 +282,47 @@ fast-xml-parser "3.19.0" tslib "^2.3.1" +"@aws-sdk/client-sts@3.87.0": + version "3.87.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.87.0.tgz#65c18dce2ba8312a8cb4289b29bc1f507db97e92" + integrity sha512-JGI5rzSq8T7IVlfDJ8ltGl8nyVEtwvqXrYR87DwTjeE4HP+/oBdWdbO0oBL1TJMGjzZcENyVYvmaSAkobenkTg== + dependencies: + "@aws-crypto/sha256-browser" "2.0.0" + "@aws-crypto/sha256-js" "2.0.0" + "@aws-sdk/config-resolver" "3.80.0" + "@aws-sdk/credential-provider-node" "3.87.0" + "@aws-sdk/fetch-http-handler" "3.78.0" + "@aws-sdk/hash-node" "3.78.0" + "@aws-sdk/invalid-dependency" "3.78.0" + "@aws-sdk/middleware-content-length" "3.78.0" + "@aws-sdk/middleware-host-header" "3.78.0" + "@aws-sdk/middleware-logger" "3.78.0" + "@aws-sdk/middleware-retry" "3.80.0" + "@aws-sdk/middleware-sdk-sts" "3.78.0" + "@aws-sdk/middleware-serde" "3.78.0" + "@aws-sdk/middleware-signing" "3.78.0" + "@aws-sdk/middleware-stack" "3.78.0" + "@aws-sdk/middleware-user-agent" "3.78.0" + "@aws-sdk/node-config-provider" "3.80.0" + "@aws-sdk/node-http-handler" "3.82.0" + "@aws-sdk/protocol-http" "3.78.0" + "@aws-sdk/smithy-client" "3.85.0" + "@aws-sdk/types" "3.78.0" + "@aws-sdk/url-parser" "3.78.0" + "@aws-sdk/util-base64-browser" "3.58.0" + "@aws-sdk/util-base64-node" "3.55.0" + "@aws-sdk/util-body-length-browser" "3.55.0" + "@aws-sdk/util-body-length-node" "3.55.0" + "@aws-sdk/util-defaults-mode-browser" "3.85.0" + "@aws-sdk/util-defaults-mode-node" "3.85.0" + "@aws-sdk/util-user-agent-browser" "3.78.0" + "@aws-sdk/util-user-agent-node" "3.80.0" + "@aws-sdk/util-utf8-browser" "3.55.0" + "@aws-sdk/util-utf8-node" "3.55.0" + entities "2.2.0" + fast-xml-parser "3.19.0" + tslib "^2.3.1" + "@aws-sdk/config-resolver@3.58.0": version "3.58.0" resolved "https://registry.yarnpkg.com/@aws-sdk/config-resolver/-/config-resolver-3.58.0.tgz#c990541276ecdc76acf25f68f58cdb0d0d7eb07e" @@ -249,6 +334,17 @@ "@aws-sdk/util-middleware" "3.55.0" tslib "^2.3.1" +"@aws-sdk/config-resolver@3.80.0": + version "3.80.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/config-resolver/-/config-resolver-3.80.0.tgz#a804aba4d4767402ab15640757c8c8bb2254eec1" + integrity sha512-vFruNKlmhsaC8yjnHmasi1WW/7EELlEuFTj4mqcqNqR4dfraf0maVvpqF1VSR8EstpFMsGYI5dmoWAnnG4PcLQ== + dependencies: + "@aws-sdk/signature-v4" "3.78.0" + "@aws-sdk/types" "3.78.0" + "@aws-sdk/util-config-provider" "3.55.0" + "@aws-sdk/util-middleware" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/credential-provider-cognito-identity@3.67.0": version "3.67.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.67.0.tgz#3d16f3e082714da898d84d87e48f5e9b5cd5d8e7" @@ -268,6 +364,15 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/credential-provider-env@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.78.0.tgz#e3013073bab0db313b0505d790aa79a35bd582d9" + integrity sha512-K41VTIzVHm2RyIwtBER8Hte3huUBXdV1WKO+i7olYVgLFmaqcZUNrlyoGDRqZcQ/u4AbxTzBU9jeMIbIfzMOWg== + dependencies: + "@aws-sdk/property-provider" "3.78.0" + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/credential-provider-imds@3.58.0": version "3.58.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.58.0.tgz#89d3963895f5e6150b74b5ba2010158d8576b95e" @@ -279,6 +384,17 @@ "@aws-sdk/url-parser" "3.55.0" tslib "^2.3.1" +"@aws-sdk/credential-provider-imds@3.81.0": + version "3.81.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.81.0.tgz#1ffd1219b7fd19eec4d4d4b5b06bda66e3bc210e" + integrity sha512-BHopP+gaovTYj+4tSrwCk8NNCR48gE9CWmpIOLkP9ell0gOL81Qh7aCEiIK0BZBZkccv1s16cYq1MSZZGS7PEQ== + dependencies: + "@aws-sdk/node-config-provider" "3.80.0" + "@aws-sdk/property-provider" "3.78.0" + "@aws-sdk/types" "3.78.0" + "@aws-sdk/url-parser" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/credential-provider-ini@3.67.0": version "3.67.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.67.0.tgz#94107021cc1869d98a5a068b95495fb673b9a22a" @@ -293,6 +409,20 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/credential-provider-ini@3.85.0": + version "3.85.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.85.0.tgz#ecbd1d9f3afbcb054b241ae5ced0bc5db6b2a053" + integrity sha512-KgzLGq+w8OrSLutwdYUw0POeLinGQKcqvQJ9702eoeXCwZMnEHwKqU61bn8QKMX/tuYVCNV4I1enI7MmYPW8Lw== + dependencies: + "@aws-sdk/credential-provider-env" "3.78.0" + "@aws-sdk/credential-provider-imds" "3.81.0" + "@aws-sdk/credential-provider-sso" "3.85.0" + "@aws-sdk/credential-provider-web-identity" "3.78.0" + "@aws-sdk/property-provider" "3.78.0" + "@aws-sdk/shared-ini-file-loader" "3.80.0" + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/credential-provider-node@3.67.0": version "3.67.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.67.0.tgz#282bc00800a6e6f753d64ac2f66615d7a3545309" @@ -309,6 +439,22 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/credential-provider-node@3.87.0": + version "3.87.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.87.0.tgz#700e328ac21219cac521e119d06dead873c5ada1" + integrity sha512-yL9W5nX00grNNsGj2df1y7hQ0F77UA7+2toPOVqYPIDhFtIUA97AVYiBEFQz1mO9OAhUfCGgxuFF4pyqFoMcHQ== + dependencies: + "@aws-sdk/credential-provider-env" "3.78.0" + "@aws-sdk/credential-provider-imds" "3.81.0" + "@aws-sdk/credential-provider-ini" "3.85.0" + "@aws-sdk/credential-provider-process" "3.80.0" + "@aws-sdk/credential-provider-sso" "3.85.0" + "@aws-sdk/credential-provider-web-identity" "3.78.0" + "@aws-sdk/property-provider" "3.78.0" + "@aws-sdk/shared-ini-file-loader" "3.80.0" + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/credential-provider-process@3.58.0": version "3.58.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.58.0.tgz#ff6db03266428bb2074e9b32db8021efa1af6570" @@ -319,6 +465,16 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/credential-provider-process@3.80.0": + version "3.80.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.80.0.tgz#625577774278f845fe5bd0f311ed53973ec92ede" + integrity sha512-3Ro+kMMyLUJHefOhGc5pOO/ibGcJi8bkj0z/Jtqd5I2Sm1qi7avoztST67/k48KMW1OqPnD/FUqxz5T8B2d+FQ== + dependencies: + "@aws-sdk/property-provider" "3.78.0" + "@aws-sdk/shared-ini-file-loader" "3.80.0" + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/credential-provider-sso@3.67.0": version "3.67.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.67.0.tgz#6541a93ba6cbf36dd18db97f9f4a2beaa1df2a62" @@ -330,6 +486,17 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/credential-provider-sso@3.85.0": + version "3.85.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.85.0.tgz#05f0d4b004d0a6ff799c09f0923ae4d4c55f2c9a" + integrity sha512-uE238BgJ/AftPDlBGDlV0XdiNWnUZxFmUmLxgbr19/6jHaCuBr//T6rP+Bc0BjcHkvQCvTdFoCjs17R3Quy3cw== + dependencies: + "@aws-sdk/client-sso" "3.85.0" + "@aws-sdk/property-provider" "3.78.0" + "@aws-sdk/shared-ini-file-loader" "3.80.0" + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/credential-provider-web-identity@3.55.0": version "3.55.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.55.0.tgz#21aebe5b4ad7a5b4abaf8df9aabfba0994ece357" @@ -339,6 +506,15 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/credential-provider-web-identity@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.78.0.tgz#61cc6c5c065de3d8d34b7633899e3bbfa9a24c9d" + integrity sha512-9/IvqHdJaVqMEABA8xZE3t5YF1S2PepfckVu0Ws9YUglj6oO+2QyVX6aRgMF1xph6781+Yc31TDh8/3eaDja7w== + dependencies: + "@aws-sdk/property-provider" "3.78.0" + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/credential-providers@^3.39.0": version "3.67.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-providers/-/credential-providers-3.67.0.tgz#4cd977d1d9ab51b46a869d6cef74fe562114876a" @@ -370,6 +546,17 @@ "@aws-sdk/util-base64-browser" "3.58.0" tslib "^2.3.1" +"@aws-sdk/fetch-http-handler@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.78.0.tgz#9cd4a02eaf015b4a5a18552e8c9e8fbfce7219a3" + integrity sha512-cR6r2h2kJ1DNEZSXC6GknQB7OKmy+s9ZNV+g3AsNqkrUmNNOaHpFoSn+m6SC3qaclcGd0eQBpqzSu/TDn23Ihw== + dependencies: + "@aws-sdk/protocol-http" "3.78.0" + "@aws-sdk/querystring-builder" "3.78.0" + "@aws-sdk/types" "3.78.0" + "@aws-sdk/util-base64-browser" "3.58.0" + tslib "^2.3.1" + "@aws-sdk/hash-node@3.55.0": version "3.55.0" resolved "https://registry.yarnpkg.com/@aws-sdk/hash-node/-/hash-node-3.55.0.tgz#ea58e9b6f2147c59ad4e41e83bd6864df59b331e" @@ -379,6 +566,15 @@ "@aws-sdk/util-buffer-from" "3.55.0" tslib "^2.3.1" +"@aws-sdk/hash-node@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/hash-node/-/hash-node-3.78.0.tgz#d03f804a685bc1cea9df3eabf499b2a7659d01fd" + integrity sha512-ev48yXaqZVtMeuKy52LUZPHCyKvkKQ9uiUebqkA+zFxIk+eN8SMPFHmsififIHWuS6ZkXBUSctjH9wmLebH60A== + dependencies: + "@aws-sdk/types" "3.78.0" + "@aws-sdk/util-buffer-from" "3.55.0" + tslib "^2.3.1" + "@aws-sdk/invalid-dependency@3.55.0": version "3.55.0" resolved "https://registry.yarnpkg.com/@aws-sdk/invalid-dependency/-/invalid-dependency-3.55.0.tgz#5406c80e4be534700b92b61c21a74efd754c9492" @@ -387,6 +583,14 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/invalid-dependency@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/invalid-dependency/-/invalid-dependency-3.78.0.tgz#c4e30871d69894dbf3450023319385110ce95c81" + integrity sha512-zUo+PbeRMN/Mzj6y+6p9qqk/znuFetT1gmpOcZGL9Rp2T+b9WJWd+daq5ktsL10sVCzIt2UvneJRz6b+aU+bfw== + dependencies: + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/is-array-buffer@3.55.0": version "3.55.0" resolved "https://registry.yarnpkg.com/@aws-sdk/is-array-buffer/-/is-array-buffer-3.55.0.tgz#c46122c5636f01d5895e5256a587768c3425ea7a" @@ -403,6 +607,15 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/middleware-content-length@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-content-length/-/middleware-content-length-3.78.0.tgz#57d46be61d1176d4c5fce7ba4b0682798c170208" + integrity sha512-5MpKt6lB9TdFy25/AGrpOjPY0iDHZAKpEHc+jSOJBXLl6xunXA7qHdiYaVqkWodLxy70nIckGNHqQ3drabidkA== + dependencies: + "@aws-sdk/protocol-http" "3.78.0" + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/middleware-host-header@3.58.0": version "3.58.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.58.0.tgz#c7fe87ed16306e328e780bbed282dbf31d605236" @@ -412,6 +625,15 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/middleware-host-header@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.78.0.tgz#9130d176c2839bc658aff01bf2a36fee705f0e86" + integrity sha512-1zL8uaDWGmH50c8B8jjz75e0ePj6/3QeZEhjJgTgL6DTdiqvRt32p3t+XWHW+yDI14fZZUYeTklAaLVxqFrHqQ== + dependencies: + "@aws-sdk/protocol-http" "3.78.0" + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/middleware-logger@3.55.0": version "3.55.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.55.0.tgz#83adc985a3a98493519384565e0c1a06552b8704" @@ -420,6 +642,14 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/middleware-logger@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.78.0.tgz#758b84711213b2e78afe0df20bc2d4d70a856da1" + integrity sha512-GBhwxNjhCJUIeQQDaGasX/C23Jay77al2vRyGwmxf8no0DdFsa4J1Ik6/2hhIqkqko+WM4SpCnpZrY4MtnxNvA== + dependencies: + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/middleware-retry@3.58.0": version "3.58.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-retry/-/middleware-retry-3.58.0.tgz#967518e5b9e55546dcb5de0dfe5784df71807d72" @@ -432,6 +662,18 @@ tslib "^2.3.1" uuid "^8.3.2" +"@aws-sdk/middleware-retry@3.80.0": + version "3.80.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-retry/-/middleware-retry-3.80.0.tgz#d62ebd68ded78bdaf0a8b07bb4cc1c394c99cc8f" + integrity sha512-CTk+tA4+WMUNOcUfR6UQrkhwvPYFpnMsQ1vuHlpLFOGG3nCqywA2hueLMRQmVcDXzP0sGeygce6dzRI9dJB/GA== + dependencies: + "@aws-sdk/protocol-http" "3.78.0" + "@aws-sdk/service-error-classification" "3.78.0" + "@aws-sdk/types" "3.78.0" + "@aws-sdk/util-middleware" "3.78.0" + tslib "^2.3.1" + uuid "^8.3.2" + "@aws-sdk/middleware-sdk-sts@3.58.0": version "3.58.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.58.0.tgz#5b433a49d2aeb10120805d0f13f6700153d55ec9" @@ -444,6 +686,18 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/middleware-sdk-sts@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.78.0.tgz#15d91c421380f748b58bb006e1c398cfdf59b290" + integrity sha512-Lu/kN0J0/Kt0ON1hvwNel+y8yvf35licfIgtedHbBCa/ju8qQ9j+uL9Lla6Y5Tqu29yVaye1JxhiIDhscSwrLA== + dependencies: + "@aws-sdk/middleware-signing" "3.78.0" + "@aws-sdk/property-provider" "3.78.0" + "@aws-sdk/protocol-http" "3.78.0" + "@aws-sdk/signature-v4" "3.78.0" + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/middleware-serde@3.55.0": version "3.55.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-serde/-/middleware-serde-3.55.0.tgz#326a0696255868a9dfca7c482a616897e9d54fdf" @@ -452,6 +706,14 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/middleware-serde@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-serde/-/middleware-serde-3.78.0.tgz#d1e1a7b9ac58638b973e533ac4c2ca52f413883c" + integrity sha512-4DPsNOxsl1bxRzfo1WXEZjmD7OEi7qGNpxrDWucVe96Fqj2dH08jR8wxvBIVV1e6bAad07IwdPuCGmivNvwRuQ== + dependencies: + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/middleware-signing@3.58.0": version "3.58.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.58.0.tgz#996828122526ec5f95e6e898a6573791db4cd5e1" @@ -463,6 +725,17 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/middleware-signing@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.78.0.tgz#2fb41819a9ae0953cf8f428851a57696442469ca" + integrity sha512-OEjJJCNhHHSOprLZ9CzjHIXEKFtPHWP/bG9pMhkV3/6Bmscsgcf8gWHcOnmIrjqX+hT1VALDNpl/RIh0J6/eQw== + dependencies: + "@aws-sdk/property-provider" "3.78.0" + "@aws-sdk/protocol-http" "3.78.0" + "@aws-sdk/signature-v4" "3.78.0" + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/middleware-stack@3.55.0": version "3.55.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-stack/-/middleware-stack-3.55.0.tgz#e99ffb0bdd6861ec3b5a667561dc41dfcb44d36b" @@ -470,6 +743,13 @@ dependencies: tslib "^2.3.1" +"@aws-sdk/middleware-stack@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-stack/-/middleware-stack-3.78.0.tgz#e9f42039e500bed23ec74359924ae16e7bf9c77a" + integrity sha512-UoNfRh6eAJN3BJHlG1eb+KeuSe+zARTC2cglroJRyHc2j7GxH2i9FD3IJbj5wvzopJEnQzuY/VCs6STFkqWL1g== + dependencies: + tslib "^2.3.1" + "@aws-sdk/middleware-user-agent@3.58.0": version "3.58.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.58.0.tgz#c60b83f61ed385989e0be5dc80b05a8d5626bbf8" @@ -479,6 +759,15 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/middleware-user-agent@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.78.0.tgz#e4c7345d26d718de0e84b60ba02b2b08b566fa15" + integrity sha512-wdN5uoq8RxxhLhj0EPeuDSRFuXfUwKeEqRzCKMsYAOC0cAm+PryaP2leo0oTGJ9LUK8REK7zyfFcmtC4oOzlkA== + dependencies: + "@aws-sdk/protocol-http" "3.78.0" + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/node-config-provider@3.58.0": version "3.58.0" resolved "https://registry.yarnpkg.com/@aws-sdk/node-config-provider/-/node-config-provider-3.58.0.tgz#1a138c571f6b2608cff49a64f4f2936971734f1e" @@ -489,6 +778,16 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/node-config-provider@3.80.0": + version "3.80.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/node-config-provider/-/node-config-provider-3.80.0.tgz#dbb02aa48fb1a0acc3201ca73db5bbf1738895b5" + integrity sha512-vyTOMK04huB7n10ZUv0thd2TE6KlY8livOuLqFTMtj99AJ6vyeB5XBNwKnQtJIt/P7CijYgp8KcFvI9fndOmKg== + dependencies: + "@aws-sdk/property-provider" "3.78.0" + "@aws-sdk/shared-ini-file-loader" "3.80.0" + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/node-http-handler@3.58.0": version "3.58.0" resolved "https://registry.yarnpkg.com/@aws-sdk/node-http-handler/-/node-http-handler-3.58.0.tgz#bb633b51a205181657bfc59b24b7bf1720b7e652" @@ -500,6 +799,17 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/node-http-handler@3.82.0": + version "3.82.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/node-http-handler/-/node-http-handler-3.82.0.tgz#e28064815c6c6caf22a16bb7fee4e9e7e73ef3bb" + integrity sha512-yyq/DA/IMzL4fLJhV7zVfP7aUQWPHfOKTCJjWB3KeV5YPiviJtSKb/KyzNi+gQyO7SmsL/8vQbQrf3/s7N/2OA== + dependencies: + "@aws-sdk/abort-controller" "3.78.0" + "@aws-sdk/protocol-http" "3.78.0" + "@aws-sdk/querystring-builder" "3.78.0" + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/property-provider@3.55.0": version "3.55.0" resolved "https://registry.yarnpkg.com/@aws-sdk/property-provider/-/property-provider-3.55.0.tgz#0eabe5e84d9258c85c2c5e44bcb09379ae9429d2" @@ -508,6 +818,14 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/property-provider@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/property-provider/-/property-provider-3.78.0.tgz#f12341fa87da2b54daac95f623bf7ede1754f8ae" + integrity sha512-PZpLvV0hF6lqg3CSN9YmphrB/t5LVJVWGJLB9d9qm7sJs5ksjTYBb5bY91OQ3zit0F4cqBMU8xt2GQ9J6d4DvQ== + dependencies: + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/protocol-http@3.58.0": version "3.58.0" resolved "https://registry.yarnpkg.com/@aws-sdk/protocol-http/-/protocol-http-3.58.0.tgz#170798abcc97884d4beabc4dbbdfe3b41acd2d0a" @@ -516,6 +834,14 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/protocol-http@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/protocol-http/-/protocol-http-3.78.0.tgz#8a30db90e3373fe94e2b0007c3cba47b5c9e08bd" + integrity sha512-SQB26MhEK96yDxyXd3UAaxLz1Y/ZvgE4pzv7V3wZiokdEedM0kawHKEn1UQJlqJLEZcQI9QYyysh3rTvHZ3fyg== + dependencies: + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/querystring-builder@3.55.0": version "3.55.0" resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-builder/-/querystring-builder-3.55.0.tgz#7d6d4e2c597eb3d636bd3a368b494dac175ba329" @@ -525,6 +851,15 @@ "@aws-sdk/util-uri-escape" "3.55.0" tslib "^2.3.1" +"@aws-sdk/querystring-builder@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-builder/-/querystring-builder-3.78.0.tgz#29068c4d1fad056e26f848779a31335469cb0038" + integrity sha512-aib6RW1WAaTQDqVgRU1Ku9idkhm90gJKbCxVaGId+as6QHNUqMChEfK2v+0afuKiPNOs5uWmqvOXI9+Gt+UGDg== + dependencies: + "@aws-sdk/types" "3.78.0" + "@aws-sdk/util-uri-escape" "3.55.0" + tslib "^2.3.1" + "@aws-sdk/querystring-parser@3.55.0": version "3.55.0" resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-parser/-/querystring-parser-3.55.0.tgz#ea35642c1b8324dd896d45185f99ad9d6c3af6d2" @@ -533,11 +868,24 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/querystring-parser@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-parser/-/querystring-parser-3.78.0.tgz#4c76fe15ef2e9bbf4c387c83889d1c25d2c3a614" + integrity sha512-csaH8YTyN+KMNczeK6fBS8l7iJaqcQcKOIbpQFg5upX4Ly5A56HJn4sVQhY1LSgfSk4xRsNfMy5mu6BlsIiaXA== + dependencies: + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/service-error-classification@3.55.0": version "3.55.0" resolved "https://registry.yarnpkg.com/@aws-sdk/service-error-classification/-/service-error-classification-3.55.0.tgz#4a85d2d947102c50076bd2af295f62abd74e26ab" integrity sha512-HdjnDyarsa1Avq1MJurkLyEe9c3eRa76dPmK4TmRGgwJ+tInEzGHL0rBW7V8xBK+PDF+fJQ71hvm8jPYmzvBwQ== +"@aws-sdk/service-error-classification@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/service-error-classification/-/service-error-classification-3.78.0.tgz#8d3ac1064e39c180d9b764bb838c7f9de5615281" + integrity sha512-x7Lx8KWctJa01q4Q72Zb4ol9L/era3vy2daASu8l2paHHxsAPBE0PThkvLdUSLZSzlHSVdh3YHESIsT++VsK4w== + "@aws-sdk/shared-ini-file-loader@3.58.0": version "3.58.0" resolved "https://registry.yarnpkg.com/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.58.0.tgz#321f80f34ef3f15ab40b756fb5ee2797812748c7" @@ -545,6 +893,13 @@ dependencies: tslib "^2.3.1" +"@aws-sdk/shared-ini-file-loader@3.80.0": + version "3.80.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.80.0.tgz#e3d1b0532e9a884e52f967717ba2666ca32bbd74" + integrity sha512-3d5EBJjnWWkjLK9skqLLHYbagtFaZZy+3jUTlbTuOKhlOwe8jF7CUM3j6I4JA6yXNcB3w0exDKKHa8w+l+05aA== + dependencies: + tslib "^2.3.1" + "@aws-sdk/signature-v4@3.58.0": version "3.58.0" resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4/-/signature-v4-3.58.0.tgz#0d81dd317f9bf35bc0de670c0e534d7793f8e170" @@ -557,6 +912,18 @@ "@aws-sdk/util-uri-escape" "3.55.0" tslib "^2.3.1" +"@aws-sdk/signature-v4@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4/-/signature-v4-3.78.0.tgz#adb735b9604d4bb8e44d16f1baa87618d576013b" + integrity sha512-eePjRYuzKoi3VMr/lgrUEF1ytLeH4fA/NMCykr/uR6NMo4bSJA59KrFLYSM7SlWLRIyB0UvJqygVEvSxFluyDw== + dependencies: + "@aws-sdk/is-array-buffer" "3.55.0" + "@aws-sdk/types" "3.78.0" + "@aws-sdk/util-hex-encoding" "3.58.0" + "@aws-sdk/util-middleware" "3.78.0" + "@aws-sdk/util-uri-escape" "3.55.0" + tslib "^2.3.1" + "@aws-sdk/smithy-client@3.55.0": version "3.55.0" resolved "https://registry.yarnpkg.com/@aws-sdk/smithy-client/-/smithy-client-3.55.0.tgz#bf1f5a64d1d2374c291338a52f6c75c6d67e8148" @@ -566,11 +933,25 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/smithy-client@3.85.0": + version "3.85.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/smithy-client/-/smithy-client-3.85.0.tgz#70852daa14fef9af1adfb4411237026cb68943da" + integrity sha512-Ox/yQEAnANzhpJMyrpuxWtF/i3EviavENczT7fo4uwSyZTz/sfSBQNjs/YAG1UeA6uOI3pBP5EaFERV5hr2fRA== + dependencies: + "@aws-sdk/middleware-stack" "3.78.0" + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/types@3.55.0", "@aws-sdk/types@^3.1.0": version "3.55.0" resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.55.0.tgz#d524d567e2b2722f2d6be83e2417dd6d46ce1490" integrity sha512-wrDZjuy1CVAYxDCbm3bWQIKMGfNs7XXmG0eG4858Ixgqmq2avsIn5TORy8ynBxcXn9aekV/+tGEQ7BBSYzIVNQ== +"@aws-sdk/types@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.78.0.tgz#51dc80b2142ee20821fb9f476bdca6e541021443" + integrity sha512-I9PTlVNSbwhIgMfmDM5as1tqRIkVZunjVmfogb2WVVPp4CaX0Ll01S0FSMSLL9k6tcQLXqh45pFRjrxCl9WKdQ== + "@aws-sdk/url-parser@3.55.0": version "3.55.0" resolved "https://registry.yarnpkg.com/@aws-sdk/url-parser/-/url-parser-3.55.0.tgz#03b47a45c591d52c9d00dc40c630b91094991fe7" @@ -580,6 +961,15 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/url-parser@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/url-parser/-/url-parser-3.78.0.tgz#8903011fda4b04c1207df099a21eda1304573099" + integrity sha512-iQn2AjECUoJE0Ae9XtgHtGGKvUkvE8hhbktGopdj+zsPBe4WrBN2DgVxlKPPrBonG/YlcL1D7a5EXaujWSlUUw== + dependencies: + "@aws-sdk/querystring-parser" "3.78.0" + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/util-base64-browser@3.58.0": version "3.58.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-base64-browser/-/util-base64-browser-3.58.0.tgz#e213f91a5d40dd2d048d340f1ab192ca86c1f40c" @@ -634,6 +1024,16 @@ bowser "^2.11.0" tslib "^2.3.1" +"@aws-sdk/util-defaults-mode-browser@3.85.0": + version "3.85.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.85.0.tgz#215e99e8815f885ce722668a0e5afbbca69fa964" + integrity sha512-oqK/e2pHuMWrvTJWtDBzylbj232ezlTay5dCq4RQlyi3LPPVBQ08haYD1Mk2ikQ/qa0XvbSD6YVhjpTlvwRNjw== + dependencies: + "@aws-sdk/property-provider" "3.78.0" + "@aws-sdk/types" "3.78.0" + bowser "^2.11.0" + tslib "^2.3.1" + "@aws-sdk/util-defaults-mode-node@3.58.0": version "3.58.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.58.0.tgz#57bb445172f10b681f34a7d382d420b9053b2122" @@ -646,6 +1046,18 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/util-defaults-mode-node@3.85.0": + version "3.85.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.85.0.tgz#8cd27ea50ddce298ec586d36eb6379ba14d7bfaf" + integrity sha512-KDNl4H8jJJLh6y7I3MSwRKe4plKbFKK8MVkS0+Fce/GJh4EnqxF0HzMMaSeNUcPvO2wHRq2a60+XW+0d7eWo1A== + dependencies: + "@aws-sdk/config-resolver" "3.80.0" + "@aws-sdk/credential-provider-imds" "3.81.0" + "@aws-sdk/node-config-provider" "3.80.0" + "@aws-sdk/property-provider" "3.78.0" + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/util-hex-encoding@3.58.0": version "3.58.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.58.0.tgz#d999eb19329933a94563881540a06d7ac7f515f5" @@ -667,6 +1079,13 @@ dependencies: tslib "^2.3.1" +"@aws-sdk/util-middleware@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-middleware/-/util-middleware-3.78.0.tgz#d907a9b8b7878265cd3e3ee15996bc17de41db11" + integrity sha512-Hi3wv2b0VogO4mzyeEaeU5KgIt4qeo0LXU5gS6oRrG0T7s2FyKbMBkJW3YDh/Y8fNwqArZ+/QQFujpP0PIKwkA== + dependencies: + tslib "^2.3.1" + "@aws-sdk/util-uri-escape@3.55.0": version "3.55.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-uri-escape/-/util-uri-escape-3.55.0.tgz#ee57743c628a1c9f942dfe73205ce890ec011916" @@ -683,6 +1102,15 @@ bowser "^2.11.0" tslib "^2.3.1" +"@aws-sdk/util-user-agent-browser@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.78.0.tgz#12509ed9cc77624da0e0c017099565e37a5038d0" + integrity sha512-diGO/Bf4ggBOEnfD7lrrXaaXOwOXGz0bAJ0HhpizwEMlBld5zfDlWXjNpslh+8+u3EHRjPJQ16KGT6mp/Dm+aw== + dependencies: + "@aws-sdk/types" "3.78.0" + bowser "^2.11.0" + tslib "^2.3.1" + "@aws-sdk/util-user-agent-node@3.58.0": version "3.58.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.58.0.tgz#ea821601b0d2c7d81239ad0de60964f3967f06ac" @@ -692,6 +1120,15 @@ "@aws-sdk/types" "3.55.0" tslib "^2.3.1" +"@aws-sdk/util-user-agent-node@3.80.0": + version "3.80.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.80.0.tgz#269ea0f9bfab4f378af759afa9137936081f010a" + integrity sha512-QV26qIXws1m6sZXg65NS+XrQ5NhAzbDVQLtEVE4nC39UN8fuieP6Uet/gZm9mlLI9hllwvcV7EfgBM3GSC7pZg== + dependencies: + "@aws-sdk/node-config-provider" "3.80.0" + "@aws-sdk/types" "3.78.0" + tslib "^2.3.1" + "@aws-sdk/util-utf8-browser@3.55.0", "@aws-sdk/util-utf8-browser@^3.0.0": version "3.55.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.55.0.tgz#a045bf1a93f6e0ff9c846631b168ea55bbb37668" @@ -707,13 +1144,13 @@ "@aws-sdk/util-buffer-from" "3.55.0" tslib "^2.3.1" -"@aws-sdk/util-waiter@3.55.0": - version "3.55.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-waiter/-/util-waiter-3.55.0.tgz#0e48a8ce98931f99cfbcad750222fd1f0b237fda" - integrity sha512-Do34MKPFSC/+zVN6vY+FZ+0WN61hzga4nPoAC590AOjs8rW6/H6sDN6Gz1KAZbPnuQUZfvsIJjMxN7lblXHJkQ== +"@aws-sdk/util-waiter@3.78.0": + version "3.78.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-waiter/-/util-waiter-3.78.0.tgz#5886f3e06ae6df9a12ef7079a6e75c76921ea4da" + integrity sha512-8pWd0XiNOS8AkWQyac8VNEI+gz/cGWlC2TAE2CJp0rOK5XhvlcNBINai4D6TxQ+9foyJXLOI1b8nuXemekoG8A== dependencies: - "@aws-sdk/abort-controller" "3.55.0" - "@aws-sdk/types" "3.55.0" + "@aws-sdk/abort-controller" "3.78.0" + "@aws-sdk/types" "3.78.0" tslib "^2.3.1" "@awsui/collection-hooks@^1.0.0": @@ -4552,10 +4989,10 @@ resolve-from "^5.0.0" store2 "^2.12.0" -"@synchro-charts/core@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@synchro-charts/core/-/core-4.0.1.tgz#67a0070e6cafa91660764278c01e2ff362459807" - integrity sha512-Y+QmZU+bs+3dzXD6tJNDZh4AeBi19kbLda9psUrxx0+uomzeUIcJ+ntNHA5N1lvaPXMYQS6IbOdgbhR373emWA== +"@synchro-charts/core@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@synchro-charts/core/-/core-5.0.0.tgz#354cae51fbf8de1994de06de135670c70e713a58" + integrity sha512-zJ7vALPFw9tHCccDWUWFcpR3As7QY380CNGFUgS5ZOn49W1F++VySsGL64XsJYGjoU4/rk5/hAA+BXhsn54dtA== dependencies: "@stencil/redux" "^0.1.1" "@types/d3" "^5.16.4" @@ -9590,6 +10027,11 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +dataloader@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.1.0.tgz#c69c538235e85e7ac6c6c444bae8ecabf5de9df7" + integrity sha512-qTcEYLen3r7ojZNgVUaRggOI+KM7jrKxXeSHhogh/TWxYMeONEMqY+hmkobiYQozsGIyg9OYVzO4ZIfoB4I0pQ== + date-fns@^1.27.2: version "1.30.1" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"