diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html index ed936cd60e..2c3767c4c8 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html @@ -59,6 +59,7 @@ @if (!data.entityId) { @@ -134,6 +135,7 @@ placeholder="{{ 'alarm-rule.alarm-rule-relation-types-list' | translate }}" hint="{{ 'alarm-rule.alarm-rule-relation-types-list-hint' | translate }}" [predefinedValues]="predefinedTypeValues" + allowUserValue formControlName="propagateRelationTypes"> } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts index 854f7fb1a0..f0d3140ed0 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, DestroyRef, Inject, ViewEncapsulation } from '@angular/core'; +import { Component, DestroyRef, Inject, ViewChild, ViewEncapsulation } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -43,6 +43,10 @@ import { RelationTypes } from "@shared/models/relation.models"; import { StringItemsOption } from "@shared/components/string-items-list.component"; import { BaseData } from "@shared/models/base-data"; import { CalculatedFieldFormService } from '@core/services/calculated-field-form.service'; +import { EntitySelectComponent } from '@shared/components/entity/entity-select.component'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { AssetInfo } from '@shared/models/asset.models'; +import { DeviceInfo } from '@shared/models/device.models'; export interface AlarmRuleDialogData { value?: CalculatedField; @@ -64,6 +68,8 @@ export interface AlarmRuleDialogData { }) export class AlarmRuleDialogComponent extends DialogComponent { + @ViewChild('entitySelect') entitySelect!: EntitySelectComponent; + fieldFormGroup: FormGroup ; additionalDebugActionConfig = this.data.value?.id ? { @@ -80,6 +86,7 @@ export class AlarmRuleDialogComponent extends DialogComponent): void { this.entityName = entity.name; + if (this.isAssignedToCustomer(entity as AssetInfo | DeviceInfo)) { + this.ownerId = (entity as AssetInfo | DeviceInfo).customerId; + } + } + + private isAssignedToCustomer(entity: AssetInfo | DeviceInfo): boolean { + return entity && entity.customerId && entity.customerId.id !== NULL_UUID; } } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html index 64f4ab652f..32107f6eda 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html @@ -68,7 +68,7 @@ required entityTypeLabel="{{ 'entity.entity-type' | translate }}" [filterAllowedEntityTypes]="false" - [allowedEntityTypes]="calculatedFieldsEntityTypeList" + [allowedEntityTypes]="alarmRuleEntityTypeList" (entityChanged)="changeEntity($event)"> @@ -137,6 +137,7 @@ placeholder="{{ 'alarm-rule.alarm-rule-relation-types-list' | translate }}" hint="{{ 'alarm-rule.alarm-rule-relation-types-list-hint' | translate }}" [predefinedValues]="predefinedTypeValues" + allowUserValue formControlName="propagateRelationTypes"> } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts index fb92f6c4fd..0092bf2132 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts @@ -25,7 +25,6 @@ import { CalculatedFieldArgument, CalculatedFieldConfiguration, CalculatedFieldInfo, - calculatedFieldsEntityTypeList, CalculatedFieldType } from '@shared/models/calculated-field.models'; import { EntityId } from '@shared/models/id/entity-id'; @@ -39,8 +38,17 @@ import { import { TenantId } from '@shared/models/id/tenant-id'; import { StringItemsOption } from '@shared/components/string-items-list.component'; import { RelationTypes } from '@shared/models/relation.models'; -import { AlarmRule, AlarmRuleConditionType, AlarmRuleExpressionType } from '@shared/models/alarm-rule.models'; +import { + AlarmRule, + AlarmRuleConditionType, + alarmRuleEntityTypeList, + AlarmRuleExpressionType +} from '@shared/models/alarm-rule.models'; import { CalculatedFieldFormService } from '@core/services/calculated-field-form.service'; +import { AssetInfo } from '@shared/models/asset.models'; +import { DeviceInfo } from '@shared/models/device.models'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { EntityService } from '@core/http/entity.service'; @Component({ selector: 'tb-alarm-rules', @@ -55,10 +63,10 @@ export class AlarmRulesComponent extends EntityComponent { - this.entityForm.patchValue({ configuration, debugSettings, entityId, ...value }, {emitEvent: false}); - }); + this.entityForm.patchValue({ configuration, debugSettings, entityId, ...value }, {emitEvent: false}); if (!entityId) { this.entityForm.get('configuration').disable({emitEvent: false}); } @@ -134,6 +141,7 @@ export class AlarmRulesComponent extends EntityComponent { + if (this.isAssignedToCustomer(entity)) { + this.ownerId = entity.customerId; + } + } + ); + } + } + + private isAssignedToCustomer(entity: AssetInfo | DeviceInfo): boolean { + return entity && entity.customerId && entity.customerId.id !== NULL_UUID; + } + } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts index 20ad4a4bfb..6f691ecd21 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts @@ -184,11 +184,13 @@ export class CfAlarmRuleConditionDialogComponent extends DialogComponent { this.updateStaticValueValidator(AlarmRuleConditionType.DURATION, mode); + this.updateSpecText(this.conditionFormGroup.get('type').value); }); this.repeatingDynamicModeControl.valueChanges.pipe( takeUntilDestroyed() ).subscribe((mode) => { this.updateStaticValueValidator(AlarmRuleConditionType.REPEATING, mode); + this.updateSpecText(this.conditionFormGroup.get('type').value); }); this.updateValidators(this.conditionFormGroup.get('type').value ?? AlarmRuleConditionType.SIMPLE); @@ -289,20 +291,20 @@ export class CfAlarmRuleConditionDialogComponent extends DialogComponent
{{ 'alarm-rule.condition' | translate }}
-
{{ 'alarm-rule.schedule-title' | translate }}
-
diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.ts index 7a2ad22fdd..0fcb6d0135 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.ts @@ -137,23 +137,27 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali writeValue(value: AlarmRuleCondition): void { this.modelValue = value; this.updateConditionInfo(); - if (value && !this.disabled) { - if (this.validate(this.alarmRuleConditionFormGroup)) { - this.onValidatorChange(); - } - } + this.recalculateArgumentValidity(); } ngOnChanges(changes: SimpleChanges) { if (changes.arguments) { - if (changes.arguments && !changes.arguments.firstChange && this.modelValue && !this.disabled) { - if (this.validate(this.alarmRuleConditionFormGroup)) { - this.onValidatorChange(); - } + if (changes.arguments && !changes.arguments.firstChange) { + this.recalculateArgumentValidity(); } } } + private recalculateArgumentValidity(): void { + if (!this.modelValue || !this.arguments) { + this.filtersArgumentsValid = true; + this.schedulerArgumentsValid = true; + return; + } + this.filtersArgumentsValid = this.areFilterAndPredicateArgumentsValid(this.modelValue, this.arguments); + this.schedulerArgumentsValid = this.isScheduleArgumentValid(this.modelValue, Object.keys(this.arguments)); + } + private isScheduleArgumentValid(obj: any, validArguments: string[]): boolean { const arg = obj?.schedule?.dynamicValueArgument; return !arg || validArguments.includes(arg); @@ -177,21 +181,14 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali return true; } - public conditionSet() { - return this.modelValue && (this.modelValue.expression?.expression || this.modelValue.expression?.filters); + public conditionSet(): boolean { + return !!this.modelValue && !!(this.modelValue.expression?.expression || this.modelValue.expression?.filters); } public validate(control: AbstractControl): ValidationErrors | null { - this.filtersArgumentsValid = this.areFilterAndPredicateArgumentsValid(this.modelValue, this.arguments); - this.schedulerArgumentsValid = this.isScheduleArgumentValid(this.modelValue, Object.keys(this.arguments)); - this.onValidatorChange = () => { - control.updateValueAndValidity({ emitEvent: !this.disabled }); - }; - return this.conditionSet() && this.filtersArgumentsValid && this.schedulerArgumentsValid ? null : { - alarmRuleCondition: { - valid: false, - } - }; + const hasCondition = this.conditionSet(); + const argsValid = this.filtersArgumentsValid && this.schedulerArgumentsValid; + return (hasCondition && argsValid) ? null : { alarmRuleCondition: { valid: false } }; } public openFilterDialog($event: Event) { @@ -212,7 +209,6 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali if (result) { this.modelValue = {...this.modelValue, ...result}; this.updateModel(); - this.cd.detectChanges(); } }); } @@ -278,10 +274,10 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali private updateModel() { this.updateConditionInfo(); + this.recalculateArgumentValidity(); this.propagateChange(this.modelValue); - if (this.modelValue) { - this.onValidatorChange(); - } + this.onValidatorChange(); + this.cd.detectChanges(); } public openScheduleDialog($event: Event) { @@ -301,7 +297,6 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali if (result) { this.modelValue.schedule = result; this.updateModel(); - this.cd.detectChanges(); } }); } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts index 65595fe9b8..fcf20b5421 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts @@ -39,6 +39,10 @@ import { } from '@home/components/calculated-fields/calculated-fields-table-config'; import { TenantId } from '@shared/models/id/tenant-id'; import { CalculatedFieldFormService } from '@core/services/calculated-field-form.service'; +import { AssetInfo } from '@shared/models/asset.models'; +import { DeviceInfo } from '@shared/models/device.models'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { EntityService } from '@core/http/entity.service'; @Component({ selector: 'tb-calculated-field', @@ -55,7 +59,7 @@ export class CalculatedFieldComponent extends EntityComponent { + if (this.isAssignedToCustomer(entity)) { + this.ownerId = entity.customerId; + } + } + ); + } + } + + private isAssignedToCustomer(entity: AssetInfo | DeviceInfo): boolean { + return entity && entity.customerId && entity.customerId.id !== NULL_UUID; + } } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts index e09949d2c6..49cc6b75ca 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts @@ -333,7 +333,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig { - if (calculatedField.type === CalculatedFieldType.ALARM) { + if (calculatedField.type === CalculatedFieldType.ALARM || !CalculatedFieldType[calculatedField.type]) { this.store.dispatch(new ActionNotificationShow({ message: this.translate.instant('calculated-fields.hint.import-invalid-calculated-field-type'), type: 'error', diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html index 1c4a2995b5..4aba05c99e 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html @@ -94,7 +94,7 @@ [class.tb-form-panel-disabled]="disabledConfiguration" [entityId]="entityId" [entityName]="entityName" - [ownerId]="data.ownerId" + [ownerId]="ownerId" [tenantId]="data.tenantId" > } @@ -104,7 +104,7 @@ [entityId]="entityId" [entityName]="entityName" [tenantId]="data.tenantId" - [ownerId]="data.ownerId" + [ownerId]="ownerId" [testScript]="onTestScript.bind(this)" > } @@ -130,7 +130,7 @@ [class.tb-form-panel-disabled]="disabledConfiguration" [entityId]="entityId" [entityName]="entityName" - [ownerId]="data.ownerId" + [ownerId]="ownerId" [tenantId]="data.tenantId" [isScript]="fieldFormGroup.get('type').value === CalculatedFieldType.SCRIPT" [testScript]="onTestScript.bind(this)" diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts index ee38fb2a2d..7f1c9e376a 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts @@ -39,6 +39,9 @@ import { deepTrim } from '@core/utils'; import { BaseData } from '@shared/models/base-data'; import { CalculatedFieldFormService } from '@core/services/calculated-field-form.service'; import { FormGroup } from '@angular/forms'; +import { AssetInfo } from '@shared/models/asset.models'; +import { DeviceInfo } from '@shared/models/device.models'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; export interface CalculatedFieldDialogData { value?: CalculatedField; @@ -69,6 +72,7 @@ export class CalculatedFieldDialogComponent extends DialogComponent): void { this.entityName = entity.name; + if (this.isAssignedToCustomer(entity as AssetInfo | DeviceInfo)) { + this.ownerId = (entity as AssetInfo | DeviceInfo).customerId; + } } get entityId(): EntityId { @@ -163,4 +171,8 @@ export class CalculatedFieldDialogComponent extends DialogComponent ): void { this.entityChanged.emit(entity); } + + entityAutocompleteMarkAsTouched() { + this.entityAutocompleteComponent.markAsTouched(); + } } diff --git a/ui-ngx/src/app/shared/components/string-items-list.component.html b/ui-ngx/src/app/shared/components/string-items-list.component.html index 0469be2589..bcd0076269 100644 --- a/ui-ngx/src/app/shared/components/string-items-list.component.html +++ b/ui-ngx/src/app/shared/components/string-items-list.component.html @@ -52,7 +52,9 @@ @for (value of filteredValues | async; track value.value) { } @empty { - {{ 'common.not-found' | translate }} + @if (!allowUserValue) { + {{ 'common.not-found' | translate }} + } } diff --git a/ui-ngx/src/app/shared/components/string-items-list.component.ts b/ui-ngx/src/app/shared/components/string-items-list.component.ts index 1c210fdf1f..ab9f1f5831 100644 --- a/ui-ngx/src/app/shared/components/string-items-list.component.ts +++ b/ui-ngx/src/app/shared/components/string-items-list.component.ts @@ -210,8 +210,8 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit { addOnBlur(event: FocusEvent) { const target: HTMLElement = event.relatedTarget as HTMLElement; - if (target && target.tagName !== 'MAT-OPTION') { - this.addItem(this.stringItemInput.nativeElement.value ?? '') + if ((target && target.tagName !== 'MAT-OPTION') || this.allowUserValue) { + this.addItem(this.stringItemInput.nativeElement.value ?? ''); } this.onTouched(); } diff --git a/ui-ngx/src/app/shared/models/calculated-field.models.ts b/ui-ngx/src/app/shared/models/calculated-field.models.ts index b0c2c1a899..db3da4235e 100644 --- a/ui-ngx/src/app/shared/models/calculated-field.models.ts +++ b/ui-ngx/src/app/shared/models/calculated-field.models.ts @@ -525,7 +525,7 @@ export const ArgumentEntityTypeParamsMap =new Map { - switch (entityId.entityType) { + switch (entityId?.entityType) { case EntityType.ASSET_PROFILE: return { assetTypes: [entityName], 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 e6ce6ccdc0..eaad687821 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1434,7 +1434,7 @@ "condition-during": "During {{during}}", "condition-during-dynamic": "During \"{{ attribute }}\"", "condition-repeat-times": "Repeats { count, plural, =1 {1 time} other {# times} }", - "condition-repeat-times-dynamic": "Repeats \"{{ attribute }}\"", + "condition-repeat-times-dynamic": "Repeats \"{{ attribute }}\" times", "filter-preview": "Filter preview", "condition-settings": "Condition settings", "static": "Static",