Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ <h3>{{ "APPLICATION.DETAILS" | translate }}</h3>
[personalData]="application.personalData"
[startDate]="application.startDate"
[status]="application.status"
[metadata]="application.metadata"
></app-metadata-details>
</div>
</div>
Expand Down
4 changes: 4 additions & 0 deletions src/app/applications/application.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { Organisation } from "../admin/organisation/organisation.model";
import { ApplicationStatus } from "./enums/status.enum";
import { IotDevice } from "./iot-devices/iot-device.model";
import { ApplicationDeviceType } from "./models/application-device-type.model";
import { KeyValue } from "@shared/types/tuple.type";
import { Metadata } from "@shared/types/metadata";

export type ApplicationWithStatus = Application & { statusCheck: "stable" | "alert" };

Expand Down Expand Up @@ -37,6 +39,7 @@ export class Application {
public permissions: PermissionResponse[];
public permissionIds: number[];
public dataTargets: Datatarget[];
public metadata?: Metadata;
}

export class ApplicationRequest {
Expand All @@ -56,6 +59,7 @@ export class ApplicationRequest {
public controlledProperties?: ControlledPropertyTypes[];
public deviceTypes?: ApplicationDeviceTypeUnion[];
public permissionIds: number[];
public metadata?: Metadata;
}

export interface ApplicationData {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
</ul>
</div>
}

<div class="row">
<div class="form-group mt-3">
<label class="form-label" for="name">{{ "GEN.NAME" | translate }}*</label>
Expand Down Expand Up @@ -313,6 +314,11 @@
</div>
</div>

<div class="row mt-2">
<h3>{{ "QUESTION.METADATA" | translate }}</h3>
<app-form-key-value-list [(tags)]="metadataTags" [errorFieldId]="errorMetadataFieldId"></app-form-key-value-list>
</div>

<div class="form-group mt-5">
<button (click)="routeBack()" class="btn btn-secondary" type="button">{{ "GEN.CANCEL" | translate }}</button>
<button class="btn btn-primary ms-2" type="submit">{{ submitButton }}</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import { SharedVariableService } from "@shared/shared-variable/shared-variable.s
import { isPhoneNumberValid } from "@shared/validators/phone-number.validator";
import { ReplaySubject, Subject, Subscription } from "rxjs";
import { takeUntil } from "rxjs/operators";
import { keyValueListToMetadata, metadataToKeyValueList } from "@shared/helpers/metadata.helper";
import { TranslatableError } from "@shared/translatable-error";
import { addWarning } from "@angular-devkit/build-angular/src/utils/webpack-diagnostics";

export class User {
public name: string;
Expand Down Expand Up @@ -58,6 +61,8 @@ export class FormBodyApplicationComponent implements OnInit, OnDestroy {
public filteredPermissionsMulti: ReplaySubject<PermissionResponse[]> = new ReplaySubject<PermissionResponse[]>(1);
private id: number;
private _onDestroy = new Subject<void>();
metadataTags: { key?: string; value?: string }[] = [];
errorMetadataFieldId: string | undefined;

constructor(
private restService: RestService,
Expand Down Expand Up @@ -157,6 +162,9 @@ export class FormBodyApplicationComponent implements OnInit, OnDestroy {
this.application.deviceTypes = application.deviceTypes.map(deviceType => deviceType.type);
this.application.permissionIds = application.permissionIds;
this.permissionMultiCtrl.setValue(this.application.permissionIds);
if (application.metadata) {
this.metadataTags = metadataToKeyValueList(application.metadata) ?? [];
}

this.fillDefaultMetadata();
});
Expand All @@ -177,6 +185,18 @@ export class FormBodyApplicationComponent implements OnInit, OnDestroy {
this.application.endDate = this.serializedEndDate.value?.toISOString();
this.application.contactPhone = this.phoneCtrl.value ? this.phoneCtrl.value : null;

try {
this.application.metadata = keyValueListToMetadata(this.metadataTags);
} catch (error) {
let message = error.message;
if (error instanceof TranslatableError) {
// @todo Can we safely use `instant` here?
message = this.translate.instant(message, error.context);
}
this.handleError(this.buildErrorMessage(message), "application.metadata");
return;
}

if (this.id) {
this.updateApplication(this.id);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,16 @@
>{{ deviceTypeText }}
</p>
}

@if (metadata) {
<p>
<strong>{{ "APPLICATION.METADATA-FIELD.METADATA" | translate }}</strong
>
</p>
@for (key of Object.keys(metadata); track key; let i = $index) {
<p>
<strong>{{ i + 1 }}: {{ key }}</strong>
<span>{{ metadata[key] }}</span>
</p>
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ApplicationDeviceType } from "@applications/models/application-device-t
import { TranslateService } from "@ngx-translate/core";
import { toPascalKebabCase } from "@shared/helpers/string.helper";
import { ControlledProperty } from "@shared/models/controlled-property.model";
import { Metadata } from "@shared/types/metadata";

@Component({
selector: "app-metadata-details",
Expand All @@ -26,6 +27,7 @@ export class MetadataDetailsComponent implements OnInit {
@Input() hardware?: string;
@Input() controlledProperties?: ControlledProperty[];
@Input() deviceTypes?: ApplicationDeviceType[];
@Input() metadata?: Metadata;
controlledPropertyText: string;
deviceTypeText: string;

Expand All @@ -48,4 +50,6 @@ export class MetadataDetailsComponent implements OnInit {
.join(", ");
});
}

protected readonly Object = Object;
}
39 changes: 39 additions & 0 deletions src/app/shared/helpers/metadata.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { KeyValue } from "@shared/types/tuple.type";
import { Metadata } from "@shared/types/metadata";
import { TranslatableError } from "@shared/translatable-error";

export const metadataToKeyValueList = (metadata: object, shouldThrowOnError = false): KeyValue[] => {
try {
const tags: KeyValue[] = [];

for (const key of Object.keys(metadata)) {
tags.push({ key, value: metadata[key] });
}

return tags;
} catch (error) {
if (shouldThrowOnError) {
throw error;
}
}
};

export const keyValueListToMetadata = (pairs: KeyValue[]): Metadata => {
const metadata = {}

for (const pair of pairs) {
const key = pair.key?.trim();
const value = pair.value?.trim();
if (!key) {
throw new TranslatableError("ERROR.METADATA-EMPTY-KEY");
} else if (key in metadata) {
throw new TranslatableError("ERROR.METADATA-DUPLICATE-KEY", { key });
}
if (!value) {
throw new TranslatableError("ERROR.METADATA-EMPTY-VALUE", { key });
}
metadata[key] = value
}

return metadata;
};
12 changes: 12 additions & 0 deletions src/app/shared/translatable-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { InterpolationParameters } from "@ngx-translate/core";

export class TranslatableError extends Error {
constructor(
msg: string,
public readonly context?: InterpolationParameters
) {
super(msg);
// Set the prototype explicitly.
Object.setPrototypeOf(this, TranslatableError.prototype);
}
}
1 change: 1 addition & 0 deletions src/app/shared/types/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type Metadata = Record<string, string>
8 changes: 6 additions & 2 deletions src/assets/i18n/da.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@
"PERSONAL-DATA": "Persondata",
"HARDWARE": "Hardware",
"CONTROLLED-PROPERTY": "Data",
"DEVICE-TYPE": "Forbindelsesteknologi"
"DEVICE-TYPE": "Forbindelsesteknologi",
"METADATA": "Metadata"
},
"CHANGE-ORGANIZATION": {
"TITLE": "Skift organisation",
Expand Down Expand Up @@ -1290,7 +1291,10 @@
"USER-INACTIVE": "Login successfuldt, men din bruger er deaktiveret i OS2IoT. Kontakt din administrator for at få tildelt rettigheder"
},
"ERROR": {
"SEMANTIC": "Semantisk fejl, verificer at navn og værdier er på det rigtige format"
"SEMANTIC": "Semantisk fejl, verificer at navn og værdier er på det rigtige format",
"METADATA-EMPTY-KEY": "Metadatanavnet må ikke være tomt",
"METADATA-EMPTY-VALUE": "Metadataværdien for \"{{key}}\" må ikke være tom",
"METADATA-DUPLICATE-KEY": "Metadatanavnet \"{{key}}\" er allerede brugt"
},
"DIALOG": {
"SAVE.TITLE": "Slet",
Expand Down