diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKeyValueType.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKeyValueType.java new file mode 100644 index 0000000000..0239d42451 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKeyValueType.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.query; + +public enum EntityKeyValueType { + STRING, + NUMERIC, + BOOLEAN, + DATE_TIME +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/KeyFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/KeyFilter.java index 120f5e1fed..6ab5ce7736 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/KeyFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/KeyFilter.java @@ -21,6 +21,7 @@ import lombok.Data; public class KeyFilter { private EntityKey key; + private EntityKeyValueType valueType; private KeyFilterPredicate predicate; } diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index 0f5f1d554a..a510735855 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -62,6 +62,7 @@ import { entityInfoFields, EntityKey, EntityKeyType, + EntityKeyValueType, FilterPredicateType, singleEntityDataPageLink, StringOperation @@ -399,6 +400,7 @@ export class EntityService { keyFilters: searchText && searchText.length ? [ { key: nameField, + valueType: EntityKeyValueType.STRING, predicate: { type: FilterPredicateType.STRING, operation: StringOperation.STARTS_WITH, @@ -593,10 +595,10 @@ export class EntityService { return entityTypes; } - private getEntityFieldKeys (entityType: EntityType, searchText: string): Array { + private getEntityFieldKeys(entityType: EntityType, searchText: string): Array { const entityFieldKeys: string[] = [entityFields.createdTime.keyName]; const query = searchText.toLowerCase(); - switch(entityType) { + switch (entityType) { case EntityType.USER: entityFieldKeys.push(entityFields.name.keyName); entityFieldKeys.push(entityFields.email.keyName); @@ -863,7 +865,7 @@ export class EntityService { const tasks: Observable[] = []; const result: Device | Asset = entity as (Device | Asset); const additionalInfo = result.additionalInfo || {}; - if(result.label !== entityData.label || + if (result.label !== entityData.label || result.type !== entityData.type || additionalInfo.description !== entityData.description || (result.id.entityType === EntityType.DEVICE && (additionalInfo.gateway !== entityData.gateway)) ) { diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts index f6ef733560..8eb046c384 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts @@ -251,13 +251,14 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit } onToggleEditMode(isEdit: boolean) { - this.isEdit = isEdit; - if (!this.isEdit) { + if (!isEdit) { this.entityComponent.entity = this.entity; if (this.entityTabsComponent) { this.entityTabsComponent.entity = this.entity; } + this.isEdit = isEdit; } else { + this.isEdit = isEdit; this.editingEntity = deepClone(this.entity); this.entityComponent.entity = this.editingEntity; if (this.entityTabsComponent) { diff --git a/ui-ngx/src/app/modules/home/components/entity/entity.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity.component.ts index 72efaf8d45..cd6dcb6367 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity.component.ts @@ -65,7 +65,6 @@ export abstract class EntityComponent, set entity(entity: T) { this.entityValue = entity; if (this.entityForm) { - this.entityForm.reset(undefined, {emitEvent: false}); this.entityForm.markAsPristine(); this.updateForm(entity); } diff --git a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html index fe0b217063..a2e01977ea 100644 --- a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html @@ -37,6 +37,7 @@ @@ -45,6 +46,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.ts b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.ts index 09607d1690..623ca4f968 100644 --- a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.ts @@ -32,9 +32,10 @@ import { export interface ComplexFilterPredicateDialogData { complexPredicate: ComplexFilterPredicateInfo; key: string; - disabled: boolean; + readonly: boolean; isAdd: boolean; valueType: EntityKeyValueType; + displayUserParameters: boolean; } @Component({ @@ -73,6 +74,9 @@ export class ComplexFilterPredicateDialogComponent extends predicates: [this.data.complexPredicate.predicates, [Validators.required]] } ); + if (this.data.readonly) { + this.complexFilterFormGroup.disable({emitEvent: false}); + } } ngOnInit(): void { diff --git a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.html b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.html index 3157ff4f5b..1a6351be10 100644 --- a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.html @@ -19,11 +19,10 @@ filter.complex-filter diff --git a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.ts b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.ts index 8a9b5a8531..654eecdefd 100644 --- a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.ts @@ -48,6 +48,8 @@ export class ComplexFilterPredicateComponent implements ControlValueAccessor, On @Input() key: string; + @Input() displayUserParameters = true; + private propagateChange = null; private complexFilterPredicate: ComplexFilterPredicateInfo; @@ -79,11 +81,12 @@ export class ComplexFilterPredicateComponent implements ControlValueAccessor, On disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { - complexPredicate: deepClone(this.complexFilterPredicate), - disabled: this.disabled, + complexPredicate: this.disabled ? this.complexFilterPredicate : deepClone(this.complexFilterPredicate), + readonly: this.disabled, valueType: this.valueType, isAdd: false, - key: this.key + key: this.key, + displayUserParameters: this.displayUserParameters } }).afterClosed().subscribe( (result) => { diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.html b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.html index dd42106dd3..2da9a68e4d 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.html @@ -33,7 +33,8 @@ - +   @@ -50,6 +51,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.ts b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.ts index d993e973e4..c5eb501b16 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.ts @@ -62,6 +62,8 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni @Input() operation: ComplexOperation = ComplexOperation.AND; + @Input() displayUserParameters = true; + filterListFormGroup: FormGroup; valueTypeEnum = EntityKeyValueType; @@ -150,10 +152,11 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { complexPredicate: predicate.keyFilterPredicate as ComplexFilterPredicateInfo, - disabled: this.disabled, + readonly: this.disabled, valueType: this.valueType, key: this.key, - isAdd: true + isAdd: true, + displayUserParameters: this.displayUserParameters } }).afterClosed().pipe( map((result) => { diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.html b/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.html index 27ea3685bb..abe231219b 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.html @@ -35,11 +35,12 @@ -
-

filter.edit-filter-user-params

+

{{(data.readonly ? 'filter.filter-user-params' : 'filter.edit-filter-user-params') | translate}}

diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.ts b/ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.ts index fe4063bb12..58e644f725 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/filter-user-info-dialog.component.ts @@ -23,7 +23,7 @@ import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Valida import { Router } from '@angular/router'; import { DialogComponent } from '@app/shared/components/dialog.component'; import { - BooleanOperation, + BooleanOperation, createDefaultFilterPredicateUserInfo, EntityKeyValueType, generateUserFilterValueLabel, KeyFilterPredicateUserInfo, NumericOperation, StringOperation @@ -35,6 +35,7 @@ export interface FilterUserInfoDialogData { valueType: EntityKeyValueType; operation: StringOperation | BooleanOperation | NumericOperation; keyFilterPredicateUserInfo: KeyFilterPredicateUserInfo; + readonly: boolean; } @Component({ @@ -60,18 +61,24 @@ export class FilterUserInfoDialogComponent extends private translate: TranslateService) { super(store, router, dialogRef); + const userInfo: KeyFilterPredicateUserInfo = this.data.keyFilterPredicateUserInfo || createDefaultFilterPredicateUserInfo(); + this.filterUserInfoFormGroup = this.fb.group( { - editable: [this.data.keyFilterPredicateUserInfo.editable], - label: [this.data.keyFilterPredicateUserInfo.label], - autogeneratedLabel: [this.data.keyFilterPredicateUserInfo.autogeneratedLabel], - order: [this.data.keyFilterPredicateUserInfo.order] + editable: [userInfo.editable], + label: [userInfo.label], + autogeneratedLabel: [userInfo.autogeneratedLabel], + order: [userInfo.order] } ); this.onAutogeneratedLabelChange(); - this.filterUserInfoFormGroup.get('autogeneratedLabel').valueChanges.subscribe(() => { - this.onAutogeneratedLabelChange(); - }); + if (!this.data.readonly) { + this.filterUserInfoFormGroup.get('autogeneratedLabel').valueChanges.subscribe(() => { + this.onAutogeneratedLabelChange(); + }); + } else { + this.filterUserInfoFormGroup.disable({emitEvent: false}); + } } private onAutogeneratedLabelChange() { diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-user-info.component.html b/ui-ngx/src/app/modules/home/components/filter/filter-user-info.component.html index bcdae3452f..40cc46be35 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-user-info.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filter-user-info.component.html @@ -17,10 +17,9 @@ --> diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-user-info.component.ts b/ui-ngx/src/app/modules/home/components/filter/filter-user-info.component.ts index 968e6c95e9..5bf5c2a82b 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-user-info.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/filter-user-info.component.ts @@ -76,7 +76,7 @@ export class FilterUserInfoComponent implements ControlValueAccessor, OnInit { this.keyFilterPredicateUserInfo = keyFilterPredicateUserInfo; } - private openFilterUserInfoDialog() { + public openFilterUserInfoDialog() { this.dialog.open(FilterUserInfoDialogComponent, { disableClose: true, @@ -85,7 +85,8 @@ export class FilterUserInfoComponent implements ControlValueAccessor, OnInit { keyFilterPredicateUserInfo: deepClone(this.keyFilterPredicateUserInfo), valueType: this.valueType, key: this.key, - operation: this.operation + operation: this.operation, + readonly: this.disabled } }).afterClosed().subscribe( (result) => { diff --git a/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html b/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html index e0930b8e7d..fe820e58c6 100644 --- a/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html @@ -17,7 +17,7 @@ -->
-

{{(data.isAdd ? 'filter.add-key-filter' : 'filter.edit-key-filter') | translate}}

+

{{(data.isAdd ? 'filter.add-key-filter' : (data.readonly ? 'filter.key-filter' : 'filter.edit-key-filter')) | translate}}

@@ -87,7 +89,7 @@ [disabled]="(isLoading$ | async)" (click)="cancel()" cdkFocusInitial> - {{ 'action.cancel' | translate }} + {{ (data.readonly ? 'action.close' : 'action.cancel') | translate }} diff --git a/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts b/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts index e475d37f29..6dede3f203 100644 --- a/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts @@ -39,6 +39,9 @@ import { filter, map, startWith } from 'rxjs/operators'; export interface KeyFilterDialogData { keyFilter: KeyFilterInfo; isAdd: boolean; + displayUserParameters: boolean; + readonly: boolean; + telemetryKeysOnly: boolean; } @Component({ @@ -53,7 +56,10 @@ export class KeyFilterDialogComponent extends keyFilterFormGroup: FormGroup; - entityKeyTypes = [EntityKeyType.ENTITY_FIELD, EntityKeyType.ATTRIBUTE, EntityKeyType.TIME_SERIES]; + entityKeyTypes = + this.data.telemetryKeysOnly ? + [EntityKeyType.ATTRIBUTE, EntityKeyType.TIME_SERIES] : + [EntityKeyType.ENTITY_FIELD, EntityKeyType.ATTRIBUTE, EntityKeyType.TIME_SERIES]; entityKeyTypeTranslations = entityKeyTypeTranslationMap; @@ -95,32 +101,37 @@ export class KeyFilterDialogComponent extends predicates: [this.data.keyFilter.predicates, [Validators.required]] } ); - this.keyFilterFormGroup.get('valueType').valueChanges.subscribe((valueType: EntityKeyValueType) => { - const prevValue: EntityKeyValueType = this.keyFilterFormGroup.value.valueType; - const predicates: KeyFilterPredicate[] = this.keyFilterFormGroup.get('predicates').value; - if (prevValue && prevValue !== valueType && predicates && predicates.length) { - this.dialogs.confirm(this.translate.instant('filter.key-value-type-change-title'), - this.translate.instant('filter.key-value-type-change-message')).subscribe( - (result) => { - if (result) { - this.keyFilterFormGroup.get('predicates').setValue([]); - } else { - this.keyFilterFormGroup.get('valueType').setValue(prevValue, {emitEvent: false}); - } - } - ); - } - }); - this.keyFilterFormGroup.get('key.key').valueChanges.pipe( - filter((keyName) => this.keyFilterFormGroup.get('key.type').value === this.entityField && this.entityFields.hasOwnProperty(keyName)) - ).subscribe((keyName: string) => { - const prevValueType: EntityKeyValueType = this.keyFilterFormGroup.value.valueType; - const newValueType = this.entityFields[keyName]?.time ? EntityKeyValueType.DATE_TIME : EntityKeyValueType.STRING; - if (prevValueType !== newValueType) { - this.keyFilterFormGroup.get('valueType').patchValue(newValueType, {emitEvent: false}); - } - }); + if (!this.data.readonly) { + this.keyFilterFormGroup.get('valueType').valueChanges.subscribe((valueType: EntityKeyValueType) => { + const prevValue: EntityKeyValueType = this.keyFilterFormGroup.value.valueType; + const predicates: KeyFilterPredicate[] = this.keyFilterFormGroup.get('predicates').value; + if (prevValue && prevValue !== valueType && predicates && predicates.length) { + this.dialogs.confirm(this.translate.instant('filter.key-value-type-change-title'), + this.translate.instant('filter.key-value-type-change-message')).subscribe( + (result) => { + if (result) { + this.keyFilterFormGroup.get('predicates').setValue([]); + } else { + this.keyFilterFormGroup.get('valueType').setValue(prevValue, {emitEvent: false}); + } + } + ); + } + }); + + this.keyFilterFormGroup.get('key.key').valueChanges.pipe( + filter((keyName) => this.keyFilterFormGroup.get('key.type').value === this.entityField && this.entityFields.hasOwnProperty(keyName)) + ).subscribe((keyName: string) => { + const prevValueType: EntityKeyValueType = this.keyFilterFormGroup.value.valueType; + const newValueType = this.entityFields[keyName]?.time ? EntityKeyValueType.DATE_TIME : EntityKeyValueType.STRING; + if (prevValueType !== newValueType) { + this.keyFilterFormGroup.get('valueType').patchValue(newValueType, {emitEvent: false}); + } + }); + } else { + this.keyFilterFormGroup.disable({emitEvent: false}); + } this.entityFields = entityFields; this.entityFieldsList = Object.values(entityFields).map(entityField => entityField.keyName).sort(); diff --git a/ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html b/ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html index 213f86b702..5319138d11 100644 --- a/ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html @@ -46,9 +46,9 @@ +
+ + +
+
+
+ + +
+
+
+
+ + +
+ diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-key-filters-dialog.component.ts similarity index 51% rename from ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm-dialog.component.ts rename to ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-key-filters-dialog.component.ts index e3a42d7a6e..a25fe9a1c9 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-key-filters-dialog.component.ts @@ -14,68 +14,58 @@ /// limitations under the License. /// -import { - Component, - Inject, - OnInit, - SkipSelf -} from '@angular/core'; +import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; -import { DialogComponent } from '@shared/components/dialog.component'; import { Router } from '@angular/router'; -import { DeviceProfileAlarm } from '@shared/models/device.models'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { UtilsService } from '@core/services/utils.service'; +import { TranslateService } from '@ngx-translate/core'; +import { KeyFilter, keyFilterInfosToKeyFilters, keyFiltersToKeyFilterInfos } from '@shared/models/query/query.models'; -export interface DeviceProfileAlarmDialogData { - alarm: DeviceProfileAlarm; - isAdd: boolean; - isReadOnly: boolean; +export interface AlarmRuleKeyFiltersDialogData { + readonly: boolean; + keyFilters: Array; } @Component({ - selector: 'tb-device-profile-alarm-dialog', - templateUrl: './device-profile-alarm-dialog.component.html', - providers: [{provide: ErrorStateMatcher, useExisting: DeviceProfileAlarmDialogComponent}], + selector: 'tb-alarm-rule-key-filters-dialog', + templateUrl: './alarm-rule-key-filters-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: AlarmRuleKeyFiltersDialogComponent}], styleUrls: [] }) -export class DeviceProfileAlarmDialogComponent extends - DialogComponent implements OnInit, ErrorStateMatcher { +export class AlarmRuleKeyFiltersDialogComponent extends DialogComponent> + implements OnInit, ErrorStateMatcher { - alarmFormGroup: FormGroup; + readonly = this.data.readonly; + keyFilters = this.data.keyFilters; - isReadOnly = this.data.isReadOnly; - alarm = this.data.alarm; - isAdd = this.data.isAdd; + keyFiltersFormGroup: FormGroup; submitted = false; constructor(protected store: Store, protected router: Router, - @Inject(MAT_DIALOG_DATA) public data: DeviceProfileAlarmDialogData, - public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: AlarmRuleKeyFiltersDialogData, @SkipSelf() private errorStateMatcher: ErrorStateMatcher, - public fb: FormBuilder) { + public dialogRef: MatDialogRef>, + private fb: FormBuilder, + private utils: UtilsService, + public translate: TranslateService) { super(store, router, dialogRef); - this.isAdd = this.data.isAdd; - this.alarm = this.data.alarm; + + this.keyFiltersFormGroup = this.fb.group({ + keyFilters: [keyFiltersToKeyFilterInfos(this.keyFilters), Validators.required] + }); + if (this.readonly) { + this.keyFiltersFormGroup.disable({emitEvent: false}); + } } ngOnInit(): void { - this.alarmFormGroup = this.fb.group({ - id: [null, Validators.required], - alarmType: [null, Validators.required], - createRules: [null], - clearRule: [null], - propagate: [null], - propagateRelationTypes: [null] - }); - this.alarmFormGroup.reset(this.alarm, {emitEvent: false}); - if (this.isReadOnly) { - this.alarmFormGroup.disable({emitEvent: false}); - } } isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { @@ -90,10 +80,7 @@ export class DeviceProfileAlarmDialogComponent extends save(): void { this.submitted = true; - if (this.alarmFormGroup.valid) { - this.alarm = {...this.alarm, ...this.alarmFormGroup.value}; - this.dialogRef.close(this.alarm); - } + this.keyFilters = keyFilterInfosToKeyFilters(this.keyFiltersFormGroup.get('keyFilters').value); + this.dialogRef.close(this.keyFilters); } - } diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html index 51569d5311..3b7354c05a 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html @@ -15,8 +15,71 @@ limitations under the License. --> -
- - +
+
+
+
+ +
+ + +
+
+
+ + + +
+
+ + + + + {{ 'device-profile.condition-duration-value-required' | translate }} + + + {{ 'device-profile.condition-duration-value-range' | translate }} + + + {{ 'device-profile.condition-duration-value-range' | translate }} + + + + + + + {{ timeUnitTranslations.get(timeUnit) | translate }} + + + + {{ 'device-profile.condition-duration-time-unit-required' | translate }} + + +
+
+
+ + + +
+
device-profile.advanced-settings
+
+
+
+ + device-profile.alarm-details + + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss new file mode 100644 index 0000000000..00885ed393 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.scss @@ -0,0 +1,41 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .mat-expansion-panel.advanced-settings { + box-shadow: none; + border: none; + padding: 0; + } +} + +:host ::ng-deep { + .mat-expansion-panel.advanced-settings { + .mat-expansion-panel-body { + padding: 0; + } + } + .mat-form-field.duration-value-field { + .mat-form-field-infix { + width: 120px; + } + } + .mat-form-field.duration-unit-field { + .mat-form-field-infix { + width: 120px; + } + } +} + diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts index 88805de563..74365dc8a8 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, forwardRef, Input, NgZone, OnInit } from '@angular/core'; import { ControlValueAccessor, FormBuilder, @@ -27,11 +27,13 @@ import { } from '@angular/forms'; import { AlarmRule } from '@shared/models/device.models'; import { MatDialog } from '@angular/material/dialog'; +import { TimeUnit, timeUnitTranslationMap } from '../../../../../shared/models/time/time.models'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; @Component({ selector: 'tb-alarm-rule', templateUrl: './alarm-rule.component.html', - styleUrls: [], + styleUrls: ['./alarm-rule.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, @@ -47,9 +49,23 @@ import { MatDialog } from '@angular/material/dialog'; }) export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validator { + timeUnits = Object.keys(TimeUnit); + timeUnitTranslations = timeUnitTranslationMap; + @Input() disabled: boolean; + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + enableDuration = false; + private modelValue: AlarmRule; alarmRuleFormGroup: FormGroup; @@ -69,7 +85,11 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat ngOnInit() { this.alarmRuleFormGroup = this.fb.group({ - condition: [null, Validators.required], + condition: this.fb.group({ + condition: [null, Validators.required], + durationUnit: [null], + durationValue: [null] + }, Validators.required), alarmDetails: [null] }); this.alarmRuleFormGroup.valueChanges.subscribe(() => { @@ -88,24 +108,51 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat writeValue(value: AlarmRule): void { this.modelValue = value; - this.alarmRuleFormGroup.reset(this.modelValue, {emitEvent: false}); + this.enableDuration = value && !!value.condition.durationValue; + this.alarmRuleFormGroup.reset(this.modelValue || undefined, {emitEvent: false}); + this.updateValidators(); } public validate(c: FormControl) { - return (this.alarmRuleFormGroup.valid) ? null : { + return (!this.required && !this.modelValue || this.alarmRuleFormGroup.valid) ? null : { alarmRule: { valid: false, }, }; } + public enableDurationChanged(enableDuration) { + this.enableDuration = enableDuration; + this.updateValidators(true, true); + } + + private updateValidators(resetDuration = false, emitEvent = false) { + if (this.enableDuration) { + this.alarmRuleFormGroup.get('condition').get('durationValue') + .setValidators([Validators.required, Validators.min(1), Validators.max(2147483647)]); + this.alarmRuleFormGroup.get('condition').get('durationUnit') + .setValidators([Validators.required]); + } else { + this.alarmRuleFormGroup.get('condition').get('durationValue') + .setValidators([]); + this.alarmRuleFormGroup.get('condition').get('durationUnit') + .setValidators([]); + if (resetDuration) { + this.alarmRuleFormGroup.get('condition').patchValue({ + durationValue: null, + durationUnit: null + }); + } + } + this.alarmRuleFormGroup.get('condition').get('durationValue').updateValueAndValidity({emitEvent}); + this.alarmRuleFormGroup.get('condition').get('durationUnit').updateValueAndValidity({emitEvent}); + } + private updateModel() { - if (this.alarmRuleFormGroup.valid) { - const value = this.alarmRuleFormGroup.value; + const value = this.alarmRuleFormGroup.value; + if (this.modelValue) { this.modelValue = {...this.modelValue, ...value}; this.propagateChange(this.modelValue); - } else { - this.propagateChange(null); } } } diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html index 9551371af2..9a5835d556 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html @@ -17,38 +17,42 @@ -->
- - - - - {{ alarmSeverityTranslationMap.get(alarmSeverityEnum[alarmSeverity]) | translate }} - - - - {{ 'device-profile.alarm-severity-required' | translate }} - - - - + last as isLast;" fxLayout="row" fxLayoutAlign="start center" + fxLayoutGap="8px" style="padding-bottom: 8px;" [formGroup]="createAlarmRuleControl"> +
+ + alarm.severity + + + {{ alarmSeverityTranslationMap.get(alarmSeverityEnum[alarmSeverity]) | translate }} + + + + {{ 'device-profile.alarm-severity-required' | translate }} + + + + +
-
-
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.scss index bb3718c2da..5fb42cf1c2 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.scss @@ -13,6 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host { +:host { + .create-alarm-rule { + border: 1px groove rgba(0, 0, 0, .25); + border-radius: 4px; + padding: 8px; + .mat-form-field.severity { + border-right: 1px groove rgba(0, 0, 0, 0.25); + padding-right: 8px; + } + } +} + +:host ::ng-deep { + .mat-form-field.severity { + .mat-form-field-infix { + width: 160px; + } + } } diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.ts index efec9d639c..e7e8a8b94e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.ts @@ -150,15 +150,11 @@ export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit, } private updateModel() { - if (this.createAlarmRulesFormGroup.valid) { - const value: {severity: string, alarmRule: AlarmRule}[] = this.createAlarmRulesFormGroup.get('createAlarmRules').value; - const createAlarmRules: {[severity: string]: AlarmRule} = {}; - value.forEach(v => { - createAlarmRules[v.severity] = v.alarmRule; - }); - this.propagateChange(createAlarmRules); - } else { - this.propagateChange(null); - } + const value: {severity: string, alarmRule: AlarmRule}[] = this.createAlarmRulesFormGroup.get('createAlarmRules').value; + const createAlarmRules: {[severity: string]: AlarmRule} = {}; + value.forEach(v => { + createAlarmRules[v.severity] = v.alarmRule; + }); + this.propagateChange(createAlarmRules); } } diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm-dialog.component.html deleted file mode 100644 index 4bc2fe878e..0000000000 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm-dialog.component.html +++ /dev/null @@ -1,65 +0,0 @@ - -
- -

{{ (isReadOnly ? 'device-profile.alarm-rule-details' : (isAdd ? 'device-profile.add-alarm-rule' : 'device-profile.edit-alarm-rule')) | translate }}

- - -
- - -
-
-
- - device-profile.alarm-type - - - {{ 'device-profile.alarm-type-required' | translate }} - - - -
- device-profile.create-alarm-rules -
-
- device-profile.clear-alarm-rule -
-
-
-
- - - -
-
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html index 650982ffdf..c9e11a9a45 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html @@ -15,33 +15,80 @@ limitations under the License. --> -
- - - - {{'device-profile.alarm-type' | translate}} - - - {{ 'device-profile.alarm-type-required' | translate }} - - - - + + +
+ +
+ {{ alarmFormGroup.get('alarmType').value }} +
+
+ + {{'device-profile.alarm-type' | translate}} + + + {{ 'device-profile.alarm-type-required' | translate }} + + + + + +
+
-
device-profile.create-alarm-rules
- - +
device-profile.create-alarm-rules
+ -
device-profile.clear-alarm-rule
- +
device-profile.clear-alarm-rule
+
+
+ + +
+ +
+
+ +
-
+ + + +
+
device-profile.advanced-settings
+
+
+
+ + {{ 'device-profile.propagate-alarm' | translate }} + +
TODO: Propagate relation types
+
+ diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.scss index 6e661f9cfc..9180f507da 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.scss @@ -13,35 +13,42 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import '../scss/constants'; :host { display: block; - .tb-device-profile-alarm { - &.mat-padding { - padding: 8px; - @media #{$mat-gt-sm} { - padding: 16px; - } - } - } - a.mat-icon-button { - &:hover, &:focus { - border-bottom: none; - } - } - .fields-group { - padding: 8px; - margin: 10px 0; + .clear-alarm-rule { border: 1px groove rgba(0, 0, 0, .25); border-radius: 4px; + padding: 8px; + } + .mat-expansion-panel { + box-shadow: none; + &.device-profile-alarm { + border: 1px groove rgba(0, 0, 0, .25); + .mat-expansion-panel-header { + padding: 0 24px 0 8px; + &.mat-expanded { + height: 80px; + } + } + } + &.advanced-settings { + border: none; + padding: 0; + } + } +} - legend { - padding-left: 8px; - padding-right: 8px; - margin-bottom: -30px; - .mat-form-field { - margin-bottom: 21px; +:host ::ng-deep { + .mat-expansion-panel { + &.device-profile-alarm { + .mat-expansion-panel-body { + padding: 0 8px; + } + } + &.advanced-settings { + .mat-expansion-panel-body { + padding: 0; } } } diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts index 445ab19a90..9ab4e39e27 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts @@ -19,17 +19,14 @@ import { ControlValueAccessor, FormBuilder, FormControl, - FormGroup, NG_VALIDATORS, - NG_VALUE_ACCESSOR, Validator, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + Validator, Validators } from '@angular/forms'; -import { DeviceProfileAlarm } from '@shared/models/device.models'; -import { deepClone } from '@core/utils'; +import { AlarmRule, DeviceProfileAlarm } from '@shared/models/device.models'; import { MatDialog } from '@angular/material/dialog'; -import { - DeviceProfileAlarmDialogComponent, - DeviceProfileAlarmDialogData -} from './device-profile-alarm-dialog.component'; @Component({ selector: 'tb-device-profile-alarm', @@ -56,6 +53,8 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit @Output() removeAlarm = new EventEmitter(); + expanded = false; + private modelValue: DeviceProfileAlarm; alarmFormGroup: FormGroup; @@ -98,31 +97,24 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit writeValue(value: DeviceProfileAlarm): void { this.modelValue = value; - this.alarmFormGroup.reset(this.modelValue, {emitEvent: false}); + if (!this.modelValue.alarmType) { + this.expanded = true; + } + this.alarmFormGroup.reset(this.modelValue || undefined, {emitEvent: false}); } -/* openAlarm($event: Event) { - if ($event) { - $event.stopPropagation(); - } - this.dialog.open(DeviceProfileAlarmDialogComponent, { - disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - isAdd: false, - alarm: this.disabled ? this.modelValue : deepClone(this.modelValue), - isReadOnly: this.disabled + public addClearAlarmRule() { + const clearAlarmRule: AlarmRule = { + condition: { + condition: [] } - }).afterClosed().subscribe( - (deviceProfileAlarm) => { - if (deviceProfileAlarm) { - this.modelValue = deviceProfileAlarm; - this.updateModel(); - } - } - ); - } */ + }; + this.alarmFormGroup.patchValue({clearRule: clearAlarmRule}); + } + + public removeClearAlarmRule() { + this.alarmFormGroup.patchValue({clearRule: null}); + } public validate(c: FormControl) { return (this.alarmFormGroup.valid) ? null : { @@ -133,12 +125,8 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit } private updateModel() { - if (this.alarmFormGroup.valid) { - const value = this.alarmFormGroup.value; - this.modelValue = {...this.modelValue, ...value}; - this.propagateChange(this.modelValue); - } else { - this.propagateChange(null); - } + const value = this.alarmFormGroup.value; + this.modelValue = {...this.modelValue, ...value}; + this.propagateChange(this.modelValue); } } diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.html index 6d5d0ec865..40c5de4b68 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.html @@ -17,8 +17,9 @@ -->
-
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.scss index 22d3556cac..a7ac1d1ad6 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.scss @@ -17,7 +17,6 @@ :host { .tb-device-profile-alarms { - max-height: 400px; overflow-y: auto; &.mat-padding { padding: 8px; diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.ts index 1118bcc163..91db1cfdf7 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.ts @@ -19,9 +19,12 @@ import { AbstractControl, ControlValueAccessor, FormArray, - FormBuilder, FormControl, - FormGroup, NG_VALIDATORS, - NG_VALUE_ACCESSOR, Validator, + FormBuilder, + FormControl, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + Validator, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; @@ -30,10 +33,6 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { DeviceProfileAlarm } from '@shared/models/device.models'; import { guid } from '@core/utils'; import { Subscription } from 'rxjs'; -import { - DeviceProfileAlarmDialogComponent, - DeviceProfileAlarmDialogData -} from './device-profile-alarm-dialog.component'; import { MatDialog } from '@angular/material/dialog'; @Component({ @@ -125,6 +124,14 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni }); } + public trackByAlarm(index: number, alarmControl: AbstractControl): string { + if (alarmControl) { + return alarmControl.value.id; + } else { + return null; + } + } + public removeAlarm(index: number) { (this.deviceProfileAlarmsFormGroup.get('alarms') as FormArray).removeAt(index); } @@ -144,22 +151,6 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni const alarmsArray = this.deviceProfileAlarmsFormGroup.get('alarms') as FormArray; alarmsArray.push(this.fb.control(alarm, [Validators.required])); this.deviceProfileAlarmsFormGroup.updateValueAndValidity(); - -/* this.dialog.open(DeviceProfileAlarmDialogComponent, { - disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - isAdd: true, - alarm, - isReadOnly: false - } - }).afterClosed().subscribe( - (deviceProfileAlarm) => { - if (deviceProfileAlarm) { - } - } - ); */ } public validate(c: FormControl) { @@ -171,11 +162,11 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni } private updateModel() { - if (this.deviceProfileAlarmsFormGroup.valid) { +// if (this.deviceProfileAlarmsFormGroup.valid) { const alarms: Array = this.deviceProfileAlarmsFormGroup.get('alarms').value; this.propagateChange(alarms); - } else { + /* } else { this.propagateChange(null); - } + } */ } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.html index c55c973678..ad7282e7ff 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+

{{ (isAdd ? 'device-profile.add' : 'device-profile.edit' ) | translate }}

diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts index 105960754a..f1f3b01963 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts @@ -52,7 +52,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve('createdTime', 'common.created-time', this.datePipe, '150px'), diff --git a/ui-ngx/src/app/shared/models/query/query.models.ts b/ui-ngx/src/app/shared/models/query/query.models.ts index 1ae528abeb..e6019fe023 100644 --- a/ui-ngx/src/app/shared/models/query/query.models.ts +++ b/ui-ngx/src/app/shared/models/query/query.models.ts @@ -148,12 +148,16 @@ export function createDefaultFilterPredicateInfo(valueType: EntityKeyValueType, const predicate = createDefaultFilterPredicate(valueType, complex); return { keyFilterPredicate: predicate, - userInfo: { - editable: true, - label: '', - autogeneratedLabel: true, - order: 0 - } + userInfo: createDefaultFilterPredicateUserInfo() + }; +} + +export function createDefaultFilterPredicateUserInfo(): KeyFilterPredicateUserInfo { + return { + editable: true, + label: '', + autogeneratedLabel: true, + order: 0 }; } @@ -334,6 +338,7 @@ export interface KeyFilterPredicateInfo { export interface KeyFilter { key: EntityKey; + valueType: EntityKeyValueType; predicate: KeyFilterPredicate; } @@ -353,6 +358,45 @@ export interface FiltersInfo { datasourceFilters: {[datasourceIndex: number]: FilterInfo}; } +export function keyFilterInfosToKeyFilters(keyFilterInfos: Array): Array { + const keyFilters: Array = []; + for (const keyFilterInfo of keyFilterInfos) { + const key = keyFilterInfo.key; + for (const predicate of keyFilterInfo.predicates) { + const keyFilter: KeyFilter = { + key, + valueType: keyFilterInfo.valueType, + predicate: keyFilterPredicateInfoToKeyFilterPredicate(predicate) + }; + keyFilters.push(keyFilter); + } + } + return keyFilters; +} + +export function keyFiltersToKeyFilterInfos(keyFilters: Array): Array { + const keyFilterInfos: Array = []; + const keyFilterInfoMap: {[infoKey: string]: KeyFilterInfo} = {}; + for (const keyFilter of keyFilters) { + const key = keyFilter.key; + const infoKey = key.key + key.type + keyFilter.valueType; + let keyFilterInfo = keyFilterInfoMap[infoKey]; + if (!keyFilterInfo) { + keyFilterInfo = { + key, + valueType: keyFilter.valueType, + predicates: [] + }; + keyFilterInfoMap[infoKey] = keyFilterInfo; + keyFilterInfos.push(keyFilterInfo); + } + if (keyFilter.predicate) { + keyFilterInfo.predicates.push(keyFilterPredicateToKeyFilterPredicateInfo(keyFilter.predicate)); + } + } + return keyFilterInfos; +} + export function filterInfoToKeyFilters(filter: FilterInfo): Array { const keyFilterInfos = filter.keyFilters; const keyFilters: Array = []; @@ -361,6 +405,7 @@ export function filterInfoToKeyFilters(filter: FilterInfo): Array { for (const predicate of keyFilterInfo.predicates) { const keyFilter: KeyFilter = { key, + valueType: keyFilterInfo.valueType, predicate: keyFilterPredicateInfoToKeyFilterPredicate(predicate) }; keyFilters.push(keyFilter); @@ -383,6 +428,26 @@ export function keyFilterPredicateInfoToKeyFilterPredicate(keyFilterPredicateInf return keyFilterPredicate; } +export function keyFilterPredicateToKeyFilterPredicateInfo(keyFilterPredicate: KeyFilterPredicate): KeyFilterPredicateInfo { + const keyFilterPredicateInfo: KeyFilterPredicateInfo = { + keyFilterPredicate: null, + userInfo: null + }; + if (keyFilterPredicate.type === FilterPredicateType.COMPLEX) { + const complexPredicate = keyFilterPredicate as ComplexFilterPredicate; + const predicateInfos = complexPredicate.predicates.map( + predicate => keyFilterPredicateToKeyFilterPredicateInfo(predicate)); + keyFilterPredicateInfo.keyFilterPredicate = { + predicates: predicateInfos, + operation: complexPredicate.operation, + type: FilterPredicateType.COMPLEX + } as ComplexFilterPredicateInfo; + } else { + keyFilterPredicateInfo.keyFilterPredicate = keyFilterPredicate; + } + return keyFilterPredicateInfo; +} + export function isFilterEditable(filter: FilterInfo): boolean { if (filter.editable) { return filter.keyFilters.some(value => isKeyFilterInfoEditable(value)); diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 9a41d1bf2a..64f5f849e2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -815,8 +815,21 @@ "create-alarm-rules": "Create alarm rules", "clear-alarm-rule": "Clear alarm rule", "add-create-alarm-rule": "Add create alarm rule", + "add-clear-alarm-rule": "Add clear alarm rule", "select-alarm-severity": "Select alarm severity", - "alarm-severity-required": "Alarm severity is required." + "alarm-severity-required": "Alarm severity is required.", + "condition-duration": "Condition duration", + "condition-duration-value": "Duration value", + "condition-duration-time-unit": "Time unit", + "condition-duration-value-range": "Duration value should be in a range from 1 to 2147483647.", + "condition-duration-value-required": "Duration value is required.", + "condition-duration-time-unit-required": "Time unit is required.", + "advanced-settings": "Advanced settings", + "propagate-alarm": "Propagate alarm", + "alarm-details": "Alarm details", + "alarm-rule-condition": "Alarm rule condition", + "enter-alarm-rule-condition-prompt": "Please add alarm rule condition", + "edit-alarm-rule-condition": "Edit alarm rule condition" }, "dialog": { "close": "Close dialog" @@ -1286,6 +1299,7 @@ "complex-filter": "Complex filter", "edit-complex-filter": "Edit complex filter", "edit-filter-user-params": "Edit filter predicate user parameters", + "filter-user-params": "Filter predicate user parameters", "user-parameters": "User parameters", "display-label": "Label to display", "autogenerated-label": "Auto generate label", diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index 1721196b2b..b01e7cdaa3 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -563,7 +563,7 @@ mat-label { } } - mat-toolbar.mat-table-toolbar:not(.mat-primary), .mat-cell { + mat-toolbar.mat-table-toolbar:not(.mat-primary), .mat-cell, .mat-expansion-panel-header { button.mat-icon-button { mat-icon { color: rgba(0, 0, 0, .54);