Skip to content

Commit

Permalink
feat: implement comprehensible metadata (#332)
Browse files Browse the repository at this point in the history
* feat: implement comprehensible metadata

* doc: add documentation for comprehensive metadata

* refactor: improve typing in models

* fix: apply review fixes
  • Loading branch information
Juiced66 committed Mar 13, 2024
1 parent 6082008 commit e80ef8e
Show file tree
Hide file tree
Showing 13 changed files with 577 additions and 35 deletions.
6 changes: 5 additions & 1 deletion doc/2/concepts/models/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ A sensor model contains the following information:
- `decoder`: (optional) instance of a [Decoder] to normalize the data
- `metadataMappings`: (optional) metadata mappings (See [Collection Mappings](https://docs.kuzzle.io/core/2/guides/main-concepts/data-storage/#collection-mappings))
- `defaultMetadata`: (optional) default metadata values
- `metadataDetails`: (optional) Metadata group and translations. You can use it to keep consistency on translations between your apps
- `metadataGroups`: (optional) Groups list with translations for group name. You can use it to group metadatas by their concerns

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

Expand Down Expand Up @@ -66,7 +68,9 @@ An asset model contains the following information:
- `engineGroup`: engine group to which the model belongs.
- `measures`: received measurements
- `metadataMappings`: (optional) metadata mappings (See [Collection Mappings](https://docs.kuzzle.io/core/2/guides/main-concepts/data-storage/#collection-mappings))
- `defaultMetadata`: (optional) default metadata values
- `defaultMetadata`: (optional) default metadata values-
- `metadataDetails`: (optional) Metadata group and translations . You can use it to keep consistency on translations between your apps
- `metadataGroups`: (optional) Groups list with translations for group name. You can use it to group metadatas by their concerns

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

Expand Down
32 changes: 31 additions & 1 deletion doc/2/controllers/models/write-asset/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,35 @@ Method: POST
},
"defaultValues": {
// Default values for metadata
}
},
"metadataDetails": {
/*
Metadata details including tanslations and group.
[name: string]: {
group?: string;
locales: {
[locale: string]: {
friendlyName: string;
description: string;
};
};
};
*/
},
"metadataGroups"; {
/*
Metadata groups list and details.
{
[groupName: string]: {
locales: {
[locale: string]: {
groupFriendlyName: string;
};
};
};
};
*/
},
"measures": [
// Array of measure definition with type and name
]
Expand All @@ -53,6 +81,8 @@ Method: POST
- `model`: Asset model name
- `metadataMappings`: Mappings of the metadata in Elasticsearch format
- `defaultValues`: Default values for the metadata
- `metadataDetails`: Metadata group and translations
- `metadataGroups`: Groups list with translations for group name
- `measures`: Array of measure definition. Each item define a `type` and `name` properties for the measure.

---
Expand Down
34 changes: 32 additions & 2 deletions doc/2/controllers/models/write-device/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,35 @@ Method: POST
},
"defaultValues": {
// Default values for metadata
}
},
"metadataDetails": {
/*
Metadata details including tanslations and group.
[name: string]: {
group?: string;
locales: {
[locale: string]: {
friendlyName: string;
description: string;
};
};
};
*/
},
"metadataGroups"; {
/*
Metadata groups list and details.
{
[groupName: string]: {
locales: {
[locale: string]: {
groupFriendlyName: string;
};
};
};
};
*/
},
"measures": [
// Array of measure definition with type and name
]
Expand All @@ -50,7 +78,9 @@ Method: POST

- `model`: Device model name
- `metadataMappings`: Mappings of the metadata in Elasticsearch format
- `defaultValues`: Default values for the metadata
- `defaultValues`: Default values for the metadata-
- `metadataDetails`: Metadata group and translations
- `metadataGroups`: Groups list with translations for group name
- `measures`: Array of measure definition. Each item define a `type` and `name` properties for the measure.

---
Expand Down
31 changes: 26 additions & 5 deletions lib/modules/model/ModelService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import {
AssetModelContent,
DeviceModelContent,
MeasureModelContent,
MetadataDetails,
MetadataGroups,
MetadataMappings,
} from "./types/ModelContent";
import {
AskModelAssetGet,
Expand Down Expand Up @@ -59,16 +62,25 @@ export class ModelService extends BaseService {
async writeAsset(
engineGroup: string,
model: string,
metadataMappings: JSONObject,
metadataMappings: MetadataMappings,
defaultMetadata: JSONObject,
metadataDetails: MetadataDetails,
metadataGroups: MetadataGroups,
measures: AssetModelContent["asset"]["measures"],
): Promise<KDocument<AssetModelContent>> {
if (Inflector.pascalCase(model) !== model) {
throw new BadRequestError(`Asset model "${model}" must be PascalCase.`);
}

const modelContent: AssetModelContent = {
asset: { defaultMetadata, measures, metadataMappings, model },
asset: {
defaultMetadata,
measures,
metadataDetails,
metadataGroups,
metadataMappings,
model,
},
engineGroup,
type: "asset",
};
Expand Down Expand Up @@ -97,7 +109,7 @@ export class ModelService extends BaseService {
}

private checkDefaultValues(
metadataMappings: JSONObject,
metadataMappings: MetadataMappings,
defaultMetadata: JSONObject,
) {
const metadata = Object.keys(
Expand All @@ -121,16 +133,25 @@ export class ModelService extends BaseService {

async writeDevice(
model: string,
metadataMappings: JSONObject,
metadataMappings: MetadataMappings,
defaultMetadata: JSONObject,
metadataDetails: MetadataDetails,
metadataGroups: MetadataGroups,
measures: DeviceModelContent["device"]["measures"],
): Promise<KDocument<DeviceModelContent>> {
if (Inflector.pascalCase(model) !== model) {
throw new BadRequestError(`Device model "${model}" must be PascalCase.`);
}

const modelContent: DeviceModelContent = {
device: { defaultMetadata, measures, metadataMappings, model },
device: {
defaultMetadata,
measures,
metadataDetails,
metadataGroups,
metadataMappings,
model,
},
type: "device",
};

Expand Down
8 changes: 8 additions & 0 deletions lib/modules/model/ModelsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,16 @@ export class ModelsController {
const metadataMappings = request.getBodyObject("metadataMappings", {});
const defaultValues = request.getBodyObject("defaultValues", {});
const measures = request.getBodyArray("measures", []);
const metadataDetails = request.getBodyObject("metadataDetails", {});
const metadataGroups = request.getBodyObject("metadataGroups", {});

const assetModel = await this.modelService.writeAsset(
engineGroup,
model,
metadataMappings,
defaultValues,
metadataDetails,
metadataGroups,
measures,
);

Expand All @@ -130,11 +134,15 @@ export class ModelsController {
const metadataMappings = request.getBodyObject("metadataMappings", {});
const defaultValues = request.getBodyObject("defaultValues", {});
const measures = request.getBodyArray("measures");
const metadataDetails = request.getBodyObject("metadataDetails", {});
const metadataGroups = request.getBodyObject("metadataGroups", {});

const deviceModel = await this.modelService.writeDevice(
model,
metadataMappings,
defaultValues,
metadataDetails,
metadataGroups,
measures,
);

Expand Down
56 changes: 51 additions & 5 deletions lib/modules/model/ModelsRegister.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Inflector, PluginContext, PluginImplementationError } from "kuzzle";
import { JSONObject } from "kuzzle-sdk";

import {
DeviceManagerConfiguration,
Expand All @@ -13,9 +12,13 @@ import {
AssetModelContent,
DeviceModelContent,
MeasureModelContent,
MetadataDetails,
MetadataGroups,
MetadataMappings,
ModelContent,
} from "./types/ModelContent";
import { ModelSerializer } from "./ModelSerializer";
import { JSONObject } from "kuzzle-sdk";

export class ModelsRegister {
private config: DeviceManagerConfiguration;
Expand Down Expand Up @@ -46,40 +49,83 @@ export class ModelsRegister {
);
}

/**
* Registers an asset model.
*
* @param engineGroup - The engine group name.
* @param model - The name of the asset model, which must be in PascalCase.
* @param measures - The measures associated with this asset model.
* @param metadataMappings - The metadata mappings for the model, defaults to an empty object.
* @param defaultMetadata - The default metadata values for the model, defaults to an empty object.
* @param metadataDetails - Optional detailed metadata descriptions and localizations.
* @param metadataGroups - Optional groups for organizing metadata, with localizations.
* @throws PluginImplementationError if the model name is not in PascalCase.
*/
registerAsset(
engineGroup: string,
model: string,
measures: NamedMeasures,
metadataMappings: JSONObject = {},
metadataMappings: MetadataMappings = {},
defaultMetadata: JSONObject = {},
metadataDetails: MetadataDetails = {},
metadataGroups: MetadataGroups = {},
) {
if (Inflector.pascalCase(model) !== model) {
throw new PluginImplementationError(
`Asset model "${model}" must be PascalCase`,
);
}

// Construct and push the new asset model to the assetModels array
this.assetModels.push({
asset: { defaultMetadata, measures, metadataMappings, model },
asset: {
defaultMetadata,
measures,
metadataDetails,
metadataGroups,
metadataMappings,
model,
},
engineGroup,
type: "asset",
});
}

/**
* Registers a device model.
*
* @param model - The name of the device model, which must be in PascalCase.
* @param measures - The measures associated with this device model.
* @param metadataMappings - The metadata mappings for the model, defaults to an empty object.
* @param defaultMetadata - The default metadata values for the model, defaults to an empty object.
* @param metadataDetails - Optional detailed metadata descriptions and localizations.
* @param metadataGroups - Optional groups for organizing metadata, with localizations.
* @throws PluginImplementationError if the model name is not in PascalCase.
*/
registerDevice(
model: string,
measures: NamedMeasures,
metadataMappings: JSONObject = {},
metadataMappings: MetadataMappings = {},
defaultMetadata: JSONObject = {},
metadataDetails: MetadataDetails = {},
metadataGroups: MetadataGroups = {},
) {
if (Inflector.pascalCase(model) !== model) {
throw new PluginImplementationError(
`Device model "${model}" must be PascalCase`,
);
}

// Construct and push the new device model to the deviceModels array
this.deviceModels.push({
device: { defaultMetadata, measures, metadataMappings, model },
device: {
defaultMetadata,
measures,
metadataDetails,
metadataGroups,
metadataMappings,
model,
},
type: "device",
});
}
Expand Down
16 changes: 16 additions & 0 deletions lib/modules/model/collections/modelsMappings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ export const modelsMappings: CollectionMappings = {
dynamic: "false",
properties: {},
},
metadataDetails: {
dynamic: "false",
properties: {},
},
metadataGroups: {
dynamic: "false",
properties: {},
},
measures: {
properties: {
type: { type: "keyword" },
Expand All @@ -61,6 +69,14 @@ export const modelsMappings: CollectionMappings = {
dynamic: "false",
properties: {},
},
metadataDetails: {
dynamic: "false",
properties: {},
},
metadataGroups: {
dynamic: "false",
properties: {},
},
measures: {
properties: {
type: { type: "keyword" },
Expand Down
Loading

0 comments on commit e80ef8e

Please sign in to comment.