-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add Asset Hierarchy loading & Asset Tree support
The PR allows access to the Asset Hierarchy as a tree
- Loading branch information
1 parent
b4fde90
commit 6adc67e
Showing
21 changed files
with
1,221 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
93 changes: 93 additions & 0 deletions
93
packages/components/src/components/iot-asset-tree-demo/iot-asset-tree-demo.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { Component, h, Prop, State, Watch } from '@stencil/core'; | ||
import { | ||
getSiteWiseAssetModule, | ||
SiteWiseAssetTreeModule, | ||
SiteWiseAssetTreeQuery, | ||
SiteWiseAssetTreeNode, | ||
SiteWiseAssetTreeSession, | ||
BranchReference, | ||
AssetTreeSubscription, | ||
HierarchyGroup, | ||
} from '@iot-app-kit/core'; | ||
|
||
@Component({ | ||
tag: 'iot-asset-tree-demo', | ||
shadow: false, | ||
}) | ||
export class IotAssetTreeDemo { | ||
@Prop() query: SiteWiseAssetTreeQuery; | ||
@Prop() subscription: AssetTreeSubscription; | ||
@State() roots: SiteWiseAssetTreeNode[] = []; | ||
|
||
componentDidLoad() { | ||
// TODO: this needs to be done elsewhere... | ||
let session: SiteWiseAssetTreeSession = new SiteWiseAssetTreeModule(getSiteWiseAssetModule()).startSession( | ||
this.query | ||
); | ||
this.subscription = session.subscribe((newTree) => { | ||
this.roots = newTree; | ||
// check the tree for any new unexpanded nodes and expand them: | ||
this.expandNodes(newTree); | ||
}); | ||
} | ||
|
||
expandNodes(nodes: SiteWiseAssetTreeNode[]) { | ||
nodes.forEach((node) => { | ||
Array.from(node.hierarchies.values()).forEach((hierarchyGroup) => { | ||
if (!hierarchyGroup.isExpanded) { | ||
this.subscription.expand(new BranchReference(node.asset.id, hierarchyGroup.id)); | ||
} | ||
this.expandNodes(hierarchyGroup.children); | ||
}); | ||
}); | ||
} | ||
|
||
componentWillUnmount() { | ||
this.subscription.unsubscribe(); | ||
} | ||
|
||
render() { | ||
return ( | ||
<div> | ||
<h1>Tree Demo</h1> | ||
{this.renderAssetList(this.roots)} | ||
</div> | ||
); | ||
} | ||
|
||
renderAssetList(assets: SiteWiseAssetTreeNode[]) { | ||
if (!assets) { | ||
return ''; | ||
} | ||
|
||
return <ul>{assets.map((asset) => this.renderAsset(asset))}</ul>; | ||
} | ||
|
||
renderAsset(assetNode: SiteWiseAssetTreeNode) { | ||
return ( | ||
<li key={'asset-' + assetNode.asset?.id}> | ||
{assetNode.asset?.name} | ||
{this.renderHierarchies(assetNode)} | ||
</li> | ||
); | ||
} | ||
|
||
renderHierarchies(node: SiteWiseAssetTreeNode) { | ||
if (!node.hierarchies || !node.hierarchies.size) { | ||
return; | ||
} | ||
|
||
return <ul>{Array.from(node.hierarchies.values()).map((hierarchy) => this.renderHierarchy(hierarchy))}</ul>; | ||
} | ||
|
||
renderHierarchy(hierarchy: HierarchyGroup) { | ||
if (hierarchy.children) { | ||
return ( | ||
<li key={'hierarchy-' + hierarchy.id}> | ||
{hierarchy.name} | ||
{this.renderAssetList(hierarchy?.children)} | ||
</li> | ||
); | ||
} | ||
} | ||
} |
4 changes: 2 additions & 2 deletions
4
packages/components/src/testing/testing-ground/siteWiseQueries.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
export * from './sitewise/types'; | ||
export * from './sitewise/siteWiseAssetModule'; | ||
export * from './sitewise/session'; | ||
export * from './sitewise-asset-tree/types'; | ||
export * from './sitewise-asset-tree/assetTreeModule'; | ||
export * from './sitewise-asset-tree/assetTreeSession'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import { | ||
AssetHierarchyQuery, assetHierarchyQueryKey, | ||
AssetModelQuery, | ||
AssetPropertyValueQuery, | ||
AssetSummaryQuery, | ||
HierarchyAssetSummaryList, | ||
isAssetHierarchyQuery, | ||
isAssetModelQuery, | ||
isAssetPropertyValueQuery, isAssetSummaryQuery, | ||
SiteWiseAssetModuleInterface, | ||
SiteWiseAssetSessionInterface | ||
} from './sitewise/types'; | ||
import { AssetState, DescribeAssetModelResponse, DescribeAssetResponse, Quality } from '@aws-sdk/client-iotsitewise'; | ||
import { Observable, Subscription } from 'rxjs'; | ||
import { AssetPropertyValue, AssetSummary } from '@aws-sdk/client-iotsitewise/dist-types/ts3.4'; | ||
|
||
export const ASSET_ID = 'assetABC123'; | ||
export const ASSET_MODEL_ID = 'assetModelABC123'; | ||
export const ASSET_PROPERTY_ID = 'assetPropertyIdAbc123'; | ||
export const HIERARCHY_ID = 'hierarchyIdAbc123'; | ||
export const creationDate: Date = new Date(2000, 0, 0); | ||
export const lastUpdatedDate: Date = new Date(2021, 0, 0); | ||
export const sampleAssetSummary: AssetSummary = { | ||
id: ASSET_ID, | ||
assetModelId: ASSET_MODEL_ID, | ||
name: 'assetName', | ||
arn: 'arn:assetArn', | ||
creationDate: creationDate, | ||
lastUpdateDate: lastUpdatedDate, | ||
hierarchies: [], | ||
status: { | ||
error: { | ||
code: undefined, | ||
details: undefined, | ||
message: undefined, | ||
}, | ||
state: AssetState.ACTIVE, | ||
}, | ||
}; | ||
export const sampleAssetDescription: DescribeAssetResponse = { | ||
assetId: ASSET_ID, | ||
assetModelId: ASSET_MODEL_ID, | ||
assetName: 'assetName', | ||
assetArn: 'arn:assetArn', | ||
assetCreationDate: creationDate, | ||
assetLastUpdateDate: lastUpdatedDate, | ||
assetHierarchies: [], | ||
assetStatus: { | ||
error: { | ||
code: undefined, | ||
details: undefined, | ||
message: undefined, | ||
}, | ||
state: AssetState.ACTIVE, | ||
}, | ||
assetCompositeModels: [], | ||
assetProperties: [], | ||
}; | ||
export const sampleAssetModel: DescribeAssetModelResponse = { | ||
assetModelId: ASSET_MODEL_ID, | ||
assetModelName: 'Asset Model Name', | ||
assetModelDescription: 'a happy little asset model', | ||
assetModelArn: 'arn:assetModelArn', | ||
assetModelCreationDate: creationDate, | ||
assetModelLastUpdateDate: lastUpdatedDate, | ||
assetModelProperties: [], | ||
assetModelCompositeModels: [], | ||
assetModelHierarchies: [], | ||
assetModelStatus: { | ||
error: { | ||
code: undefined, | ||
details: undefined, | ||
message: undefined, | ||
}, | ||
state: AssetState.ACTIVE, | ||
}, | ||
}; | ||
export const samplePropertyValue: AssetPropertyValue = { | ||
value: { stringValue: undefined, booleanValue: undefined, doubleValue: undefined, integerValue: 1234 }, | ||
quality: Quality.GOOD, | ||
timestamp: { | ||
timeInSeconds: 100, | ||
offsetInNanos: 100, | ||
}, | ||
}; | ||
|
||
export class MockSiteWiseAssetsReplayData { | ||
public models: Map<string, DescribeAssetModelResponse> = new Map<string, DescribeAssetModelResponse>(); | ||
public hierarchies: Map<string, HierarchyAssetSummaryList> = new Map<string, HierarchyAssetSummaryList>(); | ||
public properties: Map<string, AssetPropertyValue> = new Map<string, AssetPropertyValue>(); | ||
public assets: Map<string, AssetSummary> = new Map<string, AssetSummary>(); | ||
|
||
public addAssetModels(newModels: DescribeAssetModelResponse[]) { | ||
newModels.forEach(model => this.models.set(model.assetModelId as string, model)); | ||
} | ||
|
||
public addAssetSummaries(newAssetSummaries: AssetSummary[]) { | ||
newAssetSummaries.forEach(summary => this.assets.set(summary.id as string, summary)); | ||
} | ||
|
||
public addAssetPropertyValues(propertyValue: {assetId: string, propertyId: string, value: AssetPropertyValue}) { | ||
this.properties.set(propertyValue.assetId + ':' + propertyValue.propertyId, propertyValue.value); | ||
} | ||
|
||
public addHierarchyAssetSummaryList(query: AssetHierarchyQuery, newHierarchyAssetSummaryList: HierarchyAssetSummaryList) { | ||
this.hierarchies.set(assetHierarchyQueryKey(query), newHierarchyAssetSummaryList); | ||
} | ||
} | ||
|
||
|
||
export class MockSiteWiseAssetSession implements SiteWiseAssetSessionInterface { | ||
private readonly replayData: MockSiteWiseAssetsReplayData; | ||
|
||
constructor(replayData: MockSiteWiseAssetsReplayData) { | ||
this.replayData = replayData; | ||
} | ||
|
||
addRequest(query: AssetModelQuery, observer: (assetModel: DescribeAssetModelResponse) => void): Subscription; | ||
addRequest(query: AssetPropertyValueQuery, observer: (assetPropertyValue: AssetPropertyValue) => void): Subscription; | ||
addRequest(query: AssetHierarchyQuery, observer: (assetSummary: HierarchyAssetSummaryList) => void): Subscription; | ||
addRequest(query: AssetSummaryQuery, observer: (assetSummary: AssetSummary) => void): Subscription; | ||
addRequest(query: AssetModelQuery | AssetPropertyValueQuery | AssetHierarchyQuery | AssetSummaryQuery, | ||
observer: ((assetModel: DescribeAssetModelResponse) => void) | ||
| ((assetPropertyValue: AssetPropertyValue) => void) | ||
| ((assetSummary: HierarchyAssetSummaryList) => void) | ||
| ((assetSummary: AssetSummary) => void)): Subscription { | ||
let observable: Observable<any>; | ||
if (isAssetModelQuery(query)) { | ||
observable = new Observable<DescribeAssetModelResponse>((observer) => { | ||
observer.next(this.replayData.models.get(query.assetModelId)); | ||
}); | ||
} else if (isAssetPropertyValueQuery(query)) { | ||
observable = new Observable<AssetPropertyValue>((observer) => { | ||
observer.next(this.replayData.properties.get(query.assetId + ':' + query.propertyId)); | ||
}); | ||
} else if (isAssetHierarchyQuery(query)) { | ||
observable = new Observable<HierarchyAssetSummaryList>((observer) => { | ||
observer.next(this.replayData.hierarchies.get(assetHierarchyQueryKey(query))); | ||
}); | ||
} else if (isAssetSummaryQuery(query)) { | ||
observable = new Observable<AssetSummary>((observer) => { | ||
observer.next(this.replayData.assets.get(query.assetId)); | ||
}); | ||
} else { | ||
throw 'Unexpected request type: the type of the request object could not be determined'; | ||
} | ||
|
||
return observable.subscribe(observer); | ||
} | ||
|
||
close(): void { | ||
} | ||
} | ||
|
||
export class MockSiteWiseAssetModule implements SiteWiseAssetModuleInterface { | ||
private readonly replayData: MockSiteWiseAssetsReplayData; | ||
|
||
constructor(replayData: MockSiteWiseAssetsReplayData) { | ||
this.replayData = replayData; | ||
} | ||
|
||
startSession(): SiteWiseAssetSessionInterface { | ||
return new MockSiteWiseAssetSession(this.replayData); | ||
} | ||
} | ||
|
||
it('no-op', () => { expect(true).toBeTruthy()}); |
27 changes: 27 additions & 0 deletions
27
packages/core/src/asset-modules/sitewise-asset-tree/assetTreeModule.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { SiteWiseAssetTreeModule } from './assetTreeModule'; | ||
import { MockSiteWiseAssetModule, MockSiteWiseAssetsReplayData, sampleAssetSummary } from '../mocks.spec'; | ||
import { HIERARCHY_ROOT_ID, HierarchyAssetSummaryList, LoadingStateEnum } from '../sitewise/types'; | ||
|
||
it('initializes', () => { | ||
expect( | ||
() => | ||
new SiteWiseAssetTreeModule(new MockSiteWiseAssetModule(new MockSiteWiseAssetsReplayData())) | ||
).not.toThrowError(); | ||
}); | ||
|
||
it('returns a session', () => { | ||
let replayData = new MockSiteWiseAssetsReplayData(); | ||
let testData:HierarchyAssetSummaryList = { | ||
assetHierarchyId: HIERARCHY_ROOT_ID, | ||
assets: [sampleAssetSummary], | ||
loadingState: LoadingStateEnum.LOADED | ||
} | ||
replayData.addHierarchyAssetSummaryList({assetHierarchyId: HIERARCHY_ROOT_ID}, testData); | ||
replayData.addAssetSummaries([sampleAssetSummary]); | ||
expect( | ||
() => | ||
new SiteWiseAssetTreeModule(new MockSiteWiseAssetModule(replayData)) | ||
.startSession({rootAssetId: undefined}) | ||
).not.toBeUndefined(); | ||
}); | ||
|
16 changes: 16 additions & 0 deletions
16
packages/core/src/asset-modules/sitewise-asset-tree/assetTreeModule.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { SiteWiseAssetTreeQuery } from './types'; | ||
import { SiteWiseAssetModule } from '../sitewise/siteWiseAssetModule'; | ||
import { SiteWiseAssetTreeSession } from './assetTreeSession'; | ||
import { SiteWiseAssetModuleInterface } from '../sitewise/types'; | ||
|
||
export class SiteWiseAssetTreeModule { | ||
private assetModule: SiteWiseAssetModuleInterface; | ||
|
||
constructor(assetModule: SiteWiseAssetModuleInterface) { | ||
this.assetModule = assetModule; | ||
} | ||
|
||
public startSession(query: SiteWiseAssetTreeQuery) { | ||
return new SiteWiseAssetTreeSession(this.assetModule.startSession(), query); | ||
} | ||
} |
Oops, something went wrong.