Skip to content

Commit d85fc48

Browse files
authored
Merge pull request #101 from kuzzleio/0.4.0-proposal
# [0.4.0](https://github.com/kuzzleio/kuzzle-plugin-device-manager/releases/tag/0.4.0) (2021-10-06) #### New features - [ [#100](#100) ] Add historization ([Aschen](https://github.com/Aschen)) ---
2 parents 5812b9f + 9b6ed84 commit d85fc48

File tree

23 files changed

+580
-249
lines changed

23 files changed

+580
-249
lines changed
-389 KB
Loading

doc/1/guides/assets/index.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ An asset is uniquely identified by the `type` + `model` + `reference` triplet.
3131
"qos": {
3232
"battery": 2.3
3333
},
34-
34+
3535
"latitude": 41.074688,
3636
"longitude": 28.9800192,
3737
"accuracy": 42,
@@ -85,3 +85,27 @@ By default, for each measurement type the following information are copied in ad
8585
```
8686

8787
It is possible to override the [Decoder.copyToAsset](/official-plugins/device-manager/1/classes/decoder/copy-to-asset) method to choose what to copy into the asset.
88+
89+
## Historization
90+
91+
Assets are historized in the `assets-history` collection when a new measure is received.
92+
93+
Before historization, the `tenant:<tenant-id>:asset:measure:new` event is emitted. This event allows to enrich the asset before it is historized.
94+
95+
The payload contains the new `measures` and the `assetId`.
96+
97+
The content of `request.result.asset` will be used to update the asset measures and then create the history document.
98+
99+
```js
100+
app.pipe.register(`tenant:<tenant-id>:asset:measures:new`, async (request: KuzzleRequest) => {
101+
const measures: AssetMeasures = request.result.asset.measures;
102+
const assetId: string = request.result.assetId;
103+
104+
request.result.asset.metadata = {
105+
enriched: true,
106+
assetId: request.result.assetId
107+
};
108+
109+
return request;
110+
});
111+
```

features/Engine.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Feature: Engine Controller
55
When I successfully execute the action "collection":"list" with args:
66
| index | "tenant-kuzzle" |
77
Then I should receive a result matching:
8-
| collections | [{"name":"assets","type":"stored"},{"name":"config","type":"stored"},{"name":"devices","type":"stored"}] |
8+
| collections | [{"name":"assets","type":"stored"},{"name":"assets-history","type":"stored"}, {"name":"config","type":"stored"},{"name":"devices","type":"stored"}] |
99
When I successfully execute the action "device-manager/engine":"delete" with args:
1010
| index | "tenant-kuzzle" |
1111
And I successfully execute the action "collection":"list" with args:

features/PayloadController.feature

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -127,43 +127,43 @@ Feature: Payloads Controller
127127
| tenantId | "tenant-ayse" |
128128

129129
Scenario: Propagate device measures to asset
130-
Given I successfully execute the action "device-manager/device":"linkAsset" with args:
131-
| _id | "DummyTemp-attached_ayse_unlinked" |
132-
| assetId | "PERFO-unlinked" |
133130
When I successfully receive a "dummy-temp" payload with:
134-
| deviceEUI | "attached_ayse_unlinked" |
135-
| register55 | 42.2 |
136-
| batteryLevel | 0.4 |
137-
Then The document "device-manager":"devices":"DummyTemp-attached_ayse_unlinked" content match:
138-
| tenantId | "tenant-ayse" |
139-
| assetId | "PERFO-unlinked" |
140-
Then The document "tenant-ayse":"devices":"DummyTemp-attached_ayse_unlinked" content match:
141-
| tenantId | "tenant-ayse" |
142-
| assetId | "PERFO-unlinked" |
143-
And The document "tenant-ayse":"assets":"PERFO-unlinked" content match:
144-
| measures.temperature.id | "DummyTemp-attached_ayse_unlinked" |
145-
| measures.temperature.reference | "attached_ayse_unlinked" |
146-
| measures.temperature.model | "DummyTemp" |
147-
| measures.temperature.updatedAt | "_DATE_NOW_" |
148-
| measures.temperature.payloadUuid | "_STRING_" |
149-
| measures.temperature.degree | 42.2 |
150-
| measures.temperature.qos.battery | 40 |
131+
| deviceEUI | "attached_ayse_linked" |
132+
| register55 | 42.2 |
133+
| batteryLevel | 0.4 |
134+
Then The document "device-manager":"devices":"DummyTemp-attached_ayse_linked" content match:
135+
| tenantId | "tenant-ayse" |
136+
| assetId | "MART-linked" |
137+
Then The document "tenant-ayse":"devices":"DummyTemp-attached_ayse_linked" content match:
138+
| tenantId | "tenant-ayse" |
139+
| assetId | "MART-linked" |
140+
And The document "tenant-ayse":"assets":"MART-linked" content match:
141+
| measures.temperature.id | "DummyTemp-attached_ayse_linked" |
142+
| measures.temperature.reference | "attached_ayse_linked" |
143+
| measures.temperature.model | "DummyTemp" |
144+
| measures.temperature.updatedAt | "_DATE_NOW_" |
145+
| measures.temperature.payloadUuid | "_STRING_" |
146+
| measures.temperature.degree | 42.2 |
147+
| measures.temperature.qos.battery | 40 |
148+
# Enriched with the event
149+
| metadata.enriched | true |
150+
| metadata.assetId | "MART-linked" |
151151
And I should receive a result matching:
152-
| device._id | "DummyTemp-attached_ayse_unlinked" |
153-
| asset._id | "PERFO-unlinked" |
154-
| tenantId | "tenant-ayse" |
152+
| device._id | "DummyTemp-attached_ayse_linked" |
153+
| asset._id | "MART-linked" |
154+
| tenantId | "tenant-ayse" |
155155

156-
Scenario: Trigger tenant specific events
157-
Given I subscribe to "tests":"messages" notifications
158-
And I successfully execute the action "device-manager/device":"linkAsset" with args:
159-
| _id | "DummyTemp-attached_ayse_unlinked" |
160-
| assetId | "PERFO-unlinked" |
156+
Scenario: Use event to enrich the asset and historize it
161157
When I successfully receive a "dummy-temp" payload with:
162-
| deviceEUI | "attached_ayse_unlinked" |
163-
| register55 | 42.2 |
164-
| batteryLevel | 0.4 |
165-
Then I should receive realtime notifications for "tests":"messages" matching:
166-
| result._source.device._id | result._source.asset._id |
167-
| "DummyTemp-attached_ayse_unlinked" | "PERFO-unlinked" |
168-
169-
158+
| deviceEUI | "attached_ayse_linked" |
159+
| register55 | 51.1 |
160+
| batteryLevel | 0.42 |
161+
And I refresh the collection "tenant-ayse":"assets-history"
162+
And The last document from "tenant-ayse":"assets-history" content match:
163+
| assetId | "MART-linked" |
164+
| measureTypes | ["temperature"] |
165+
| assetId | "MART-linked" |
166+
| asset.model | "MART" |
167+
| asset.reference | "linked" |
168+
| asset.metadata.enriched | true |
169+
| asset.metadata.assetId | "MART-linked" |

features/fixtures/application/app.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Backend, KuzzleRequest } from 'kuzzle';
1+
import { Backend, Kuzzle, KuzzleRequest } from 'kuzzle';
2+
import { AssetMeasures, BaseAssetContent } from 'lib/types';
23

34
import { DeviceManagerPlugin } from '../../../index';
45
import { DummyTempDecoder, DummyTempPositionDecoder } from './decoders';
@@ -58,6 +59,21 @@ deviceManager.assets.register('hevSuit', {
5859
freezing: { type: 'boolean' }
5960
}, { group: 'astronaut' });
6061

62+
63+
// Register a pipe to enrich a tenant asset
64+
app.pipe.register(`tenant:tenant-ayse:asset:measures:new`, async (request: KuzzleRequest) => {
65+
if (request.result.assetId !== 'MART-linked') {
66+
return request;
67+
}
68+
69+
request.result.asset.metadata = {
70+
enriched: true,
71+
assetId: request.result.assetId
72+
};
73+
74+
return request;
75+
});
76+
6177
app.plugin.use(deviceManager);
6278

6379
app.hook.register('request:onError', async (request: KuzzleRequest) => {
@@ -66,14 +82,8 @@ app.hook.register('request:onError', async (request: KuzzleRequest) => {
6682

6783
app.config.set('plugins.kuzzle-plugin-logger.services.stdout.level', 'debug');
6884

69-
/**
70-
* Register pipe for scenario used to test the tenant specific event propagation
71-
*/
72-
app.pipe.register('tenant:tenant-ayse:device:new-payload', async eventParam => {
73-
await app.sdk.realtime.publish('tests', 'messages', eventParam.result);
74-
75-
return eventParam;
76-
});
85+
// Reduce writing latency since we won't have significant load
86+
app.config.set('plugins.device-manager.writerInterval', 1);
7787

7888
app.config.set('limits.documentsWriteCount', 5000);
7989

features/fixtures/fixtures.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,23 @@ module.exports = {
4040
tenantId: 'tenant-ayse',
4141
assetId: null
4242
},
43+
{ index: { _id: 'DummyTemp-attached_ayse_linked' } },
44+
{
45+
reference: 'attached_ayse_linked',
46+
model: 'DummyTemp',
47+
measures: {
48+
temperature: {
49+
updatedAt: 1610793427950,
50+
payloadUuid: 'some-uuid',
51+
degree: 42.2,
52+
}
53+
},
54+
qos: {
55+
battery: 80
56+
},
57+
tenantId: 'tenant-ayse',
58+
assetId: 'MART-linked'
59+
},
4360
]
4461
},
4562
'tenant-ayse': {
@@ -61,13 +78,48 @@ module.exports = {
6178
tenantId: 'tenant-ayse',
6279
assetId: null
6380
},
81+
{ index: { _id: 'DummyTemp-attached_ayse_linked' } },
82+
{
83+
reference: 'attached_ayse_linked',
84+
model: 'DummyTemp',
85+
measures: {
86+
temperature: {
87+
updatedAt: 1610793427950,
88+
payloadUuid: 'some-uuid',
89+
degree: 42.2,
90+
}
91+
},
92+
qos: {
93+
battery: 80
94+
},
95+
tenantId: 'tenant-ayse',
96+
assetId: 'MART-linked'
97+
},
6498
],
6599
assets: [
66100
{ index: { _id: 'PERFO-unlinked' } },
67101
{
68102
model: 'PERFO',
69103
reference: 'unlinked',
70104
measures: {},
105+
},
106+
{ index: { _id: 'MART-linked' } },
107+
{
108+
model: 'MART',
109+
reference: 'linked',
110+
measures: {
111+
temperature: {
112+
reference: 'attached_ayse_linked',
113+
qos: {
114+
battery: 40
115+
},
116+
payloadUuid: 'some-uuid',
117+
degree: 42.2,
118+
model: 'DummyTemp',
119+
id: 'DummyTemp-attached_ayse_linked',
120+
updatedAt: 1610793427950
121+
}
122+
},
71123
}
72124
]
73125
},

features/step_definitions/common/documents-steps.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,21 @@ Then('I delete the document {string}', async function (id) {
180180
this.props.collection,
181181
id);
182182
});
183+
184+
Then('The last document from {string}:{string} content match:', async function (index, collection, dataTable) {
185+
const expectedContent = this.parseObject(dataTable);
186+
187+
const result = await this.sdk.document.search(
188+
index,
189+
collection,
190+
{
191+
sort: { '_kuzzle_info.createdAt': 'desc' }
192+
},
193+
{ size: 1 });
194+
195+
if (result.hits.length === 0) {
196+
throw new Error('No document found');
197+
}
198+
199+
should(result.hits[0]._source).matchObject(expectedContent);
200+
});

features/support/hooks.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,11 @@ Before({ timeout: 30 * 1000 }, async function () {
7373
removeCatalogEntries(this.sdk, 'device-manager'),
7474

7575
truncateCollection(this.sdk, 'tenant-kuzzle', 'assets'),
76+
truncateCollection(this.sdk, 'tenant-kuzzle', 'assets-history'),
7677
truncateCollection(this.sdk, 'tenant-kuzzle', 'devices'),
7778

7879
truncateCollection(this.sdk, 'tenant-ayse', 'assets'),
80+
truncateCollection(this.sdk, 'tenant-ayse', 'assets-history'),
7981
truncateCollection(this.sdk, 'tenant-ayse', 'devices'),
8082
removeCatalogEntries(this.sdk, 'tenant-ayse'),
8183
]);

lib/DeviceManagerPlugin.ts

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,52 @@ import {
3131
assetsMappings,
3232
devicesMappings,
3333
catalogMappings,
34+
assetsHistoryMappings,
3435
} from './models';
3536

37+
export type DeviceManagerConfig = {
38+
/**
39+
* Administration index name
40+
*/
41+
adminIndex: string;
42+
43+
/**
44+
* Config collection name (in admin index)
45+
*/
46+
configCollection: string;
47+
48+
/**
49+
* Administration collections mappings (in admin index)
50+
*/
51+
adminCollections: {
52+
config: JSONObject;
53+
54+
devices: JSONObject;
55+
56+
payloads: JSONObject;
57+
},
58+
59+
/**
60+
* Tenants collections
61+
*/
62+
collections: {
63+
assets: JSONObject;
64+
65+
devices: JSONObject;
66+
67+
'assets-history': JSONObject;
68+
},
69+
70+
/**
71+
* Interval to write documents from the buffer
72+
*/
73+
writerInterval: number;
74+
}
75+
3676
export class DeviceManagerPlugin extends Plugin {
37-
private defaultConfig: JSONObject;
77+
public config: DeviceManagerConfig;
78+
79+
private defaultConfig: DeviceManagerConfig;
3880

3981
private assetController: AssetController;
4082
private deviceController: DeviceController;
@@ -125,6 +167,7 @@ export class DeviceManagerPlugin extends Plugin {
125167
collections: {
126168
assets: assetsMappings,
127169
devices: devicesMappings,
170+
'assets-history': assetsHistoryMappings,
128171
},
129172
writerInterval: 50
130173
};
@@ -138,9 +181,7 @@ export class DeviceManagerPlugin extends Plugin {
138181

139182
this.context = context;
140183

141-
this.config.mappings = new Map<string, JSONObject>();
142-
143-
this.batchWriter = new BatchWriter(this.sdk, this.config.writerInterval);
184+
this.batchWriter = new BatchWriter(this.sdk, { interval: this.config.writerInterval });
144185
this.batchWriter.begin();
145186

146187
this.payloadService = new PayloadService(this, this.batchWriter);
@@ -234,7 +275,7 @@ export class DeviceManagerPlugin extends Plugin {
234275
}
235276

236277
await Promise.all([
237-
this.sdk.collection.create(this.config.adminIndex, 'config', this.config.adminCollections.config),
278+
this.sdk.collection.create(this.config.adminIndex, this.config.configCollection, this.config.adminCollections.config),
238279
this.sdk.collection.create(this.config.adminIndex, 'devices', this.deviceMappings.get()),
239280
this.sdk.collection.create(this.config.adminIndex, 'payloads', this.getPayloadsMappings()),
240281
]);
@@ -278,13 +319,13 @@ export class DeviceManagerPlugin extends Plugin {
278319
private async initializeConfig () {
279320
const exists = await this.sdk.document.exists(
280321
this.config.adminIndex,
281-
'config',
322+
this.config.configCollection,
282323
'plugin--device-manager');
283324

284325
if (! exists) {
285326
await this.sdk.document.create(
286327
this.config.adminIndex,
287-
'config',
328+
this.config.configCollection,
288329
{
289330
type: 'device-manager',
290331
'device-manager': { autoProvisionning: true }
@@ -310,7 +351,7 @@ export class DeviceManagerPlugin extends Plugin {
310351
}
311352

312353
private async generateConfigID (request: KuzzleRequest) {
313-
if (request.getCollection() !== 'config') {
354+
if (request.getCollection() !== this.config.configCollection) {
314355
return request;
315356
}
316357

0 commit comments

Comments
 (0)