@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
1 lines • 150 kB
Source Map (JSON)
{"version":3,"file":"c8y-ngx-components-datapoint-selector.mjs","sources":["../../datapoint-selector/datapoint-attributes-form/datapoint-attributes.model.ts","../../datapoint-selector/datapoint-attributes-form/datapoint-attributes-form-validation.service.ts","../../datapoint-selector/datapoint-attributes-form/datapoint-attributes-form.component.ts","../../datapoint-selector/datapoint-attributes-form/datapoint-attributes-form.component.html","../../datapoint-selector/datapoint-selection.model.ts","../../datapoint-selector/datapoint-library.service.ts","../../datapoint-selector/datapoint-template-popover/datapoint-template-popover.component.ts","../../datapoint-selector/datapoint-template-popover/datapoint-template-popover.component.html","../../datapoint-selector/datapoint-selector-list-item/datapoint-selector-list-item.component.ts","../../datapoint-selector/datapoint-selector-list-item/datapoint-selector-list-item.component.html","../../datapoint-selector/pipes/includes-datapoint.pipe.ts","../../datapoint-selector/pipes/datapoint-label.pipe.ts","../../datapoint-selector/datapoint-selector.component.ts","../../datapoint-selector/datapoint-selector.component.html","../../datapoint-selector/datapoint-selector-modal/datapoint-selector-modal.component.ts","../../datapoint-selector/datapoint-selector-modal/datapoint-selector-modal.component.html","../../datapoint-selector/datapoint-selector.service.ts","../../datapoint-selector/datapoint-selection-list/datapoint-selection-list.component.ts","../../datapoint-selector/datapoint-selection-list/datapoint-selection-list.component.html","../../datapoint-selector/pipes/filter-datapoints.pipe.ts","../../datapoint-selector/datapoint-selector.module.ts","../../datapoint-selector/c8y-ngx-components-datapoint-selector.ts"],"sourcesContent":["import { gettext } from '@c8y/ngx-components/gettext';\n\nexport const AXIS_TYPES = [\n { val: undefined, text: gettext('Auto') },\n { val: 'left', text: gettext('Left') },\n { val: 'right', text: gettext('Right') }\n] as const;\n\nexport type AxisTypes = (typeof AXIS_TYPES)[1 | 2]['val'];\n\nexport const CHART_LINE_TYPES = [\n { val: 'line', text: gettext('Line') },\n { val: 'points', text: gettext('Points') },\n { val: 'linePoints', text: gettext('Line and points') },\n { val: 'bars', text: gettext('Bars') },\n { val: 'step-before', text: gettext('Step before') },\n { val: 'step-after', text: gettext('Step after') }\n] as const;\n\nexport type ChartLineTypes = (typeof CHART_LINE_TYPES)[number]['val'];\n\nexport const CHART_RENDER_TYPES = [\n { val: 'min', text: gettext('Minimum') },\n { val: 'max', text: gettext('Maximum') },\n { val: 'area', text: gettext('Minimum and maximum') }\n] as const;\n\nexport type ChartRenderTypes = (typeof CHART_RENDER_TYPES)[number]['val'];\n","import { Injectable } from '@angular/core';\nimport {\n AbstractControl,\n FormBuilder,\n FormGroup,\n ValidationErrors,\n ValidatorFn,\n Validators\n} from '@angular/forms';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport { C8yValidators } from '@c8y/ngx-components';\nimport { AXIS_TYPES, CHART_LINE_TYPES, CHART_RENDER_TYPES } from './datapoint-attributes.model';\n\n@Injectable({ providedIn: 'root' })\nexport class DatapointAttributesFormValidationService {\n constructor(private formBuilder: FormBuilder) {}\n\n getDefaultFormGroup(fieldsToRemove: string[] = []): FormGroup {\n const formFields = {\n __active: [true, []],\n __target: this.getTargetFormGroup(),\n __template: [undefined, []],\n color: ['', this.getColorValidators()],\n label: ['', this.getLabelValidators()],\n description: ['', this.getDescriptionValidators()],\n fragment: ['', this.getFragmentValidators()],\n series: ['', this.getSeriesValidators()],\n range: this.getMinMaxFormGroup(),\n unit: [undefined, this.getUnitValidators()],\n target: [undefined, this.getTargetValidators()],\n redRange: this.getMinMaxFormGroup(),\n yellowRange: this.getMinMaxFormGroup(),\n chart: this.getChartFormGroup(),\n display: this.getDisplayFormGroup()\n };\n if (fieldsToRemove.length) {\n for (const field of fieldsToRemove) {\n delete formFields[field];\n }\n }\n return this.formBuilder.group(formFields, {\n validators: this.getOverallValidators()\n });\n }\n\n convertToBackendFormat(formDataStructure: any, showChart: boolean) {\n if (!formDataStructure) {\n return {};\n }\n\n const {\n __active,\n __target,\n __template,\n color,\n label,\n description,\n fragment,\n series,\n range,\n unit,\n target,\n redRange,\n yellowRange,\n chart,\n display\n } = formDataStructure;\n const obj = {\n __active,\n __target,\n __template,\n color,\n label,\n description,\n fragment,\n series,\n min: range?.min,\n max: range?.max,\n unit,\n target,\n redRangeMin: redRange?.min,\n redRangeMax: redRange?.max,\n yellowRangeMin: yellowRange?.min,\n yellowRangeMax: yellowRange?.max,\n renderType: showChart ? chart?.renderType : display?.renderType,\n lineType: chart?.lineType,\n yAxisType: chart?.yAxisType\n };\n return obj;\n }\n\n convertToFormGroupFormat(backendDataStructure: any) {\n if (!backendDataStructure) {\n return {};\n }\n\n const {\n __active,\n __target,\n __template,\n color,\n label,\n description,\n fragment,\n series,\n min,\n max,\n unit,\n target,\n redRangeMin,\n redRangeMax,\n yellowRangeMin,\n yellowRangeMax,\n renderType,\n lineType,\n yAxisType\n } = backendDataStructure;\n\n const obj = {\n __active,\n __target,\n __template,\n color,\n label,\n description,\n fragment,\n series,\n range: {\n min: this.convertStringToNumber(min),\n max: this.convertStringToNumber(max)\n },\n unit,\n target: this.convertStringToNumber(target),\n redRange: {\n min: this.convertStringToNumber(redRangeMin),\n max: this.convertStringToNumber(redRangeMax)\n },\n yellowRange: {\n min: this.convertStringToNumber(yellowRangeMin),\n max: this.convertStringToNumber(yellowRangeMax)\n },\n chart: renderType || lineType || yAxisType ? { renderType, lineType, yAxisType } : undefined,\n display: renderType ? { renderType } : undefined\n };\n return obj;\n }\n\n getColorValidators(): ValidatorFn[] {\n return [Validators.required, Validators.minLength(4)];\n }\n\n getLabelValidators(): ValidatorFn[] {\n return [Validators.required, Validators.minLength(1), Validators.maxLength(120)];\n }\n\n getDescriptionValidators(): ValidatorFn[] {\n return [];\n }\n\n getFragmentValidators(): ValidatorFn[] {\n return [\n Validators.required,\n Validators.minLength(1),\n Validators.maxLength(120),\n Validators.pattern(/^[^.]*$/)\n ];\n }\n\n getSeriesValidators(): ValidatorFn[] {\n return [\n Validators.required,\n Validators.minLength(1),\n Validators.maxLength(120),\n (control: AbstractControl): ValidationErrors | null => {\n const forbidden = control.value?.includes('.') || '';\n return forbidden\n ? { noPeriods: { message: gettext('Series cannot contain periods.') } }\n : null;\n }\n ];\n }\n\n getMinMaxValidators(): ValidatorFn[] {\n return [C8yValidators.minMaxValidator(), C8yValidators.requireBothMinAndMax()];\n }\n\n getUnitValidators(): ValidatorFn[] {\n return [];\n }\n\n getTargetValidators(): ValidatorFn[] {\n return [];\n }\n\n getOverallValidators(): ValidatorFn[] {\n return [\n C8yValidators.withinScale('redRange.min'),\n C8yValidators.withinScale('redRange.max'),\n C8yValidators.withinScale('yellowRange.min'),\n C8yValidators.withinScale('yellowRange.max'),\n C8yValidators.withinScale('target')\n ];\n }\n\n getMinMaxFormGroup(): FormGroup {\n return this.formBuilder.group(\n { min: [undefined, []], max: [undefined, []] },\n { validators: this.getMinMaxValidators() }\n );\n }\n\n getChartFormGroup(): FormGroup {\n return this.formBuilder.group({\n renderType: [CHART_RENDER_TYPES[0].val, []],\n lineType: [CHART_LINE_TYPES[0].val, []],\n yAxisType: [AXIS_TYPES[0].val, []]\n });\n }\n\n getDisplayFormGroup(): FormGroup {\n return this.formBuilder.group({\n renderType: [CHART_RENDER_TYPES[0].val, []]\n });\n }\n\n getTargetFormGroup(): FormGroup {\n return this.formBuilder.group({\n id: [undefined, []],\n name: [undefined, []]\n });\n }\n\n private convertStringToNumber(possibleString: string | number): number {\n if (typeof possibleString === 'string') {\n try {\n return Number.parseFloat(possibleString);\n } catch {\n return undefined;\n }\n } else {\n return possibleString;\n }\n }\n}\n","import { Component, forwardRef, Input, OnInit } from '@angular/core';\nimport {\n AbstractControl,\n ControlValueAccessor,\n FormGroup,\n NG_VALIDATORS,\n NG_VALUE_ACCESSOR,\n ValidationErrors,\n Validator,\n FormsModule,\n ReactiveFormsModule\n} from '@angular/forms';\nimport { map, take } from 'rxjs/operators';\nimport { DatapointAttributesFormValidationService } from './datapoint-attributes-form-validation.service';\nimport {\n AXIS_TYPES,\n AxisTypes,\n CHART_LINE_TYPES,\n CHART_RENDER_TYPES,\n ChartLineTypes,\n ChartRenderTypes\n} from './datapoint-attributes.model';\nimport { NgIf, NgFor, NgClass, NgTemplateOutlet, KeyValuePipe } from '@angular/common';\nimport {\n C8yTranslateDirective,\n FormGroupComponent,\n RequiredInputPlaceholderDirective,\n MessagesComponent,\n MessageDirective,\n C8yTranslatePipe\n} from '@c8y/ngx-components';\nimport { PopoverDirective } from 'ngx-bootstrap/popover';\n\n@Component({\n selector: 'c8y-datapoint-attributes-form',\n templateUrl: './datapoint-attributes-form.component.html',\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => DatapointAttributesFormComponent),\n multi: true\n },\n {\n provide: NG_VALIDATORS,\n useExisting: forwardRef(() => DatapointAttributesFormComponent),\n multi: true\n }\n ],\n imports: [\n FormsModule,\n ReactiveFormsModule,\n NgIf,\n C8yTranslateDirective,\n FormGroupComponent,\n RequiredInputPlaceholderDirective,\n MessagesComponent,\n NgFor,\n MessageDirective,\n NgClass,\n NgTemplateOutlet,\n PopoverDirective,\n C8yTranslatePipe,\n KeyValuePipe\n ]\n})\nexport class DatapointAttributesFormComponent implements ControlValueAccessor, Validator, OnInit {\n @Input() selectableChartRenderTypes: ChartRenderTypes[] = [];\n @Input() selectableChartLineTypes: ChartLineTypes[] = [];\n @Input() selectableAxisTypes: AxisTypes[] = [];\n @Input() showTarget = true;\n @Input() showRange = true;\n @Input() showYellowRange = true;\n @Input() showRedRange = true;\n @Input() showChart = true;\n @Input() showFormIfTemplateWasSelected = false;\n formGroup: FormGroup;\n range: FormGroup;\n yellowRange: FormGroup;\n redRange: FormGroup;\n chart: FormGroup;\n display: FormGroup;\n rawValue: any = {};\n CHART_RENDER_TYPES: Array<(typeof CHART_RENDER_TYPES)[number]> = Array.from(CHART_RENDER_TYPES);\n CHART_LINE_TYPES: Array<(typeof CHART_LINE_TYPES)[number]> = Array.from(CHART_LINE_TYPES);\n AXIS_TYPES: Array<(typeof AXIS_TYPES)[number]> = Array.from(AXIS_TYPES);\n\n customValidationErrorMessages: {\n [formField: string]: {\n [validationError: string]: string;\n };\n } = {};\n shouldForceInitialValidation = true;\n showOnlyDisplayForm: boolean;\n showChartForm: boolean;\n\n constructor(private formValidations: DatapointAttributesFormValidationService) {\n this.formGroup = this.formValidations.getDefaultFormGroup();\n this.setSubForms();\n }\n\n ngOnInit(): void {\n this.initializeFormVisibility();\n this.filterChartTypes();\n }\n\n validate(_control: AbstractControl): ValidationErrors {\n if (this.formGroup?.get('series')?.errors?.noPeriods) {\n return this.formGroup?.get('series')?.errors;\n }\n return this.formGroup?.valid ? null : { formInvalid: {} };\n }\n\n writeValue(obj: any): void {\n this.rawValue = obj || {};\n if (obj) {\n this.formGroup.patchValue(this.formValidations.convertToFormGroupFormat(obj), {\n emitEvent: false\n });\n }\n if (this.shouldForceInitialValidation) {\n queueMicrotask(() => {\n this.formGroup.updateValueAndValidity();\n });\n this.shouldForceInitialValidation = false;\n }\n }\n\n registerOnChange(fn: any): void {\n this.formGroup.valueChanges\n .pipe(\n map(formValue => this.formValidations.convertToBackendFormat(formValue, this.showChart)),\n map(formValue => Object.assign(this.rawValue, formValue))\n )\n .subscribe(fn);\n }\n\n registerOnTouched(fn: any): void {\n this.formGroup.valueChanges.pipe(take(1)).subscribe(fn);\n }\n\n setDisabledState(isDisabled: boolean): void {\n if (this.formGroup?.disabled === isDisabled) {\n return;\n }\n isDisabled ? this.formGroup.disable() : this.formGroup.enable();\n }\n\n private setSubForms() {\n if (!this.formGroup) {\n this.range = this.yellowRange = this.redRange = this.chart = undefined;\n return;\n }\n this.range = this.formGroup.get('range') as FormGroup;\n this.yellowRange = this.formGroup.get('yellowRange') as FormGroup;\n this.redRange = this.formGroup.get('redRange') as FormGroup;\n this.chart = this.formGroup.get('chart') as FormGroup;\n this.display = this.formGroup.get('display') as FormGroup;\n }\n\n private initializeFormVisibility(): void {\n this.showChartForm = this.chart && this.showChart;\n\n const hasLineTypes = !!this.selectableChartLineTypes?.length;\n const hasAxisTypes = !!this.selectableAxisTypes?.length;\n const hasRenderTypes =\n this.selectableChartRenderTypes?.length > 0 || this.selectableChartRenderTypes === undefined;\n\n this.showOnlyDisplayForm =\n !this.showChartForm && !hasLineTypes && !hasAxisTypes && hasRenderTypes;\n }\n\n private filterChartTypes(): void {\n this.filterRenderTypes();\n this.filterLineTypes();\n this.filterAxisTypes();\n }\n\n private filterRenderTypes(): void {\n if (!!this.selectableChartRenderTypes?.length) {\n this.CHART_RENDER_TYPES = this.CHART_RENDER_TYPES.filter(renderType =>\n this.selectableChartRenderTypes.includes(renderType.val)\n );\n }\n }\n\n private filterLineTypes(): void {\n if (!!this.selectableChartLineTypes?.length) {\n this.CHART_LINE_TYPES = this.CHART_LINE_TYPES.filter(lineType =>\n this.selectableChartLineTypes.includes(lineType.val)\n );\n }\n }\n\n private filterAxisTypes(): void {\n if (!!this.selectableAxisTypes?.length) {\n this.AXIS_TYPES = this.AXIS_TYPES.filter(axisType =>\n this.selectableAxisTypes.includes(axisType.val)\n );\n }\n }\n}\n","<div [formGroup]=\"formGroup\">\n <ng-container *ngIf=\"!rawValue?.__template || showFormIfTemplateWasSelected\">\n <fieldset\n class=\"c8y-fieldset\"\n *ngIf=\"formGroup.controls?.label || formGroup.controls?.unit || formGroup.controls?.target\"\n >\n <legend translate>Details</legend>\n <div class=\"row\">\n <div\n class=\"col-md-6\"\n *ngIf=\"formGroup.controls?.label\"\n >\n <c8y-form-group class=\"form-group-sm\">\n <label translate>Label</label>\n <input\n class=\"form-control\"\n name=\"label\"\n formControlName=\"label\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 'Temperature' }\"\n />\n <c8y-messages\n [show]=\"formGroup.controls.label.touched && formGroup.controls.label.errors\"\n >\n <c8y-message\n *ngFor=\"let item of customValidationErrorMessages['label'] | keyvalue\"\n [name]=\"item.key\"\n [text]=\"item.value\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n\n <div\n class=\"col-md-6\"\n *ngIf=\"formGroup.controls?.unit\"\n >\n <c8y-form-group class=\"form-group-sm\">\n <label translate>Unit</label>\n <input\n class=\"form-control\"\n name=\"unit\"\n formControlName=\"unit\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 'ºC' }\"\n />\n <c8y-messages\n [show]=\"formGroup.controls.unit.touched && formGroup.controls.unit.errors\"\n >\n <c8y-message\n *ngFor=\"let item of customValidationErrorMessages['unit'] | keyvalue\"\n [name]=\"item.key\"\n [text]=\"item.value\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n\n <div\n class=\"col-md-6\"\n *ngIf=\"formGroup.controls?.target && showTarget\"\n >\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{\n 'has-error':\n (range?.touched || formGroup.controls.target.touched) &&\n formGroup.controls?.target?.errors\n }\"\n >\n <label translate>Target</label>\n <input\n class=\"form-control\"\n name=\"target\"\n type=\"number\"\n formControlName=\"target\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 25 }\"\n />\n <c8y-messages\n [show]=\"\n (range?.touched || formGroup.controls.target.touched) &&\n formGroup.controls.target.errors\n \"\n >\n <c8y-message\n *ngFor=\"let item of customValidationErrorMessages['target'] | keyvalue\"\n [name]=\"item.key\"\n [text]=\"item.value\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n <fieldset\n class=\"c8y-fieldset\"\n *ngIf=\"range && showRange\"\n >\n <legend translate>Range</legend>\n <div\n class=\"row\"\n formGroupName=\"range\"\n >\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{ 'has-error': range?.touched && range?.controls?.min?.errors }\"\n >\n <label translate>Min</label>\n <input\n class=\"form-control\"\n name=\"min\"\n type=\"number\"\n formControlName=\"min\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 0 }\"\n />\n <c8y-messages [show]=\"range?.touched && range.controls?.min?.errors\"></c8y-messages>\n </c8y-form-group>\n </div>\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{ 'has-error': range?.touched && range?.controls?.max?.errors }\"\n >\n <label translate>Max</label>\n <input\n class=\"form-control\"\n name=\"max\"\n type=\"number\"\n formControlName=\"max\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 100 }\"\n />\n <c8y-messages [show]=\"range?.touched && range.controls?.max?.errors\">\n <c8y-message\n *ngFor=\"let item of customValidationErrorMessages['max'] | keyvalue\"\n [name]=\"item.key\"\n [text]=\"item.value\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n\n <fieldset\n class=\"c8y-fieldset\"\n formGroupName=\"yellowRange\"\n *ngIf=\"yellowRange && showYellowRange\"\n >\n <legend translate>Yellow range</legend>\n <div class=\"row\">\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{\n 'has-error':\n (range?.touched || yellowRange?.touched) && yellowRange?.controls?.min?.errors\n }\"\n >\n <label translate>Min</label>\n <input\n class=\"form-control\"\n name=\"min\"\n type=\"number\"\n formControlName=\"min\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 50 }\"\n />\n <c8y-messages\n [show]=\"(range?.touched || yellowRange?.touched) && yellowRange.controls?.min?.errors\"\n ></c8y-messages>\n </c8y-form-group>\n </div>\n\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{\n 'has-error':\n (range?.touched || yellowRange?.touched) && yellowRange?.controls?.max?.errors\n }\"\n >\n <label translate>Max</label>\n <input\n class=\"form-control\"\n name=\"max\"\n type=\"number\"\n formControlName=\"max\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 75 }\"\n />\n <c8y-messages\n [show]=\"(range?.touched || yellowRange?.touched) && yellowRange.controls?.max?.errors\"\n ></c8y-messages>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n\n <fieldset\n class=\"c8y-fieldset\"\n formGroupName=\"redRange\"\n *ngIf=\"redRange && showRedRange\"\n >\n <legend translate>Red range</legend>\n <div class=\"row\">\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{\n 'has-error': (range?.touched || redRange?.touched) && redRange?.controls?.min?.errors\n }\"\n >\n <label translate>Min</label>\n <input\n class=\"form-control\"\n name=\"min\"\n type=\"number\"\n formControlName=\"min\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 75 }\"\n />\n <c8y-messages\n [show]=\"(range?.touched || redRange?.touched) && redRange.controls?.min?.errors\"\n ></c8y-messages>\n </c8y-form-group>\n </div>\n\n <div class=\"col-md-6\">\n <c8y-form-group class=\"form-group-sm\"\n [ngClass]=\"{\n 'has-error': (range?.touched || redRange?.touched) && redRange?.controls?.max?.errors\n }\"\n >\n <label translate>Max</label>\n <input\n class=\"form-control\"\n name=\"max\"\n type=\"number\"\n formControlName=\"max\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 100 }\"\n />\n <c8y-messages\n [show]=\"(range?.touched || redRange?.touched) && redRange.controls?.max?.errors\"\n ></c8y-messages>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n </ng-container>\n\n <fieldset\n class=\"c8y-fieldset\"\n *ngIf=\"showChartForm\"\n formGroupName=\"chart\"\n >\n <legend translate>Chart</legend>\n <div class=\"tight-grid\">\n <div\n class=\"col-xs-6 col-sm-4\"\n *ngIf=\"selectableChartRenderTypes?.length !== 0\"\n >\n <c8y-form-group class=\"form-group-sm\">\n <ng-container *ngTemplateOutlet=\"displayHelpButton\"></ng-container>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n formControlName=\"renderType\"\n >\n <option\n *ngFor=\"let type of CHART_RENDER_TYPES\"\n [ngValue]=\"type.val\"\n >\n {{ type.text | translate }}\n </option>\n </select>\n </div>\n </c8y-form-group>\n </div>\n <div\n class=\"col-xs-6 col-sm-4\"\n *ngIf=\"selectableChartLineTypes?.length !== 0\"\n >\n <c8y-form-group class=\"form-group-sm\">\n <label\n for=\"chartType\"\n translate\n >\n Chart type\n </label>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n formControlName=\"lineType\"\n >\n <option\n *ngFor=\"let type of CHART_LINE_TYPES\"\n [ngValue]=\"type.val\"\n >\n {{ type.text | translate }}\n </option>\n </select>\n <span></span>\n </div>\n </c8y-form-group>\n </div>\n <div\n class=\"col-xs-6 col-sm-4\"\n *ngIf=\"selectableAxisTypes?.length !== 0\"\n >\n <c8y-form-group class=\"form-group-sm\">\n <label\n for=\"yAxis\"\n translate\n >\n Y-axis\n </label>\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n formControlName=\"yAxisType\"\n >\n <option\n *ngFor=\"let type of AXIS_TYPES\"\n [ngValue]=\"type.val\"\n >\n {{ type.text | translate }}\n </option>\n </select>\n <span></span>\n </div>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n <fieldset\n class=\"c8y-fieldset\"\n *ngIf=\"showOnlyDisplayForm\"\n formGroupName=\"display\"\n >\n <legend>\n <ng-container *ngTemplateOutlet=\"displayHelpButton\"></ng-container>\n </legend>\n <div class=\"tight-grid\">\n <div class=\"col-xs-6 col-sm-4\">\n <c8y-form-group class=\"form-group-sm\">\n <div class=\"c8y-select-wrapper\">\n <select\n class=\"form-control\"\n formControlName=\"renderType\"\n >\n <option\n *ngFor=\"let type of CHART_RENDER_TYPES\"\n [ngValue]=\"type.val\"\n >\n {{ type.text | translate }}\n </option>\n </select>\n </div>\n </c8y-form-group>\n </div>\n </div>\n </fieldset>\n</div>\n\n<ng-template #displayHelpButton>\n <label>\n {{ 'Display' | translate }}\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{ 'Value displayed when data is aggregated' | translate }}\"\n triggers=\"focus\"\n type=\"button\"\n placement=\"right\"\n ></button>\n </label>\n</ng-template>\n","import { IIdentified, IManagedObject } from '@c8y/client';\nimport { MillerViewComponent } from '@c8y/ngx-components/assets-navigator';\nimport { SupportedIconsSuggestions } from '@c8y/ngx-components/icon-selector/icons';\nimport {\n AxisTypes,\n ChartLineTypes,\n ChartRenderTypes\n} from './datapoint-attributes-form/datapoint-attributes.model';\n\nexport const DATAPOINT_LIBRARY_FRAGMENT = 'c8y_Kpi';\n\nexport interface KPIDetails {\n fragment: string;\n series: string;\n orientation?: string | null;\n __target?: IIdentified | null;\n __active?: boolean | null;\n __template?: string | null | number;\n unit?: string | null;\n min?: number | null;\n color?: string | null;\n max?: number | null;\n label?: string | null;\n target?: number | null;\n yellowRangeMax?: number | null;\n yellowRangeMin?: number | null;\n redRangeMin?: number | null;\n redRangeMax?: number | null;\n description?: string | null;\n renderType?: string | null;\n lineType?: string | null;\n yAxisType?: string | null;\n [key: string]: any;\n}\n\nexport interface ManagedObjectKPI extends IManagedObject {\n [DATAPOINT_LIBRARY_FRAGMENT]: KPIDetails;\n}\n\nexport interface DatapointAction {\n label: string;\n callback: (datapoint: KPIDetails) => void;\n icon: SupportedIconsSuggestions;\n}\n\nexport interface DatapointAttributesFormConfig {\n showTarget: boolean;\n showRange: boolean;\n showYellowRange: boolean;\n showRedRange: boolean;\n showChart: boolean;\n showFormIfTemplateWasSelected: boolean;\n selectableChartRenderTypes: ChartRenderTypes[];\n selectableChartLineTypes: ChartLineTypes[];\n selectableAxisTypes: AxisTypes[];\n}\n\nexport type DatapointSelectorModalAssetSelectorOptions = Omit<\n MillerViewComponent,\n | 'multi'\n | 'singleColumn'\n | 'onSelected'\n | 'millerViewWrapper'\n | 'columns'\n | 'ngOnInit'\n | 'ngOnChanges'\n | 'addNewColumn'\n | 'onSelectionChange'\n>;\n","import { Injectable } from '@angular/core';\nimport {\n IIdentified,\n IManagedObject,\n InventoryService,\n IResultList,\n MeasurementService\n} from '@c8y/client';\nimport { AppStateService, ColorService, MAX_PAGE_SIZE } from '@c8y/ngx-components';\nimport { get, sortBy, uniq } from 'lodash-es';\nimport { filter } from 'rxjs/operators';\nimport {\n DATAPOINT_LIBRARY_FRAGMENT,\n KPIDetails,\n ManagedObjectKPI\n} from './datapoint-selection.model';\n\n@Injectable({ providedIn: 'root' })\nexport class DatapointLibraryService {\n protected cache: Promise<ManagedObjectKPI[]>;\n constructor(\n private inventory: InventoryService,\n private appState: AppStateService,\n private measurements: MeasurementService,\n private color: ColorService\n ) {\n this.appState.currentUser.pipe(filter(user => !user)).subscribe(() => {\n this.cache = undefined;\n });\n }\n\n async getAllDatapointLibraryEntriesCached(forceCacheRenew = false): Promise<ManagedObjectKPI[]> {\n if (forceCacheRenew) {\n this.cache = undefined;\n }\n if (!this.cache) {\n this.cache = this.getAllDatapointLibraryEntries();\n }\n return this.cache;\n }\n\n async getFirstDatapointLibraryPage(): Promise<IResultList<ManagedObjectKPI>> {\n const filterObj = {\n currentPage: 1,\n pageSize: 50,\n fragmentType: DATAPOINT_LIBRARY_FRAGMENT,\n withTotalPages: true\n };\n return (await this.inventory.list(filterObj)) as IResultList<ManagedObjectKPI>;\n }\n\n async getAllDatapointLibraryItemsCached(): Promise<KPIDetails[]> {\n if (!this.cache) {\n this.cache = this.getAllDatapointLibraryEntries();\n }\n const res = await this.cache;\n return res.map(tmp => tmp[DATAPOINT_LIBRARY_FRAGMENT]);\n }\n\n async updateDatapoints(\n datapoints: KPIDetails[],\n skipUpdatingTarget = false\n ): Promise<KPIDetails[]> {\n if (!Array.isArray(datapoints)) {\n return datapoints;\n }\n const currentTargetsPromise: Promise<IManagedObject[]> = !skipUpdatingTarget\n ? this.getCurrentVersionOfTargetsFromDatapoints(datapoints)\n : Promise.resolve([]);\n const [currentTemplates, currentTargets] = await Promise.all([\n this.getCurrentTemplatesFromDatapoints(datapoints),\n currentTargetsPromise\n ]);\n const currentTemplateVersions = currentTemplates\n .map(tmp => this.mapDatapointLibraryEntry(tmp as ManagedObjectKPI))\n .filter(tmp => !!tmp);\n for (const datapoint of datapoints) {\n const { fragment, series, __active, __target, color, label, __template } = datapoint;\n const foundCurrentTemplateVersion = currentTemplateVersions.find(\n tmp => tmp.__template === datapoint.__template\n );\n if (foundCurrentTemplateVersion) {\n Object.assign(datapoint, foundCurrentTemplateVersion);\n Object.assign(datapoint, {\n fragment,\n series,\n __active,\n __target,\n color,\n label,\n __template\n });\n }\n\n const foundCurrentTarget = currentTargets.find(target => target.id === __target?.id);\n if (foundCurrentTarget) {\n const { id, name } = foundCurrentTarget;\n datapoint.__target = { id, name };\n }\n }\n return datapoints;\n }\n\n async getDatapointsOfAsset(\n parentReference: IIdentified,\n ignoreDatapointTemplates?: boolean,\n datapointTemplatesOnly = false\n ): Promise<KPIDetails[]> {\n const [kpiResponse, details] = await Promise.all([\n (ignoreDatapointTemplates\n ? Promise.resolve(null)\n : this.inventory.assetKPIsList(parentReference, { pageSize: MAX_PAGE_SIZE })) as Promise<\n IResultList<ManagedObjectKPI>\n >,\n this.inventory.getMeasurementsAndSeries(parentReference)\n ]);\n const kpis = kpiResponse && kpiResponse.data ? kpiResponse.data : [];\n const sortedDetails = sortBy(details, ['fragment', 'series']);\n\n return await this.combineFragmentSeriesTuplesWithDetails(\n sortedDetails,\n parentReference,\n kpis,\n datapointTemplatesOnly\n );\n }\n\n /**\n * Requests the last measurement with the given fragment and series to extract it's unit.\n * If the source attribute is provided, it will check the last measurement for this specific source.\n * @returns found unit or an empty string instead\n */\n async guessUnitOfDatapoint(\n fragment: string,\n series: string,\n source?: IIdentified\n ): Promise<string> {\n const measurementfilter: any = {\n valueFragmentSeries: series,\n valueFragmentType: fragment,\n pageSize: 1,\n revert: true,\n dateFrom: '1970-01-01'\n };\n if (source?.id) {\n measurementfilter.source = source?.id;\n }\n try {\n const { data: lastMeasurements } = await this.measurements.list(measurementfilter);\n const measurement = lastMeasurements[0];\n if (measurement) {\n const pathToUnit = `${fragment}.${series}.unit`;\n const unit = get(measurement, pathToUnit);\n if (unit?.length && typeof unit === 'string') {\n return unit;\n }\n }\n } catch {\n // nothing to do\n }\n return '';\n }\n\n protected async combineFragmentSeriesTuplesWithDetails(\n tuples: Array<{ fragment: string; series: string }>,\n target: IIdentified,\n kpis: ManagedObjectKPI[],\n datapointTemplatesOnly = false\n ) {\n const datapoints = tuples\n .map(tuple => {\n const foundDatapointLibraryEntry = kpis.find(\n kpi =>\n kpi[DATAPOINT_LIBRARY_FRAGMENT] &&\n kpi[DATAPOINT_LIBRARY_FRAGMENT].fragment === tuple.fragment &&\n kpi[DATAPOINT_LIBRARY_FRAGMENT].series === tuple.series\n );\n if (!foundDatapointLibraryEntry && datapointTemplatesOnly) {\n return null;\n }\n const datapoint: KPIDetails =\n this.mapDatapointLibraryEntry(foundDatapointLibraryEntry) || tuple;\n if (!datapoint.label) {\n datapoint.label = `${datapoint.fragment} → ${datapoint.series}`;\n }\n if (!datapoint.unit?.length) {\n datapoint.unit = '';\n }\n datapoint.__target = target;\n return datapoint;\n })\n .filter(Boolean);\n await this.assignColorToDatapoints(datapoints);\n return datapoints;\n }\n\n protected async assignColorToDatapoints(datapoints: KPIDetails[]): Promise<void> {\n const datapointsWithoutColor = datapoints.filter(datapoint => !datapoint.color);\n await Promise.all(\n datapointsWithoutColor.map(datapoint =>\n this.color\n .generateColorForDatapoint(datapoint.fragment, datapoint.series)\n .then(color => (datapoint.color = color))\n )\n );\n }\n\n protected async getAllDatapointLibraryEntries(): Promise<ManagedObjectKPI[]> {\n const entries = new Array<ManagedObjectKPI>();\n const filterObj = {\n currentPage: 1,\n pageSize: MAX_PAGE_SIZE,\n fragmentType: DATAPOINT_LIBRARY_FRAGMENT\n };\n let res = await this.inventory.list(filterObj);\n while (res.data.length) {\n entries.push(...(res.data as ManagedObjectKPI[]));\n if (res.data.length < res.paging.pageSize) {\n break;\n }\n if (!res.paging.nextPage) {\n break;\n }\n\n res = await res.paging.next();\n }\n return entries;\n }\n\n protected mapDatapointLibraryEntry(entry: ManagedObjectKPI): KPIDetails {\n if (!entry || !entry[DATAPOINT_LIBRARY_FRAGMENT]) {\n return null;\n }\n\n const datapoint = entry[DATAPOINT_LIBRARY_FRAGMENT];\n datapoint.__template = entry.id;\n return datapoint;\n }\n\n protected async getCurrentTemplatesFromDatapoints(\n datapoints: KPIDetails[]\n ): Promise<IManagedObject[]> {\n const datapointsWithTemplateId = datapoints.filter(dp => !!dp.__template);\n const usedTemplateIds = datapointsWithTemplateId.map(dp => dp.__template);\n return await this.getMOsByIds(usedTemplateIds);\n }\n\n protected async getCurrentVersionOfTargetsFromDatapoints(\n datapoints: KPIDetails[]\n ): Promise<IManagedObject[]> {\n const datapointsWithTarget = datapoints.filter(dp => !!dp.__target?.id);\n const usedTargetIds = datapointsWithTarget.map(dp => dp.__target.id);\n return await this.getMOsByIds(usedTargetIds);\n }\n\n protected async getMOsByIds(ids: Array<string | number>): Promise<IManagedObject[]> {\n const uniqManagedObjectIds = uniq(ids);\n if (!uniqManagedObjectIds.length) {\n return [];\n }\n try {\n const { data: managedObjects } = await this.inventory.list({\n ids: uniqManagedObjectIds.join(),\n pageSize: MAX_PAGE_SIZE\n });\n return managedObjects;\n } catch {\n // Fail silently in case we are not able to talk to the inventory API.\n // Should only be reached in case of an server side error.\n // instead of failing, pretend like we didn't receive any items.\n console.warn(\n `Failed to get the current version of the following managedObjects: ${uniqManagedObjectIds.join()}.`\n );\n return [];\n }\n }\n}\n","import { Component, Input } from '@angular/core';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport { IconDirective, C8yTranslatePipe } from '@c8y/ngx-components';\nimport { KPIDetails } from '../datapoint-selection.model';\nimport { NgFor, NgIf, NgSwitch, NgSwitchCase, NgSwitchDefault } from '@angular/common';\n\n@Component({\n selector: 'c8y-datapoint-template-popover',\n templateUrl: './datapoint-template-popover.component.html',\n imports: [IconDirective, NgFor, NgIf, NgSwitch, NgSwitchCase, NgSwitchDefault, C8yTranslatePipe]\n})\nexport class DatapointTemplatePopoverComponent {\n @Input() datapoint: KPIDetails;\n @Input() attributes: Array<{\n label: string;\n labelColor?: string;\n key: keyof KPIDetails;\n }> = [\n {\n label: gettext('Fragment'),\n key: 'fragment'\n },\n {\n label: gettext('Series'),\n key: 'series'\n },\n {\n label: gettext('Unit'),\n key: 'unit'\n },\n {\n label: gettext('Range'),\n key: 'min'\n },\n {\n label: gettext('Target'),\n key: 'target'\n },\n {\n label: gettext('Yellow range'),\n labelColor: 'yellow',\n key: 'yellowRangeMin'\n },\n {\n label: gettext('Red range'),\n labelColor: 'red',\n key: 'redRangeMin'\n }\n ];\n}\n","<p class=\"text-medium\">\n <i c8yIcon=\"circle\" [style.color]=\"datapoint.color\"></i>\n {{ datapoint.label }}\n</p>\n<p>{{ datapoint.description }}</p>\n\n<ul class=\"list-unstyled small p-t-16\">\n <ng-container *ngFor=\"let attribute of attributes; let i = index\">\n <li\n class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\"\n *ngIf=\"datapoint[attribute.key] !== undefined\"\n >\n <label class=\"small m-b-0 m-r-8\">\n <i *ngIf=\"attribute.labelColor\" c8yIcon=\"square\" [style.color]=\"attribute.labelColor\"></i>\n {{ attribute.label | translate }}\n </label>\n <ng-container [ngSwitch]=\"attribute.key\">\n <span *ngSwitchCase=\"'min'\" class=\"m-l-auto\">\n {{ datapoint['min'] }} — {{ datapoint['max'] }}\n </span>\n <span *ngSwitchCase=\"'yellowRangeMin'\" class=\"m-l-auto\">\n {{ datapoint['yellowRangeMin'] }} — {{ datapoint['yellowRangeMax'] }}\n </span>\n <span *ngSwitchCase=\"'redRangeMin'\" class=\"m-l-auto\">\n {{ datapoint['redRangeMin'] }} — {{ datapoint['redRangeMax'] }}\n </span>\n <span *ngSwitchDefault class=\"m-l-auto\">\n {{ datapoint[attribute.key] }}\n </span>\n </ng-container>\n </li>\n </ng-container>\n</ul>\n","import { Component, ContentChild, EventEmitter, forwardRef, Input, Output } from '@angular/core';\nimport {\n AbstractControl,\n ControlValueAccessor,\n FormBuilder,\n FormGroup,\n NG_VALIDATORS,\n NG_VALUE_ACCESSOR,\n ValidationErrors,\n Validator,\n FormsModule,\n ReactiveFormsModule\n} from '@angular/forms';\nimport { IResultList } from '@c8y/client';\nimport { Observable, pipe } from 'rxjs';\nimport { map, startWith, take } from 'rxjs/operators';\nimport {\n ForOfFilterPipe,\n ListItemDragHandleComponent,\n ListItemComponent,\n ListItemCheckboxComponent,\n RequiredInputPlaceholderDirective,\n HighlightComponent,\n IconDirective,\n ListItemActionComponent,\n ListItemCollapseComponent,\n C8yTranslateDirective,\n TypeaheadComponent,\n ForOfDirective,\n ListItemIconComponent,\n C8yTranslatePipe\n} from '@c8y/ngx-components';\nimport {\n DatapointAction,\n DatapointAttributesFormConfig,\n DATAPOINT_LIBRARY_FRAGMENT,\n KPIDetails,\n ManagedObjectKPI\n} from '../datapoint-selection.model';\n\nimport { NgIf, NgFor, AsyncPipe } from '@angular/common';\nimport { PopoverDirective } from 'ngx-bootstrap/popover';\nimport { TooltipDirective } from 'ngx-bootstrap/tooltip';\nimport { DatapointTemplatePopoverComponent } from '../datapoint-template-popover/datapoint-template-popover.component';\nimport { DatapointAttributesFormComponent } from '../datapoint-attributes-form/datapoint-attributes-form.component';\n\nexport const AddButtonTypes = {\n none: 'none',\n addRemove: 'add-remove',\n select: 'select'\n} as const;\nexport type AddButtonType = (typeof AddButtonTypes)[keyof typeof AddButtonTypes];\n\n@Component({\n selector: 'c8y-datapoint-selector-list-item',\n templateUrl: './datapoint-selector-list-item.component.html',\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => DatapointSelectorListItemComponent),\n multi: true\n },\n {\n provide: NG_VALIDATORS,\n useExisting: forwardRef(() => DatapointSelectorListItemComponent),\n multi: true\n }\n ],\n imports: [\n ListItemComponent,\n FormsModule,\n ReactiveFormsModule,\n ListItemDragHandleComponent,\n NgIf,\n ListItemCheckboxComponent,\n RequiredInputPlaceholderDirective,\n HighlightComponent,\n IconDirective,\n PopoverDirective,\n TooltipDirective,\n ListItemActionComponent,\n NgFor,\n ListItemCollapseComponent,\n C8yTranslateDirective,\n TypeaheadComponent,\n ForOfDirective,\n ListItemIconComponent,\n DatapointTemplatePopoverComponent,\n DatapointAttributesFormComponent,\n C8yTranslatePipe,\n AsyncPipe\n ]\n})\nexport class DatapointSelectorListItemComponent implements ControlValueAccessor, Validator {\n formGroup: FormGroup;\n @Input() defaultFormOptions: Partial<DatapointAttributesFormConfig> = {};\n @Input() isSelected = false;\n @Input() isCollapsed = true;\n @Input() addButtonType: AddButtonType = AddButtonTypes.addRemove;\n @Input() editable = true;\n @Input() showActiveToggle = false;\n @Input() activeToggleDisabled = false;\n @Input() showOptions = false;\n @Input() datapointLibraryEntries: Observable<IResultList<ManagedObjectKPI>>;\n @Input() actions: DatapointAction[] = [];\n @Input() optionToRemove = false;\n @Input() hasUnlinkTemplateOption = false;\n @Output() added = new EventEmitter<KPIDetails>();\n @Output() removed = new EventEmitter<KPIDetails>();\n @Input() colorPickerDisabled = true;\n @Input() disableTypeaheadIfSelected = false;\n @Input() highlightText: string;\n @ContentChild(ListItemDragHandleComponent) dragHandle: ListItemDragHandleComponent;\n\n pattern = '';\n filterPipe: ForOfFilterPipe<ManagedObjectKPI>;\n isValid$: Observable<boolean>;\n errorMessage: string;\n AddButtonTypes = AddButtonTypes;\n\n constructor(private formBuilder: FormBuilder) {\n this.formGroup = this.formBuilder.group({\n details: [],\n color: [],\n __active: [],\n __target: [],\n fragment: [],\n series: [],\n __template: []\n });\n this.isValid$ = this.formGroup.statusChanges.pipe(\n map(status => status === 'VALID'),\n startWith(this.formGroup.valid)\n );\n }\n\n validate(_control: AbstractControl): ValidationErrors {\n if (this.formGroup?.invalid) {\n Object.keys(this.formGroup.controls).forEach(controlName => {\n const control = this.formGroup.controls[controlName];\n if (control.invalid) {\n const errors = control.errors;\n if (errors) {\n this.errorMessage = Object.values(errors)[0].message;\n }\n }\n });\n }\n return this.formGroup?.valid ? null : { formInvalid: {} };\n }\n\n writeValue(obj: any): void {\n this.formGroup.patchValue({ ...obj, details: obj });\n }\n\n registerOnChange(fn: any): void {\n this.formGroup.valueChanges.pipe(map(tmp => this.transformFormValue(tmp))).subscribe(fn);\n }\n\n registerOnTouched(fn: any): void {\n this.formGroup.valueChanges.pipe(take(1)).subscribe(fn);\n }\n\n setDisabledState(isDisabled: boolean): void {\n if (this.formGroup?.disabled === isDisabled) {\n return;\n }\n isDisabled ? this.formGroup.disable() : this.formGroup.enable();\n }\n\n collapse() {\n this.isCollapsed = !this.isCollapsed;\n }\n\n addOrRemoveItem() {\n const value = this.transformFormValue(this.formGroup.value);\n if (this.isSelected) {\n this.removed.emit(value);\n } else {\n this.added.emit(value);\n }\n }\n\n remove() {\n this.removed.emit(this.transformFormValue(this.formGroup.value));\n }\n\n setPipe(filterStr: string) {\n this.pattern = filterStr;\n this.filterPipe = pipe(\n map((data: ManagedObjectKPI[]) => {\n return this.filterDatapointLabel(data, filterStr);\n })\n );\n }\n\n unlinkDatapointTemplate(): void {\n const details = this.formGroup.value.details || {};\n this.resetUnusedProperties(details);\n this.formGroup.patchValue({ __template: undefined, details });\n }\n\n dataPointTemplateSelected(template: ManagedObjectKPI) {\n const attributesToAssign: Array<keyof KPIDetails> = [\n 'color',\n 'label',\n 'min',\n 'max',\n 'yellowRangeMax',\n 'yellowRangeMin',\n 'redRangeMax',\n 'redRangeMin',\n 'target',\n 'orientation',\n 'unit'\n ];\n const { fragment, series, __target, __active } = this.formGroup.value;\n const dataPoint: KPIDetails = {\n fragment,\n series,\n __active,\n __target,\n __template: template.id\n };\n for (const attribute of attributesToAssign) {\n const value = template[DATAPOINT_LIBRARY_FRAGMENT][attribute];\n dataPoint[attribute] = value;\n }\n this.writeValue(dataPoint);\n this.setPipe('');\n }\n\n private resetUnusedProperties(details: Partial<KPIDetails>): void {\n const { showTarget, showYellowRange, showRedRange } = this.defaultFormOptions;\n details.__template = undefined;\n if (!showTarget) {\n details.target = undefined;\n }\n if (!showYellowRange) {\n details.yellowRangeMin = undefined;\n details.yellowRangeMax = undefined;\n }\n if (!showRedRange) {\n details.redRangeMin = undefined;\n details.redRangeMax = undefined;\n }\n }\n\n private filterDatapointLabel(kpis: ManagedObjectKPI[], filterStr: string): ManagedObjectKPI[] {\n return kpis.filter(\n (mo: ManagedObjectKPI) =>\n mo[DATAPOINT_LIBRARY_FRAGMENT] &&\n mo[DATAPOINT_LIBRARY_FRAGMENT].label &&\n typeof mo[DATAPOINT_LIBRARY_FRAGMENT].label === 'string' &&\n mo[DATAPOINT_LIBRARY_FRAGMENT].label.toLowerCase().indexOf(filterStr.toLowerCase()) > -1\n );\n }\n\n private transformFormValue(formValue: any) {\n const obj = Object.assign({}, formValue.details || {}, formValue);\n delete obj.details;\n return obj;\n }\n}\n","<c8y-li class=\"c8y-list__item__collapse--container-small\" [formGroup]=\"formGroup\" #li>\n <c8y-li-drag-handle><ng-content select=\"c8y-li-drag-handle\"></ng-content></c8y-li-drag-handle>\n <c8y-li-checkbox\n class=\"a-s-center p-r-0\"\n *ngIf=\"showActiveToggle\"\n [displayAsSwitch]=\"true\"\n formControlName=\"__active\"\n (click)=\"$event.stopPropagation()\"\n ></c8y-li-checkbox>\n\n <div class=\"d-flex a-i-center \">\n <div class=\"c8y-list__item__colorpicker p-t-0 p-b-0 p-l-16\" [title]=\"'Change color' | translate\">\n <div class=\"c8y-colorpicker\">\n <input\n type=\"color\"\n [at