Browse Source

Merge remote-tracking branch 'upstream/rc' into AD/imp/cf/arguments-table-preview

pull/14722/head
ArtemDzhereleiko 9 months ago
parent
commit
f78d4abc87
  1. 4
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html
  2. 17
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts
  3. 3
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html
  4. 40
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts
  5. 12
      ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts
  6. 12
      ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.html
  7. 47
      ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.ts
  8. 26
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts
  9. 2
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts
  10. 6
      ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html
  11. 12
      ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts
  12. 1
      ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-panel.component.ts
  13. 14
      ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/geofencing-configuration.component.ts
  14. 1
      ui-ngx/src/app/shared/components/entity/entity-select.component.html
  15. 9
      ui-ngx/src/app/shared/components/entity/entity-select.component.ts
  16. 4
      ui-ngx/src/app/shared/components/string-items-list.component.html
  17. 4
      ui-ngx/src/app/shared/components/string-items-list.component.ts
  18. 2
      ui-ngx/src/app/shared/models/calculated-field.models.ts
  19. 2
      ui-ngx/src/assets/locale/locale.constant-en_US.json

4
ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.html

@ -59,6 +59,7 @@
</div> </div>
@if (!data.entityId) { @if (!data.entityId) {
<tb-entity-select formControlName="entityId" <tb-entity-select formControlName="entityId"
#entitySelect
appearance="outline" appearance="outline"
required required
entityTypeLabel="{{ 'entity.entity-type' | translate }}" entityTypeLabel="{{ 'entity.entity-type' | translate }}"
@ -75,7 +76,7 @@
<tb-calculated-field-arguments-table formControlName="arguments" <tb-calculated-field-arguments-table formControlName="arguments"
[entityId]="data.entityId || fieldFormGroup.get('entityId').value" [entityId]="data.entityId || fieldFormGroup.get('entityId').value"
[tenantId]="data.tenantId" [tenantId]="data.tenantId"
[ownerId]="data.ownerId" [ownerId]="ownerId"
[watchKeyChange]="true" [watchKeyChange]="true"
[entityName]="entityName"/> [entityName]="entityName"/>
</div> </div>
@ -134,6 +135,7 @@
placeholder="{{ 'alarm-rule.alarm-rule-relation-types-list' | translate }}" placeholder="{{ 'alarm-rule.alarm-rule-relation-types-list' | translate }}"
hint="{{ 'alarm-rule.alarm-rule-relation-types-list-hint' | translate }}" hint="{{ 'alarm-rule.alarm-rule-relation-types-list-hint' | translate }}"
[predefinedValues]="predefinedTypeValues" [predefinedValues]="predefinedTypeValues"
allowUserValue
formControlName="propagateRelationTypes"> formControlName="propagateRelationTypes">
</tb-string-items-list> </tb-string-items-list>
} }

17
ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rule-dialog.component.ts

@ -14,7 +14,7 @@
/// limitations under the License. /// 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 { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; 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 { StringItemsOption } from "@shared/components/string-items-list.component";
import { BaseData } from "@shared/models/base-data"; import { BaseData } from "@shared/models/base-data";
import { CalculatedFieldFormService } from '@core/services/calculated-field-form.service'; 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 { export interface AlarmRuleDialogData {
value?: CalculatedField; value?: CalculatedField;
@ -64,6 +68,8 @@ export interface AlarmRuleDialogData {
}) })
export class AlarmRuleDialogComponent extends DialogComponent<AlarmRuleDialogComponent, CalculatedField> { export class AlarmRuleDialogComponent extends DialogComponent<AlarmRuleDialogComponent, CalculatedField> {
@ViewChild('entitySelect') entitySelect!: EntitySelectComponent;
fieldFormGroup: FormGroup ; fieldFormGroup: FormGroup ;
additionalDebugActionConfig = this.data.value?.id ? { additionalDebugActionConfig = this.data.value?.id ? {
@ -80,6 +86,7 @@ export class AlarmRuleDialogComponent extends DialogComponent<AlarmRuleDialogCom
separatorKeysCodes = [ENTER, COMMA, SEMICOLON]; separatorKeysCodes = [ENTER, COMMA, SEMICOLON];
entityName = this.data.entityName; entityName = this.data.entityName;
ownerId = this.data.ownerId;
disabledClearRuleButton = false; disabledClearRuleButton = false;
disabledArguments = false; disabledArguments = false;
@ -173,6 +180,7 @@ export class AlarmRuleDialogComponent extends DialogComponent<AlarmRuleDialogCom
}); });
} else { } else {
this.fieldFormGroup.get('name').markAsTouched(); this.fieldFormGroup.get('name').markAsTouched();
this.entitySelect.entityAutocompleteMarkAsTouched();
} }
} }
@ -219,5 +227,12 @@ export class AlarmRuleDialogComponent extends DialogComponent<AlarmRuleDialogCom
changeEntity(entity: BaseData<EntityId>): void { changeEntity(entity: BaseData<EntityId>): void {
this.entityName = entity.name; 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;
} }
} }

3
ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.html

@ -68,7 +68,7 @@
required required
entityTypeLabel="{{ 'entity.entity-type' | translate }}" entityTypeLabel="{{ 'entity.entity-type' | translate }}"
[filterAllowedEntityTypes]="false" [filterAllowedEntityTypes]="false"
[allowedEntityTypes]="calculatedFieldsEntityTypeList" [allowedEntityTypes]="alarmRuleEntityTypeList"
(entityChanged)="changeEntity($event)"> (entityChanged)="changeEntity($event)">
</tb-entity-select> </tb-entity-select>
</div> </div>
@ -137,6 +137,7 @@
placeholder="{{ 'alarm-rule.alarm-rule-relation-types-list' | translate }}" placeholder="{{ 'alarm-rule.alarm-rule-relation-types-list' | translate }}"
hint="{{ 'alarm-rule.alarm-rule-relation-types-list-hint' | translate }}" hint="{{ 'alarm-rule.alarm-rule-relation-types-list-hint' | translate }}"
[predefinedValues]="predefinedTypeValues" [predefinedValues]="predefinedTypeValues"
allowUserValue
formControlName="propagateRelationTypes"> formControlName="propagateRelationTypes">
</tb-string-items-list> </tb-string-items-list>
} }

40
ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules.component.ts

@ -25,7 +25,6 @@ import {
CalculatedFieldArgument, CalculatedFieldArgument,
CalculatedFieldConfiguration, CalculatedFieldConfiguration,
CalculatedFieldInfo, CalculatedFieldInfo,
calculatedFieldsEntityTypeList,
CalculatedFieldType CalculatedFieldType
} from '@shared/models/calculated-field.models'; } from '@shared/models/calculated-field.models';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
@ -39,8 +38,17 @@ import {
import { TenantId } from '@shared/models/id/tenant-id'; import { TenantId } from '@shared/models/id/tenant-id';
import { StringItemsOption } from '@shared/components/string-items-list.component'; import { StringItemsOption } from '@shared/components/string-items-list.component';
import { RelationTypes } from '@shared/models/relation.models'; 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 { 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({ @Component({
selector: 'tb-alarm-rules', selector: 'tb-alarm-rules',
@ -55,10 +63,10 @@ export class AlarmRulesComponent extends EntityComponent<CalculatedFieldsTableEn
@Input() @Input()
entityName: string; entityName: string;
readonly ownerId = new TenantId(getCurrentAuthUser(this.store).tenantId); ownerId = new TenantId(getCurrentAuthUser(this.store).tenantId);
readonly tenantId = getCurrentAuthUser(this.store).tenantId; readonly tenantId = getCurrentAuthUser(this.store).tenantId;
readonly EntityType = EntityType; readonly EntityType = EntityType;
readonly calculatedFieldsEntityTypeList = calculatedFieldsEntityTypeList; readonly alarmRuleEntityTypeList = alarmRuleEntityTypeList;
readonly CalculatedFieldType = CalculatedFieldType; readonly CalculatedFieldType = CalculatedFieldType;
private cfFormService = inject(CalculatedFieldFormService); private cfFormService = inject(CalculatedFieldFormService);
@ -69,7 +77,8 @@ export class AlarmRulesComponent extends EntityComponent<CalculatedFieldsTableEn
@Inject('entity') protected entityValue: CalculatedFieldInfo, @Inject('entity') protected entityValue: CalculatedFieldInfo,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: CalculatedFieldsTableConfig, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: CalculatedFieldsTableConfig,
protected fb: FormBuilder, protected fb: FormBuilder,
protected cd: ChangeDetectorRef) { protected cd: ChangeDetectorRef,
private entityService: EntityService) {
super(store, fb, entityValue, entitiesTableConfigValue, cd); super(store, fb, entityValue, entitiesTableConfigValue, cd);
} }
@ -111,9 +120,7 @@ export class AlarmRulesComponent extends EntityComponent<CalculatedFieldsTableEn
updateForm(entity: CalculatedFieldInfo) { updateForm(entity: CalculatedFieldInfo) {
const { configuration = {} as CalculatedFieldConfiguration, type = CalculatedFieldType.ALARM, debugSettings = { failuresEnabled: true, allEnabled: true }, entityId = this.entityId, ...value } = entity ?? {}; const { configuration = {} as CalculatedFieldConfiguration, type = CalculatedFieldType.ALARM, debugSettings = { failuresEnabled: true, allEnabled: true }, entityId = this.entityId, ...value } = entity ?? {};
setTimeout(() => { this.entityForm.patchValue({ configuration, debugSettings, entityId, ...value }, {emitEvent: false});
this.entityForm.patchValue({ configuration, debugSettings, entityId, ...value }, {emitEvent: false});
});
if (!entityId) { if (!entityId) {
this.entityForm.get('configuration').disable({emitEvent: false}); this.entityForm.get('configuration').disable({emitEvent: false});
} }
@ -134,6 +141,7 @@ export class AlarmRulesComponent extends EntityComponent<CalculatedFieldsTableEn
if (this.isEditValue) { if (this.isEditValue) {
this.entityForm.enable({emitEvent: false}); this.entityForm.enable({emitEvent: false});
this.entityForm.get('entityId').disable({emitEvent: false}); this.entityForm.get('entityId').disable({emitEvent: false});
this.getOwnerId(this.entityId);
} else { } else {
this.entityForm.disable({emitEvent: false}); this.entityForm.disable({emitEvent: false});
} }
@ -172,4 +180,20 @@ export class AlarmRulesComponent extends EntityComponent<CalculatedFieldsTableEn
this.configFormGroup.patchValue({clearRule: clearAlarmRule}); this.configFormGroup.patchValue({clearRule: clearAlarmRule});
} }
getOwnerId(entityId: EntityId) {
if (entityId?.entityType === EntityType.DEVICE || entityId?.entityType === EntityType.ASSET) {
this.entityService.getEntity(entityId.entityType, entityId.id, { ignoreLoading: true, ignoreErrors: true }).subscribe(
(entity: AssetInfo | DeviceInfo) => {
if (this.isAssignedToCustomer(entity)) {
this.ownerId = entity.customerId;
}
}
);
}
}
private isAssignedToCustomer(entity: AssetInfo | DeviceInfo): boolean {
return entity && entity.customerId && entity.customerId.id !== NULL_UUID;
}
} }

12
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<CfAlarm
takeUntilDestroyed() takeUntilDestroyed()
).subscribe((mode) => { ).subscribe((mode) => {
this.updateStaticValueValidator(AlarmRuleConditionType.DURATION, mode); this.updateStaticValueValidator(AlarmRuleConditionType.DURATION, mode);
this.updateSpecText(this.conditionFormGroup.get('type').value);
}); });
this.repeatingDynamicModeControl.valueChanges.pipe( this.repeatingDynamicModeControl.valueChanges.pipe(
takeUntilDestroyed() takeUntilDestroyed()
).subscribe((mode) => { ).subscribe((mode) => {
this.updateStaticValueValidator(AlarmRuleConditionType.REPEATING, mode); this.updateStaticValueValidator(AlarmRuleConditionType.REPEATING, mode);
this.updateSpecText(this.conditionFormGroup.get('type').value);
}); });
this.updateValidators(this.conditionFormGroup.get('type').value ?? AlarmRuleConditionType.SIMPLE); this.updateValidators(this.conditionFormGroup.get('type').value ?? AlarmRuleConditionType.SIMPLE);
@ -289,20 +291,20 @@ export class CfAlarmRuleConditionDialogComponent extends DialogComponent<CfAlarm
duringText = this.translate.instant('timewindow.days', {days: value.staticValue}); duringText = this.translate.instant('timewindow.days', {days: value.staticValue});
break; break;
} }
if (value.dynamicValueArgument) { if (this.durationDynamicModeControl.value) {
this.specText = this.translate.instant('alarm-rule.condition-during-dynamic', { this.specText = this.translate.instant('alarm-rule.condition-during-dynamic', {
attribute: `${value.dynamicValueArgument}` attribute: `${value.dynamicValueArgument ?? ''}`
}) + ' ' + this.translate.instant(this.timeUnitTranslations.get(this.conditionFormGroup.get('unit').value)).toLowerCase(); }) + ' ' + this.translate.instant(this.timeUnitTranslations.get(this.conditionFormGroup.get('unit').value)).toLowerCase();
} else { } else {
this.specText = this.translate.instant('alarm-rule.condition-during', { this.specText = this.translate.instant('alarm-rule.condition-during', {
during: duringText during: duringText.trim()
}); });
} }
break; break;
case AlarmRuleConditionType.REPEATING: case AlarmRuleConditionType.REPEATING:
if (count.dynamicValueArgument) { if (this.repeatingDynamicModeControl.value) {
this.specText = this.translate.instant('alarm-rule.condition-repeat-times-dynamic', { this.specText = this.translate.instant('alarm-rule.condition-repeat-times-dynamic', {
attribute: `${count.dynamicValueArgument}` attribute: `${count.dynamicValueArgument ?? ''}`
}); });
} else { } else {
this.specText = this.translate.instant('alarm-rule.condition-repeat-times', this.specText = this.translate.instant('alarm-rule.condition-repeat-times',

12
ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.html

@ -18,10 +18,9 @@
<div class="tb-alarm-rule-condition flex min-w-0 flex-col gap-4" [formGroup]="alarmRuleConditionFormGroup"> <div class="tb-alarm-rule-condition flex min-w-0 flex-col gap-4" [formGroup]="alarmRuleConditionFormGroup">
<div class="tb-form-row column-xs"> <div class="tb-form-row column-xs">
<div class="min-w-40 xs:min-w-fit">{{ 'alarm-rule.condition' | translate }}</div> <div class="min-w-40 xs:min-w-fit">{{ 'alarm-rule.condition' | translate }}</div>
<button [disabled]="disabled" <button type="button"
type="button"
class="tb-alarm-rule-condition-button" class="tb-alarm-rule-condition-button"
mat-stroked-button [color]="conditionSet() && this.filtersArgumentsValid ? 'primary' : 'warn'" mat-stroked-button [color]="conditionSet() || this.filtersArgumentsValid ? 'primary' : 'warn'"
(click)="openFilterDialog($event)"> (click)="openFilterDialog($event)">
<div class="flex items-center justify-between gap-2"> <div class="flex items-center justify-between gap-2">
<tb-alarm-rule-filter-text [alarmRuleExpression]="alarmRuleConditionFormGroup.get('expression').value" <tb-alarm-rule-filter-text [alarmRuleExpression]="alarmRuleConditionFormGroup.get('expression').value"
@ -35,15 +34,14 @@
</tb-alarm-rule-filter-text> </tb-alarm-rule-filter-text>
<mat-icon [color]="disabled ? '' : (conditionSet() && this.filtersArgumentsValid ? 'primary' : 'warn')" <mat-icon [color]="disabled ? '' : (conditionSet() && this.filtersArgumentsValid ? 'primary' : 'warn')"
class="tb-mat-20 tb-alarm-rule-schedule-edit-icon"> class="tb-mat-20 tb-alarm-rule-schedule-edit-icon">
{{ conditionSet() ? 'edit' : 'add' }} {{ disabled ? 'visibility' : (conditionSet() ? 'edit' : 'add') }}
</mat-icon> </mat-icon>
</div> </div>
</button> </button>
</div> </div>
<div class="tb-form-row column-xs"> <div class="tb-form-row column-xs">
<div class="min-w-40 xs:min-w-fit">{{ 'alarm-rule.schedule-title' | translate }}</div> <div class="min-w-40 xs:min-w-fit">{{ 'alarm-rule.schedule-title' | translate }}</div>
<button [disabled]="disabled" <button [color]="schedulerArgumentsValid ? 'primary' : 'warn'"
[color]="schedulerArgumentsValid ? 'primary' : 'warn'"
type="button" type="button"
class="tb-alarm-rule-condition-button" class="tb-alarm-rule-condition-button"
mat-stroked-button mat-stroked-button
@ -52,7 +50,7 @@
<span class="tb-alarm-rule-condition-label" tbTruncateWithTooltip [innerHTML]="scheduleText"></span> <span class="tb-alarm-rule-condition-label" tbTruncateWithTooltip [innerHTML]="scheduleText"></span>
<mat-icon [color]="disabled ? '' : (schedulerArgumentsValid ? 'primary' : 'warn')" <mat-icon [color]="disabled ? '' : (schedulerArgumentsValid ? 'primary' : 'warn')"
class="tb-mat-20 tb-alarm-rule-schedule-edit-icon"> class="tb-mat-20 tb-alarm-rule-schedule-edit-icon">
edit {{ disabled ? 'visibility' : 'edit' }}
</mat-icon> </mat-icon>
</div> </div>
</button> </button>

47
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 { writeValue(value: AlarmRuleCondition): void {
this.modelValue = value; this.modelValue = value;
this.updateConditionInfo(); this.updateConditionInfo();
if (value && !this.disabled) { this.recalculateArgumentValidity();
if (this.validate(this.alarmRuleConditionFormGroup)) {
this.onValidatorChange();
}
}
} }
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (changes.arguments) { if (changes.arguments) {
if (changes.arguments && !changes.arguments.firstChange && this.modelValue && !this.disabled) { if (changes.arguments && !changes.arguments.firstChange) {
if (this.validate(this.alarmRuleConditionFormGroup)) { this.recalculateArgumentValidity();
this.onValidatorChange();
}
} }
} }
} }
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 { private isScheduleArgumentValid(obj: any, validArguments: string[]): boolean {
const arg = obj?.schedule?.dynamicValueArgument; const arg = obj?.schedule?.dynamicValueArgument;
return !arg || validArguments.includes(arg); return !arg || validArguments.includes(arg);
@ -177,21 +181,14 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali
return true; return true;
} }
public conditionSet() { public conditionSet(): boolean {
return this.modelValue && (this.modelValue.expression?.expression || this.modelValue.expression?.filters); return !!this.modelValue && !!(this.modelValue.expression?.expression || this.modelValue.expression?.filters);
} }
public validate(control: AbstractControl): ValidationErrors | null { public validate(control: AbstractControl): ValidationErrors | null {
this.filtersArgumentsValid = this.areFilterAndPredicateArgumentsValid(this.modelValue, this.arguments); const hasCondition = this.conditionSet();
this.schedulerArgumentsValid = this.isScheduleArgumentValid(this.modelValue, Object.keys(this.arguments)); const argsValid = this.filtersArgumentsValid && this.schedulerArgumentsValid;
this.onValidatorChange = () => { return (hasCondition && argsValid) ? null : { alarmRuleCondition: { valid: false } };
control.updateValueAndValidity({ emitEvent: !this.disabled });
};
return this.conditionSet() && this.filtersArgumentsValid && this.schedulerArgumentsValid ? null : {
alarmRuleCondition: {
valid: false,
}
};
} }
public openFilterDialog($event: Event) { public openFilterDialog($event: Event) {
@ -212,7 +209,6 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali
if (result) { if (result) {
this.modelValue = {...this.modelValue, ...result}; this.modelValue = {...this.modelValue, ...result};
this.updateModel(); this.updateModel();
this.cd.detectChanges();
} }
}); });
} }
@ -278,10 +274,10 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali
private updateModel() { private updateModel() {
this.updateConditionInfo(); this.updateConditionInfo();
this.recalculateArgumentValidity();
this.propagateChange(this.modelValue); this.propagateChange(this.modelValue);
if (this.modelValue) { this.onValidatorChange();
this.onValidatorChange(); this.cd.detectChanges();
}
} }
public openScheduleDialog($event: Event) { public openScheduleDialog($event: Event) {
@ -301,7 +297,6 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali
if (result) { if (result) {
this.modelValue.schedule = result; this.modelValue.schedule = result;
this.updateModel(); this.updateModel();
this.cd.detectChanges();
} }
}); });
} }

26
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'; } from '@home/components/calculated-fields/calculated-fields-table-config';
import { TenantId } from '@shared/models/id/tenant-id'; import { TenantId } from '@shared/models/id/tenant-id';
import { CalculatedFieldFormService } from '@core/services/calculated-field-form.service'; 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({ @Component({
selector: 'tb-calculated-field', selector: 'tb-calculated-field',
@ -55,7 +59,7 @@ export class CalculatedFieldComponent extends EntityComponent<CalculatedFieldsTa
disabledConfiguration = false; disabledConfiguration = false;
readonly ownerId = new TenantId(getCurrentAuthUser(this.store).tenantId); ownerId = new TenantId(getCurrentAuthUser(this.store).tenantId);
readonly tenantId = getCurrentAuthUser(this.store).tenantId; readonly tenantId = getCurrentAuthUser(this.store).tenantId;
readonly EntityType = EntityType; readonly EntityType = EntityType;
readonly calculatedFieldsEntityTypeList = calculatedFieldsEntityTypeList; readonly calculatedFieldsEntityTypeList = calculatedFieldsEntityTypeList;
@ -71,7 +75,8 @@ export class CalculatedFieldComponent extends EntityComponent<CalculatedFieldsTa
@Inject('entity') protected entityValue: CalculatedFieldInfo, @Inject('entity') protected entityValue: CalculatedFieldInfo,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: CalculatedFieldsTableConfig, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: CalculatedFieldsTableConfig,
protected fb: FormBuilder, protected fb: FormBuilder,
protected cd: ChangeDetectorRef) { protected cd: ChangeDetectorRef,
private entityService: EntityService) {
super(store, fb, entityValue, entitiesTableConfigValue, cd); super(store, fb, entityValue, entitiesTableConfigValue, cd);
} }
@ -137,9 +142,26 @@ export class CalculatedFieldComponent extends EntityComponent<CalculatedFieldsTa
if (this.isEditValue) { if (this.isEditValue) {
this.entityForm.enable({emitEvent: false}); this.entityForm.enable({emitEvent: false});
this.entityForm.get('entityId').disable({emitEvent: false}); this.entityForm.get('entityId').disable({emitEvent: false});
this.getOwnerId(this.entityId);
} else { } else {
this.entityForm.disable({emitEvent: false}); this.entityForm.disable({emitEvent: false});
} }
} }
} }
getOwnerId(entityId: EntityId) {
if (entityId?.entityType === EntityType.DEVICE || entityId?.entityType === EntityType.ASSET) {
this.entityService.getEntity(entityId.entityType, entityId.id, { ignoreLoading: true, ignoreErrors: true }).subscribe(
(entity: AssetInfo | DeviceInfo) => {
if (this.isAssignedToCustomer(entity)) {
this.ownerId = entity.customerId;
}
}
);
}
}
private isAssignedToCustomer(entity: AssetInfo | DeviceInfo): boolean {
return entity && entity.customerId && entity.customerId.id !== NULL_UUID;
}
} }

2
ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts

@ -333,7 +333,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
.pipe( .pipe(
filter(Boolean), filter(Boolean),
switchMap(calculatedField => { switchMap(calculatedField => {
if (calculatedField.type === CalculatedFieldType.ALARM) { if (calculatedField.type === CalculatedFieldType.ALARM || !CalculatedFieldType[calculatedField.type]) {
this.store.dispatch(new ActionNotificationShow({ this.store.dispatch(new ActionNotificationShow({
message: this.translate.instant('calculated-fields.hint.import-invalid-calculated-field-type'), message: this.translate.instant('calculated-fields.hint.import-invalid-calculated-field-type'),
type: 'error', type: 'error',

6
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" [class.tb-form-panel-disabled]="disabledConfiguration"
[entityId]="entityId" [entityId]="entityId"
[entityName]="entityName" [entityName]="entityName"
[ownerId]="data.ownerId" [ownerId]="ownerId"
[tenantId]="data.tenantId" [tenantId]="data.tenantId"
></tb-geofencing-configuration> ></tb-geofencing-configuration>
} }
@ -104,7 +104,7 @@
[entityId]="entityId" [entityId]="entityId"
[entityName]="entityName" [entityName]="entityName"
[tenantId]="data.tenantId" [tenantId]="data.tenantId"
[ownerId]="data.ownerId" [ownerId]="ownerId"
[testScript]="onTestScript.bind(this)" [testScript]="onTestScript.bind(this)"
></tb-propagation-configuration> ></tb-propagation-configuration>
} }
@ -130,7 +130,7 @@
[class.tb-form-panel-disabled]="disabledConfiguration" [class.tb-form-panel-disabled]="disabledConfiguration"
[entityId]="entityId" [entityId]="entityId"
[entityName]="entityName" [entityName]="entityName"
[ownerId]="data.ownerId" [ownerId]="ownerId"
[tenantId]="data.tenantId" [tenantId]="data.tenantId"
[isScript]="fieldFormGroup.get('type').value === CalculatedFieldType.SCRIPT" [isScript]="fieldFormGroup.get('type').value === CalculatedFieldType.SCRIPT"
[testScript]="onTestScript.bind(this)" [testScript]="onTestScript.bind(this)"

12
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 { BaseData } from '@shared/models/base-data';
import { CalculatedFieldFormService } from '@core/services/calculated-field-form.service'; import { CalculatedFieldFormService } from '@core/services/calculated-field-form.service';
import { FormGroup } from '@angular/forms'; 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 { export interface CalculatedFieldDialogData {
value?: CalculatedField; value?: CalculatedField;
@ -69,6 +72,7 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
} : null; } : null;
entityName = this.data.entityName; entityName = this.data.entityName;
ownerId = this.data.ownerId;
disabledConfiguration = false; disabledConfiguration = false;
isLoading = false; isLoading = false;
@ -104,6 +108,7 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
this.fieldFormGroup.get('configuration').disable({emitEvent: false}); this.fieldFormGroup.get('configuration').disable({emitEvent: false});
} else { } else {
this.fieldFormGroup.get('configuration').enable({emitEvent: false}); this.fieldFormGroup.get('configuration').enable({emitEvent: false});
this.fieldFormGroup.get('configuration').updateValueAndValidity({emitEvent: false});
} }
}); });
} }
@ -147,6 +152,9 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
changeEntity(entity: BaseData<EntityId>): void { changeEntity(entity: BaseData<EntityId>): void {
this.entityName = entity.name; this.entityName = entity.name;
if (this.isAssignedToCustomer(entity as AssetInfo | DeviceInfo)) {
this.ownerId = (entity as AssetInfo | DeviceInfo).customerId;
}
} }
get entityId(): EntityId { get entityId(): EntityId {
@ -163,4 +171,8 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
this.disabledConfiguration = true; this.disabledConfiguration = true;
} }
} }
private isAssignedToCustomer(entity: AssetInfo | DeviceInfo): boolean {
return entity && entity.customerId && entity.customerId.id !== NULL_UUID;
}
} }

1
ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-panel.component.ts

@ -308,6 +308,7 @@ export class CalculatedFieldGeofencingZoneGroupsPanelComponent implements OnInit
removeKey(index: number) { removeKey(index: number) {
this.levelsFormArray().removeAt(index); this.levelsFormArray().removeAt(index);
this.levelsFormArray().markAsDirty();
} }
addKey() { addKey() {

14
ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/geofencing-configuration.component.ts

@ -14,7 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { Component, forwardRef, Input, OnChanges, SimpleChanges } from '@angular/core';
import { import {
ControlValueAccessor, ControlValueAccessor,
FormBuilder, FormBuilder,
@ -58,7 +58,7 @@ import { EntityId } from '@shared/models/id/entity-id';
} }
], ],
}) })
export class GeofencingConfigurationComponent implements ControlValueAccessor, Validator, OnInit { export class GeofencingConfigurationComponent implements ControlValueAccessor, Validator, OnChanges {
@Input({required: true}) @Input({required: true})
entityId: EntityId; entityId: EntityId;
@ -113,8 +113,14 @@ export class GeofencingConfigurationComponent implements ControlValueAccessor, V
}) })
} }
ngOnInit() { ngOnChanges(changes: SimpleChanges) {
this.currentEntityFilter = getCalculatedFieldCurrentEntityFilter(this.entityName, this.entityId); if (changes.entityName || changes.entityId) {
const entityNameChanges = changes.entityName;
const entityIdChanges = changes.entityId;
if ((entityNameChanges?.currentValue !== entityNameChanges?.previousValue) || (entityIdChanges?.currentValue !== entityIdChanges?.previousValue)) {
this.currentEntityFilter = getCalculatedFieldCurrentEntityFilter(this.entityName, this.entityId);
}
}
} }
validate(): ValidationErrors | null { validate(): ValidationErrors | null {

1
ui-ngx/src/app/shared/components/entity/entity-select.component.html

@ -30,6 +30,7 @@
formControlName="entityType"> formControlName="entityType">
</tb-entity-type-select> </tb-entity-type-select>
<tb-entity-autocomplete <tb-entity-autocomplete
#entityAutocompleteComponent
*ngIf="modelValue.entityType && !entityTypeNullUUID.has(modelValue.entityType)" *ngIf="modelValue.entityType && !entityTypeNullUUID.has(modelValue.entityType)"
class="flex-1" class="flex-1"
[appearance]="appearance" [appearance]="appearance"

9
ui-ngx/src/app/shared/components/entity/entity-select.component.ts

@ -14,7 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, DestroyRef, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; import { Component, DestroyRef, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
@ -27,6 +27,7 @@ import { coerceBoolean } from '@shared/decorators/coercion';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatFormFieldAppearance } from '@angular/material/form-field'; import { MatFormFieldAppearance } from '@angular/material/form-field';
import { BaseData } from '@shared/models/base-data'; import { BaseData } from '@shared/models/base-data';
import { EntityAutocompleteComponent } from '@shared/components/entity/entity-autocomplete.component';
@Component({ @Component({
selector: 'tb-entity-select', selector: 'tb-entity-select',
@ -40,6 +41,8 @@ import { BaseData } from '@shared/models/base-data';
}) })
export class EntitySelectComponent implements ControlValueAccessor, OnInit { export class EntitySelectComponent implements ControlValueAccessor, OnInit {
@ViewChild('entityAutocompleteComponent') entityAutocompleteComponent!: EntityAutocompleteComponent;
entitySelectFormGroup: UntypedFormGroup; entitySelectFormGroup: UntypedFormGroup;
modelValue: EntityId = {entityType: null, id: null}; modelValue: EntityId = {entityType: null, id: null};
@ -204,4 +207,8 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit {
changeEntity(entity: BaseData<EntityId>): void { changeEntity(entity: BaseData<EntityId>): void {
this.entityChanged.emit(entity); this.entityChanged.emit(entity);
} }
entityAutocompleteMarkAsTouched() {
this.entityAutocompleteComponent.markAsTouched();
}
} }

4
ui-ngx/src/app/shared/components/string-items-list.component.html

@ -52,7 +52,9 @@
@for (value of filteredValues | async; track value.value) { @for (value of filteredValues | async; track value.value) {
<mat-option [value]="value"><span [innerHTML]="value.name | highlight:searchText"></span></mat-option> <mat-option [value]="value"><span [innerHTML]="value.name | highlight:searchText"></span></mat-option>
} @empty { } @empty {
<mat-option [value]="null">{{ 'common.not-found' | translate }}</mat-option> @if (!allowUserValue) {
<mat-option [value]="null">{{ 'common.not-found' | translate }}</mat-option>
}
} }
</mat-autocomplete> </mat-autocomplete>
<mat-hint [hidden]="!hint"> <mat-hint [hidden]="!hint">

4
ui-ngx/src/app/shared/components/string-items-list.component.ts

@ -210,8 +210,8 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit {
addOnBlur(event: FocusEvent) { addOnBlur(event: FocusEvent) {
const target: HTMLElement = event.relatedTarget as HTMLElement; const target: HTMLElement = event.relatedTarget as HTMLElement;
if (target && target.tagName !== 'MAT-OPTION') { if ((target && target.tagName !== 'MAT-OPTION') || this.allowUserValue) {
this.addItem(this.stringItemInput.nativeElement.value ?? '') this.addItem(this.stringItemInput.nativeElement.value ?? '');
} }
this.onTouched(); this.onTouched();
} }

2
ui-ngx/src/app/shared/models/calculated-field.models.ts

@ -525,7 +525,7 @@ export const ArgumentEntityTypeParamsMap =new Map<ArgumentEntityType, ArgumentEn
]) ])
export const getCalculatedFieldCurrentEntityFilter = (entityName: string, entityId: EntityId) => { export const getCalculatedFieldCurrentEntityFilter = (entityName: string, entityId: EntityId) => {
switch (entityId.entityType) { switch (entityId?.entityType) {
case EntityType.ASSET_PROFILE: case EntityType.ASSET_PROFILE:
return { return {
assetTypes: [entityName], assetTypes: [entityName],

2
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -1435,7 +1435,7 @@
"condition-during": "During {{during}}", "condition-during": "During {{during}}",
"condition-during-dynamic": "During \"{{ attribute }}\"", "condition-during-dynamic": "During \"{{ attribute }}\"",
"condition-repeat-times": "Repeats { count, plural, =1 {1 time} other {# times} }", "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", "filter-preview": "Filter preview",
"condition-settings": "Condition settings", "condition-settings": "Condition settings",
"static": "Static", "static": "Static",

Loading…
Cancel
Save