Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update measure model for localization #385

Open
wants to merge 10 commits into
base: 2-dev
Choose a base branch
from
24 changes: 24 additions & 0 deletions doc/2/concepts/models/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ A measure model contains the following information:
- `measure`: type of the measure
- `valuesMappings`: measurements mappings (See [Collection Mappings](https://docs.kuzzle.io/core/2/guides/main-concepts/data-storage/#collection-mappings))
- `valuesDetails`: (optional) Metadata and translations of measurements. You can use it to keep consistency on translations between your apps
- `locales`: (optional) Translation for the measure model

It is possible to create new models on the Kuzzle IoT Platform using either:

Expand Down Expand Up @@ -98,6 +99,16 @@ await sdk.query({
},
},
},
locales: {
en: {
friendlyName: "Light measurement",
description: "Light measurement",
},
fr: {
friendlyName: "Mesure de lumière",
description: "Mesure de lumière",
},
}
},
});
```
Expand All @@ -124,6 +135,7 @@ An asset model contains the following information:
- Editor hint: it unlock functionalities depending on the metadata type you define.
- `metadataGroups`: (optional) Map of group names to their translations. You can use it to group metadata.
- `tooltipModels`: (optional) Tooltip model list, each containing labels and tooltip content to be shown. You can use it to create templates that displays relevant information in dashboards
- `locales`: (optional) Translation for asset model

It is possible to create new models on the Kuzzle IoT Platform using either:

Expand All @@ -150,10 +162,22 @@ import {
{ name: "temperatureInternal", type: "temperature" },
{ name: "temperatureExternal", type: "temperature" },
],
locales: {
en: {
friendlyName: "Container translated by model",
description: "Containerized container",
},
fr: {
friendlyName: "Conteneur traduit par modèle",
description: "Conteneur conteneurisé",
},
}
},
};
```

**INFO: If the locales has changed, use [updateModelLocales](../../controllers/assets/updateModelLocales/index.md) to update all assets manually and make the search on assets up to date**

The API also allows to:

- list available models `device-manager/models:listAssets`
Expand Down
55 changes: 55 additions & 0 deletions doc/2/controllers/assets/updateModelLocales/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
code: true
type: page
title: updateModelLocales
description: Update all assets localization
---

# update

Update all assets localization.

The `updateModelLocales` operation allows you to update the `locales` of all `assets` related to the specified `asset model`.
The process retrieve the locales values stored in the asset model and update the locales of all the assets with these values. For the moment, this operation has to be done `manually` when the asset model locales changed to make the `search` operation on assets to `be up to date`.

## Query Syntax

### HTTP

```http
URL: http://kuzzle:7512/_/device-manager/assets/modelLocales
Method: PUT
```

## Other protocols

```js
{
"controller": "device-manager/assets",
"action": "updateModelLocales",
"model": "Container",
"engineGroup": "commons"
}
```

## Arguments

- `engineGroup`: Engine group
- `model`: asset model

## Response

```js
{
"action": "updateModelLocales",
"collection": "assets",
"controller": "device-manager/assets",
"error": null,
"headers": {},
"index": "engine-ayse",
"node": "knode-gigantic-iago-99422",
"requestId": "3b86b6d1-8004-4273-ba03-94526b019b8a",
"status": 200,
"volatile": null
}
```
78 changes: 78 additions & 0 deletions lib/modules/asset/AssetService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
import {
AssetHistoryContent,
AssetHistoryEventMetadata,
AssetHistoryEventModelLocales,
} from "./types/AssetHistoryContent";

export class AssetService extends DigitalTwinService {
Expand Down Expand Up @@ -314,6 +315,7 @@ export class AssetService extends DigitalTwinService {
measures,
metadata: { ...assetMetadata, ...metadata },
model,
modelLocales: assetModel.asset.locales,
reference,
softTenant: [],
},
Expand Down Expand Up @@ -598,6 +600,82 @@ export class AssetService extends DigitalTwinService {
return replacedAssets;
}

/**
* Retrieve locales with the specified model and search all assets related to the model to update the locales.
* The operation is historized.
* @param request
* @param engineGroup
* @param model
*/
public async updateModelLocales(
request: KuzzleRequest,
engineGroup: string,
model: string,
): Promise<void> {
const { result } = await this.sdk.query({
action: "getAsset",
body: {},
controller: "device-manager/models",
engineGroup,
model,
});

const locales = result._source.asset.locales;

const engines = await ask<AskEngineList>("ask:device-manager:engine:list", {
group: engineGroup,
});

const targets = engines.map((engine) => ({
collections: [InternalCollection.ASSETS],
index: engine.index,
}));

const assets = await this.sdk.query<
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This request will only return 10 results... you should :

  • add a scoll ID to be able to iter on more that 10000 results
  • use a SearchResult and the .next() method to iterate over all the results
  • set a size to work on large data chunk to avoid doing to many requests

BaseRequest,
DocumentSearchResult<AssetContent>
>({
action: "search",
body: { query: { equals: { model } } },
controller: "document",
lang: "koncorde",
targets,
});

assets.result.hits.map((asset) => {
asset._source.modelLocales = locales;
});

for (const asset of assets.result.hits) {
const updatedAsset = await this.updateDocument<AssetContent>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be optimized using an updateByQuery request

request,
{
_id: asset._id,
_source: { modelLocales: locales },
},
{
collection: InternalCollection.ASSETS,
engineId: asset.index,
},
{ source: true },
);

await this.assetHistoryService.add<AssetHistoryEventModelLocales>(
asset.index,
[
{
asset: updatedAsset._source,
event: {
name: "modelLocales",
},
id: updatedAsset._id,
timestamp: Date.now(),
},
],
);
}
}

private async getEngine(engineId: string): Promise<JSONObject> {
const engine = await this.sdk.document.get(
this.config.adminIndex,
Expand Down
21 changes: 21 additions & 0 deletions lib/modules/asset/AssetsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,15 @@ export class AssetsController {
},
],
},
updateModelLocales: {
handler: this.updateModelLocales.bind(this),
http: [
{
path: "device-manager/assets/modelLocales",
verb: "patch",
},
],
},
},
};
/* eslint-enable sort-keys */
Expand Down Expand Up @@ -745,4 +754,16 @@ export class AssetsController {

return this.assetService.mGetLastMeasuredAt(engineId, assetIds);
}

/**
* Update modelLocales of all assets related to the specified asset model.
* This operation is done to make the search assets feature up to date
* @param request
*/
async updateModelLocales(request: KuzzleRequest): Promise<void> {
const model = request.getString("model");
const engineGroup = request.getString("engineGroup", "commons");

await this.assetService.updateModelLocales(request, engineGroup, model);
}
}
27 changes: 27 additions & 0 deletions lib/modules/asset/collections/assetsMappings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,33 @@ export const assetsMappings: CollectionMappings = {
type: "keyword",
fields: { text: { type: "text" } },
},
modelLocales: {
dynamic: "false",
properties: {
en: {
properties: {
description: {
type: "text",
},
friendlyName: {
type: "keyword",
fields: { text: { type: "text" } },
},
},
},
fr: {
properties: {
description: {
type: "text",
},
friendlyName: {
type: "keyword",
fields: { text: { type: "text" } },
},
},
},
},
},
reference: {
type: "keyword",
fields: { text: { type: "text" } },
Expand Down
7 changes: 6 additions & 1 deletion lib/modules/asset/types/AssetHistoryContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export type AssetHistoryEventMetadata = {
metadata: AssetHistoryMetadata;
};

export type AssetHistoryEventModelLocales = {
name: "modelLocales";
};

export type AssetHistoryEventLink = {
name: "link";
link: {
Expand All @@ -39,7 +43,8 @@ export type AssetHistoryEvent =
| AssetHistoryEventMeasure
| AssetHistoryEventMetadata
| AssetHistoryEventLink
| AssetHistoryEventUnlink;
| AssetHistoryEventUnlink
| AssetHistoryEventModelLocales;

/**
* Asset History document content
Expand Down
4 changes: 4 additions & 0 deletions lib/modules/measure/types/MeasureDefinition.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { SchemaObject } from "ajv";
import { JSONObject } from "kuzzle-sdk";
import { LocaleDetails } from "lib/modules/model";

/* *
* Represents a measure information and localization
Expand Down Expand Up @@ -63,6 +64,9 @@ export interface MeasureValuesDetails {
*/

export interface MeasureDefinition {
locales?: {
[valueName: string]: LocaleDetails;
};
/**
* Mappings for the measurement values in order to index the fields
*/
Expand Down
9 changes: 9 additions & 0 deletions lib/modules/model/ModelService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { ModelSerializer } from "./ModelSerializer";
import {
AssetModelContent,
DeviceModelContent,
LocaleDetails,
MeasureModelContent,
MetadataDetails,
MetadataGroups,
Expand Down Expand Up @@ -193,6 +194,7 @@ export class ModelService extends BaseService {
metadataGroups: MetadataGroups,
measures: NamedMeasures,
tooltipModels: TooltipModels,
locales: { [valueName: string]: LocaleDetails },
): Promise<KDocument<AssetModelContent>> {
if (Inflector.pascalCase(model) !== model) {
throw new BadRequestError(`Asset model "${model}" must be PascalCase.`);
Expand All @@ -209,6 +211,7 @@ export class ModelService extends BaseService {
const modelContent: AssetModelContent = {
asset: {
defaultMetadata,
locales,
measures,
metadataDetails,
metadataGroups,
Expand Down Expand Up @@ -369,9 +372,13 @@ export class ModelService extends BaseService {
valuesMappings: JSONObject,
validationSchema?: SchemaObject,
valuesDetails?: MeasureValuesDetails,
locales?: {
[valueName: string]: LocaleDetails;
},
): Promise<KDocument<MeasureModelContent>> {
const modelContent: MeasureModelContent = {
measure: {
locales,
type,
valuesDetails,
valuesMappings,
Expand Down Expand Up @@ -689,6 +696,7 @@ export class ModelService extends BaseService {
metadataGroups: MetadataGroups,
measures: AssetModelContent["asset"]["measures"],
tooltipModels: TooltipModels,
locales: { [valueName: string]: LocaleDetails },
request: KuzzleRequest,
): Promise<KDocument<AssetModelContent>> {
if (Inflector.pascalCase(model) !== model) {
Expand All @@ -714,6 +722,7 @@ export class ModelService extends BaseService {
const assetModelContent: AssetModelContent = {
asset: {
defaultMetadata,
locales,
measures: measuresUpdated,
metadataDetails,
metadataGroups,
Expand Down
Loading