From 960481752157fe641b7328952ba5563db38c3df2 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 23 Dec 2025 19:25:14 +0200 Subject: [PATCH 1/6] UI: Add cf details page --- .../calculated-field.component.html | 192 ++++++++++++++++++ .../calculated-field.component.scss | 18 ++ .../calculated-field.component.ts | 166 +++++++++++++++ .../calculated-field.module.ts | 4 +- .../calculated-fields-table-config.ts | 52 +++-- .../calculated-fields-table.component.ts | 4 +- .../debug/entity-debug-settings.service.ts | 4 +- .../calculated-fields-routing.module.ts | 84 +++++++- .../app/shared/models/entity-type.models.ts | 8 +- .../assets/locale/locale.constant-en_US.json | 3 +- 10 files changed, 510 insertions(+), 25 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html create mode 100644 ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html new file mode 100644 index 0000000000..5513770627 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html @@ -0,0 +1,192 @@ + +
+ + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
{{ 'common.general' | translate }}
+
+ + {{ 'entity-field.title' | translate }} + + @if (entityForm.get('name').errors && entityForm.get('name').touched) { + + @if (entityForm.get('name').hasError('required')) { + {{ 'common.hint.title-required' | translate }} + } @else if (entityForm.get('name').hasError('pattern')) { + {{ 'common.hint.title-pattern' | translate }} + } @else if (entityForm.get('name').hasError('maxlength')) { + {{ 'common.hint.title-max-length' | translate }} + } + + } + + + +
+ + + {{ 'common.type' | translate }} + + @for (type of fieldTypes; track type) { + {{ CalculatedFieldTypeTranslations.get(type).name | translate}} + } + + @if (CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint) { + {{ CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint | translate }} + } + +
+ @switch (entityForm.get('type').value) { + @case (CalculatedFieldType.GEOFENCING) { + + } + @case (CalculatedFieldType.PROPAGATION) { + + } + @case (CalculatedFieldType.RELATED_ENTITIES_AGGREGATION) { + + } + @case (CalculatedFieldType.ENTITY_AGGREGATION) { + + } + @default { + + } + } +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss new file mode 100644 index 0000000000..7cfc6dfef2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss @@ -0,0 +1,18 @@ +/** + * Copyright © 2016-2025 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 { + +} 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 new file mode 100644 index 0000000000..c521c624d4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts @@ -0,0 +1,166 @@ +/// +/// Copyright © 2016-2025 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. +/// + +import { ChangeDetectorRef, Component, DestroyRef, Inject, Input } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntityComponent } from '../../components/entity/entity.component'; +import { FormBuilder, FormGroup, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { TranslateService } from '@ngx-translate/core'; +import { AssetInfo } from '@app/shared/models/asset.models'; +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +import { + CalculatedFieldConfiguration, + CalculatedFieldInfo, calculatedFieldsEntityTypeList, + CalculatedFieldType, calculatedFieldTypes, CalculatedFieldTypeTranslations, OutputStrategyType +} from '@shared/models/calculated-field.models'; +import { oneSpaceInsideRegex } from '@shared/models/regex.constants'; +import { isDefined } from '@core/utils'; +import { EntityId } from '@shared/models/id/entity-id'; +import { pairwise, switchMap } from 'rxjs/operators'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { BaseData } from '@shared/models/base-data'; +import { Observable, of } from 'rxjs'; +import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { + CalculatedFieldsTableConfig, + CalculatedFieldsTableEntity +} from '@home/components/calculated-fields/calculated-fields-table-config'; + +@Component({ + selector: 'tb-calculated-field', + templateUrl: './calculated-field.component.html', + styleUrls: ['./calculated-field.component.scss'] +}) +export class CalculatedFieldComponent extends EntityComponent { + + @Input() + standalone = false; + + @Input() + entityName: string; + + disabledConfiguration = false; + + readonly tenantId = getCurrentAuthUser(this.store).tenantId; + readonly ownerId = getCurrentAuthUser(this.store).tenantId; + readonly EntityType = EntityType; + readonly calculatedFieldsEntityTypeList = calculatedFieldsEntityTypeList; + readonly CalculatedFieldType = CalculatedFieldType; + readonly fieldTypes = calculatedFieldTypes; + readonly CalculatedFieldTypeTranslations = CalculatedFieldTypeTranslations; + + constructor(protected store: Store, + protected translate: TranslateService, + @Inject('entity') protected entityValue: CalculatedFieldInfo, + @Inject('entitiesTableConfig') protected entitiesTableConfigValue: CalculatedFieldsTableConfig, + protected fb: FormBuilder, + protected cd: ChangeDetectorRef, + private destroyRef: DestroyRef, + private calculatedFieldsService: CalculatedFieldsService) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + isAssignedToCustomer(entity: AssetInfo): boolean { + return entity && entity.customerId && entity.customerId.id !== NULL_UUID; + } + + get entityId(): EntityId { + return this.entityForm.get('entityId').value; + } + + get entitiesTableConfig(): CalculatedFieldsTableConfig { + return this.entitiesTableConfigValue; + } + + changeEntity(entity: BaseData): void { + this.entityName = entity?.name; + } + + buildForm(entity?: CalculatedFieldInfo): FormGroup { + const form = this.fb.group({ + name: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]], + entityId: [null], + type: [CalculatedFieldType.SIMPLE], + debugSettings: [], + configuration: this.fb.control({} as CalculatedFieldConfiguration), + }); + form.get('type').valueChanges.pipe( + pairwise(), + takeUntilDestroyed(this.destroyRef) + ).subscribe(([prevType, nextType]) => { + if (![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(prevType) || + ![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(nextType)) { + form.get('configuration').setValue(({} as CalculatedFieldConfiguration), {emitEvent: false}); + } + }); + return form; + } + + updateForm(entity: CalculatedFieldInfo) { + const { configuration = {} as CalculatedFieldConfiguration, type = CalculatedFieldType.SIMPLE, debugSettings = { failuresEnabled: true, allEnabled: true }, entityId = this.entityId, ...value } = entity ?? {}; + if (configuration.type !== CalculatedFieldType.ALARM) { + if (isDefined(configuration?.output) && !configuration?.output?.strategy) { + configuration.output.strategy = {type: OutputStrategyType.RULE_CHAIN}; + } + } + this.entityForm.patchValue({ configuration, type, debugSettings, entityId, ...value }, {emitEvent: false}); + setTimeout(() => this.entityForm.get('type').updateValueAndValidity({onlySelf: true})); + if (!entityId) { + this.entityForm.get('configuration').disable({emitEvent: false}); + this.disabledConfiguration = true; + } + } + + onTestScript(expression?: string): Observable { + const calculatedFieldId = this.entityId?.id; + if (calculatedFieldId) { + return this.calculatedFieldsService.getLatestCalculatedFieldDebugEvent(calculatedFieldId, {ignoreLoading: true}) + .pipe( + switchMap(event => { + const args = event?.arguments ? JSON.parse(event.arguments) : null; + return this.entitiesTableConfig.getTestScriptDialog(this.entityFormValue(), args, false, expression); + }), + takeUntilDestroyed(this.destroyRef) + ) + } + + return this.entitiesTableConfig.getTestScriptDialog(this.entityFormValue(), null, false, expression); + } + + updateFormState() { + if (this.entityForm) { + if (this.isEditValue) { + this.entityForm.enable({emitEvent: false}); + this.entityForm.get('entityId').disable({emitEvent: false}); + } else { + this.entityForm.disable({emitEvent: false}); + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts index e3f5ac9cc2..8a9241970d 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts @@ -50,6 +50,7 @@ import { import { CalculatedFieldsFilterConfigComponent } from '@home/components/calculated-fields/table-header/calculated-fields-filter-config.component'; +import { CalculatedFieldComponent } from '@home/components/calculated-fields/calculated-field.component'; @NgModule({ declarations: [ @@ -57,7 +58,8 @@ import { CalculatedFieldScriptTestDialogComponent, CalculatedFieldTestArgumentsComponent, CalculatedFieldsHeaderComponent, - CalculatedFieldsFilterConfigComponent + CalculatedFieldsFilterConfigComponent, + CalculatedFieldComponent ], imports: [ CommonModule, 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 8385b5cb45..977e03e454 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 @@ -20,7 +20,7 @@ import { EntityTableColumn, EntityTableConfig } from '@home/models/entity/entities-table-config.models'; -import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; import { TranslateService } from '@ngx-translate/core'; import { Direction } from '@shared/models/page/sort-order'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; @@ -67,8 +67,11 @@ import { EventsDialogComponent, EventsDialogData } from '@home/dialogs/events-di import { CalculatedFieldsHeaderComponent } from '@home/components/calculated-fields/table-header/calculated-fields-header.component'; +import { EntityAction } from '@home/models/entity/entity-component.models'; +import { CalculatedFieldComponent } from '@home/components/calculated-fields/calculated-field.component'; +import { Router } from '@angular/router'; -type CalculatedFieldsTableEntity = CalculatedField | CalculatedFieldInfo; +export type CalculatedFieldsTableEntity = CalculatedField | CalculatedFieldInfo; export class CalculatedFieldsTableConfig extends EntityTableConfig { @@ -93,30 +96,33 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig { - this.editCalculatedField($event, entity); - this.rowPointer = true; - return true; - }; + this.entityComponent = CalculatedFieldComponent; + this.rowPointer = true; } this.tableTitle = this.pageMode ? '' : this.translate.instant('entity.type-calculated-fields'); - this.detailsPanelEnabled = false; + this.detailsPanelEnabled = this.pageMode; this.entityType = EntityType.CALCULATED_FIELD; this.entityTranslations = entityTypeTranslations.get(EntityType.CALCULATED_FIELD); + this.entityResources = entityTypeResources.get(EntityType.CALCULATED_FIELD); this.entitiesFetchFunction = (pageLink: PageLink) => this.fetchCalculatedFields(pageLink); this.addEntity = this.getCalculatedFieldDialog.bind(this); + this.loadEntity = id => this.calculatedFieldsService.getCalculatedFieldById(id.id); this.deleteEntityTitle = (field) => this.translate.instant('calculated-fields.delete-title', {title: field.name}); this.deleteEntityContent = () => this.translate.instant('calculated-fields.delete-text'); this.deleteEntitiesTitle = count => this.translate.instant('calculated-fields.delete-multiple-title', {count}); this.deleteEntitiesContent = () => this.translate.instant('calculated-fields.delete-multiple-text'); this.deleteEntity = id => this.calculatedFieldsService.deleteCalculatedField(id.id); + + this.onEntityAction = action => this.onCFAction(action); + this.addActionDescriptors = [ { name: this.translate.instant('calculated-fields.create'), @@ -172,14 +178,16 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig true, iconFunction: ({ debugSettings }) => this.entityDebugSettingsService.isDebugActive(debugSettings?.allEnabledUntil) || debugSettings?.failuresEnabled ? 'mdi:bug' : 'mdi:bug-outline', onAction: ($event, entity) => this.onOpenDebugConfig($event, entity), - }, - { + } + ); + if (!this.pageMode) { + this.cellActionDescriptors.push({ name: this.translate.instant('action.edit'), icon: 'edit', isEnabled: () => true, onAction: ($event, entity) => this.editCalculatedField($event, entity), - } - ); + }) + } } fetchCalculatedFields(pageLink: PageLink): Observable> { @@ -354,7 +362,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig this.updateData()); } - private getTestScriptDialog(calculatedField: CalculatedFieldsTableEntity, argumentsObj?: CalculatedFieldEventArguments, openCalculatedFieldEdit = true, expression?: string): Observable { + getTestScriptDialog(calculatedField: CalculatedFieldsTableEntity, argumentsObj?: CalculatedFieldEventArguments, openCalculatedFieldEdit = true, expression?: string): Observable { if ( calculatedField.type === CalculatedFieldType.SCRIPT || calculatedField.type === CalculatedFieldType.RELATED_ENTITIES_AGGREGATION || @@ -394,4 +402,22 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig): boolean { + switch (action.action) { + case 'open': + this.openCalculatedField(action.event, action.entity); + return true; + case 'export': + this.exportCalculatedField(action.event, action.entity); + return true; + } + return false; + } } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts index 2a0c29e31a..7687277113 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts @@ -36,7 +36,7 @@ import { ImportExportService } from '@shared/import-export/import-export.service import { EntityDebugSettingsService } from '@home/components/entity/debug/entity-debug-settings.service'; import { DatePipe } from '@angular/common'; import { UtilsService } from "@core/services/utils.service"; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'tb-calculated-fields-table', @@ -70,6 +70,7 @@ export class CalculatedFieldsTableComponent { private utilsService: UtilsService, private destroyRef: DestroyRef, private route: ActivatedRoute, + private router: Router ) { this.pageMode = !!this.route.snapshot.data.isPage; effect(() => { @@ -88,6 +89,7 @@ export class CalculatedFieldsTableComponent { this.importExportService, this.entityDebugSettingsService, this.utilsService, + this.router, this.pageMode, ); this.cd.markForCheck(); diff --git a/ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings.service.ts b/ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings.service.ts index cb982b9650..76def20a24 100644 --- a/ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings.service.ts +++ b/ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings.service.ts @@ -22,7 +22,9 @@ import { TranslateService } from '@ngx-translate/core'; import { DurationLeftPipe } from '@shared/pipe/duration-left.pipe'; import { EntityDebugSettingPanelConfig } from '@home/components/entity/debug/entity-debug-settings.model'; -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class EntityDebugSettingsService { constructor( diff --git a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts index 0ed435075c..03be3d86f6 100644 --- a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts @@ -14,24 +14,94 @@ /// limitations under the License. /// -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; +import { DestroyRef, inject, NgModule, Renderer2 } from '@angular/core'; +import { ActivatedRouteSnapshot, ResolveFn, Router, RouterModule, RouterStateSnapshot, Routes } from '@angular/router'; import { Authority } from '@shared/models/authority.enum'; import { MenuId } from '@core/services/menu.models'; import { CalculatedFieldsTableComponent } from '@home/components/calculated-fields/calculated-fields-table.component'; +import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component'; +import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; +import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; +import { BreadCrumbConfig } from '@shared/components/breadcrumb'; +import { CalculatedFieldsTableConfig } from '@home/components/calculated-fields/calculated-fields-table-config'; +import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; +import { TranslateService } from '@ngx-translate/core'; +import { MatDialog } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { DatePipe } from '@angular/common'; +import { ImportExportService } from '@shared/import-export/import-export.service'; +import { EntityDebugSettingsService } from '@home/components/entity/debug/entity-debug-settings.service'; +import { UtilsService } from '@core/services/utils.service'; + +export const CalculatedFieldsTableConfigResolver: ResolveFn = + (_route: ActivatedRouteSnapshot, + _state: RouterStateSnapshot, + calculatedFieldsService = inject(CalculatedFieldsService), + translate = inject(TranslateService), + dialog = inject(MatDialog), + store = inject(Store), + datePipe = inject(DatePipe), + destroyRef = inject(DestroyRef), + importExportService = inject(ImportExportService), + entityDebugSettingsService = inject(EntityDebugSettingsService), + utilsService = inject(UtilsService), + router = inject(Router), + ) => { + return new CalculatedFieldsTableConfig( + calculatedFieldsService, + translate, + dialog, + datePipe, + null, + store, + destroyRef, + null, + null, + null, + importExportService, + entityDebugSettingsService, + utilsService, + router, + false, + ); + }; const routes: Routes = [ { path: 'calculatedFields', - component: CalculatedFieldsTableComponent, data: { - auth: [Authority.TENANT_ADMIN], - title: 'entity.type-calculated-fields', breadcrumb: { menuId: MenuId.calculated_fields + } + }, + children: [ + { + path: '', + component: CalculatedFieldsTableComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'entity.type-calculated-fields', + isPage: true, + } }, - isPage: true, - } + { + path: ':entityId', + component: EntityDetailsPageComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + breadcrumb: { + labelFunction: entityDetailsPageBreadcrumbLabelFunction, + icon: 'mdi:function-variant' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN], + title: 'entity.type-calculated-fields', + }, + resolve: { + entitiesTableConfig: CalculatedFieldsTableConfigResolver + } + } + ] } ]; diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 48428932e4..bc0d3fd470 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -490,7 +490,7 @@ export const entityTypeTranslations = new Map Date: Wed, 24 Dec 2025 16:03:02 +0200 Subject: [PATCH 2/6] UI: Fixed bug with cf page --- .../calculated-field.component.html | 7 ++++++- .../calculated-fields/calculated-field.component.ts | 2 +- .../calculated-fields-table-config.ts | 2 +- .../output/calculated-field-output.component.ts | 2 +- .../simple-configuration.component.ts | 2 +- .../components/entity/entity-select.component.html | 2 ++ .../components/entity/entity-select.component.ts | 13 ++++++++++++- 7 files changed, 24 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html index 5513770627..10ab150b69 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html @@ -125,7 +125,12 @@ [additionalActionConfig]="entitiesTableConfig.additionalDebugActionConfig" /> - + + {{ 'common.type' | translate }} 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 c521c624d4..0b62d9bb96 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 @@ -60,7 +60,7 @@ export class CalculatedFieldComponent extends EntityComponent void): void { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts index e6ff1d1bad..2771795635 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts @@ -109,6 +109,7 @@ export class SimpleConfigurationComponent implements ControlValueAccessor, Valid private propagateChange: (config: SimpeConfiguration) => void = () => { }; constructor(private fb: FormBuilder) { + this.updatedFormWithScript(); this.simpleConfiguration.get('output').valueChanges.pipe( takeUntilDestroyed(), ).subscribe(() => { @@ -151,7 +152,6 @@ export class SimpleConfigurationComponent implements ControlValueAccessor, Valid formValue.expressionSIMPLE = formValue.expression; } this.simpleConfiguration.patchValue(formValue, {emitEvent: false}); - this.updatedFormWithScript(); setTimeout(() => { this.simpleConfiguration.get('arguments').updateValueAndValidity({onlySelf: true}); }); diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.html b/ui-ngx/src/app/shared/components/entity/entity-select.component.html index c0b4f1aafb..49e853e722 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.html +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.html @@ -25,6 +25,7 @@ [useAliasEntityTypes]="useAliasEntityTypes" [allowedEntityTypes]="allowedEntityTypes" [additionEntityTypes]="additionEntityTypes" + [filterAllowedEntityTypes]="filterAllowedEntityTypes" formControlName="entityType"> diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index 5e75426952..fbfb7ae453 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { AfterViewInit, Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core'; +import { AfterViewInit, Component, DestroyRef, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -26,6 +26,7 @@ import { NULL_UUID } from '@shared/models/id/has-uuid'; import { coerceBoolean } from '@shared/decorators/coercion'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatFormFieldAppearance } from '@angular/material/form-field'; +import { BaseData } from '@shared/models/base-data'; @Component({ selector: 'tb-entity-select', @@ -66,6 +67,12 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte @coerceBoolean() useEntityDisplayName = false; + @Input() + filterAllowedEntityTypes: boolean; + + @Output() + entityChanged = new EventEmitter>(); + displayEntityTypeSelect: boolean; AliasEntityType = AliasEntityType; @@ -176,4 +183,8 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte } } } + + changeEntity(entity: BaseData): void { + this.entityChanged.emit(entity); + } } From 2ee94a616f2cb3f2658b912e40c9023a271ac8c9 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 24 Dec 2025 16:36:08 +0200 Subject: [PATCH 3/6] UI: Add cf details panel tabs refactor edit value --- .../alarm-rules/alarm-rules-table-config.ts | 2 +- .../calculated-field.component.html | 259 +++++++----------- .../calculated-field.component.scss | 36 ++- .../calculated-field.component.ts | 52 ++-- .../calculated-field.module.ts | 3 +- .../calculated-fields-table-config.ts | 28 +- .../entity-aggregation-component.component.ts | 10 +- .../geofencing-configuration.component.ts | 4 +- .../calculated-field-output.component.ts | 2 +- .../propagation-configuration.component.ts | 6 +- .../simple-configuration.component.ts | 1 - .../entity/entity-tabs.component.ts | 10 +- .../components/event/event-table-config.ts | 16 +- .../components/event/event-table.component.ts | 24 +- .../home/dialogs/events-dialog.component.html | 1 + .../home/dialogs/events-dialog.component.ts | 6 +- ...ule.ts => calculated-field-page.module.ts} | 10 +- .../calculated-fields-routing.module.ts | 4 +- .../calculated-fields-tabs.component.html | 30 ++ .../calculated-fields-tabs.component.ts | 49 ++++ .../modules/home/pages/home-pages.module.ts | 4 +- .../shared/models/calculated-field.models.ts | 6 + .../assets/locale/locale.constant-en_US.json | 1 + 23 files changed, 342 insertions(+), 222 deletions(-) rename ui-ngx/src/app/modules/home/pages/calculated-fields/{calculated-fields.module.ts => calculated-field-page.module.ts} (77%) create mode 100644 ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts index 3d1c066839..b4082fb75c 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts @@ -288,7 +288,7 @@ export class AlarmRulesTableConfig extends EntityTableConfig false + debugActionDisabled: true } }) .afterClosed() diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html index 10ab150b69..105c1c74a0 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html @@ -20,178 +20,125 @@ [disabled]="(isLoading$ | async)" (click)="onEntityAction($event, 'open')" [class.!hidden]="isEdit || isDetailsPage"> - {{'common.open-details-page' | translate }} + {{ 'common.open-details-page' | translate }} - - - - - - - - - - + + + + + + + + + + -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
{{ 'common.general' | translate }}
-
- - {{ 'entity-field.title' | translate }} - - @if (entityForm.get('name').errors && entityForm.get('name').touched) { - - @if (entityForm.get('name').hasError('required')) { - {{ 'common.hint.title-required' | translate }} - } @else if (entityForm.get('name').hasError('pattern')) { - {{ 'common.hint.title-pattern' | translate }} - } @else if (entityForm.get('name').hasError('maxlength')) { - {{ 'common.hint.title-max-length' | translate }} - } - - } - - - -
- - - - {{ 'common.type' | translate }} - - @for (type of fieldTypes; track type) { - {{ CalculatedFieldTypeTranslations.get(type).name | translate}} - } - - @if (CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint) { - {{ CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint | translate }} +
+
+
+
{{ 'common.general' | translate }}
+
+ + {{ 'entity-field.title' | translate }} + + @if (entityForm.get('name').errors && entityForm.get('name').touched) { + + @if (entityForm.get('name').hasError('required')) { + {{ 'common.hint.title-required' | translate }} + } @else if (entityForm.get('name').hasError('pattern')) { + {{ 'common.hint.title-pattern' | translate }} + } @else if (entityForm.get('name').hasError('maxlength')) { + {{ 'common.hint.title-max-length' | translate }} + } + } + +
- @switch (entityForm.get('type').value) { - @case (CalculatedFieldType.GEOFENCING) { - - } - @case (CalculatedFieldType.PROPAGATION) { - - } - @case (CalculatedFieldType.RELATED_ENTITIES_AGGREGATION) { - - } - @case (CalculatedFieldType.ENTITY_AGGREGATION) { - - } - @default { - + + + + {{ 'common.type' | translate }} + + @for (type of fieldTypes; track type) { + {{ CalculatedFieldTypeTranslations.get(type).name | translate }} + } + + @if (CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint) { + {{ CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint | translate }} } - } +
+ @switch (entityForm.get('type').value) { + @case (CalculatedFieldType.GEOFENCING) { + + } + @case (CalculatedFieldType.PROPAGATION) { + + } + @case (CalculatedFieldType.RELATED_ENTITIES_AGGREGATION) { + + } + @case (CalculatedFieldType.ENTITY_AGGREGATION) { + + } + @default { + + } + }
diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss index 7cfc6dfef2..5bcd2552fc 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss @@ -13,6 +13,40 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host { +:host ::ng-deep { + .tbel-script-lang-chip { + line-height: 20px; + font-size: 14px; + font-weight: 500; + color: white; + border-radius: 100px; + width: 70px; + min-width: 70px; + display: flex; + justify-content: center; + margin-top: 2px; + margin-right: 4px; + } + .tb-js-func { + .ace_tb { + &.ace_calculated-field { + &-ctx { + color: #C52F00; + } + &-args { + color: #185F2A; + } + &-key { + color: #c24c1a; + } + &-time-window, &-values, &-func, &-value, &-ts, &-latestTs { + color: #7214D0; + } + &-start-ts, &-end-ts { + color: #2CAA00; + } + } + } + } } 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 0b62d9bb96..308e350116 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 @@ -18,17 +18,17 @@ import { ChangeDetectorRef, Component, DestroyRef, Inject, Input } from '@angula import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityComponent } from '../../components/entity/entity.component'; -import { FormBuilder, FormGroup, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; -import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; -import { NULL_UUID } from '@shared/models/id/has-uuid'; -import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { EntityType } from '@shared/models/entity-type.models'; import { TranslateService } from '@ngx-translate/core'; -import { AssetInfo } from '@app/shared/models/asset.models'; -import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { CalculatedFieldConfiguration, - CalculatedFieldInfo, calculatedFieldsEntityTypeList, - CalculatedFieldType, calculatedFieldTypes, CalculatedFieldTypeTranslations, OutputStrategyType + CalculatedFieldInfo, + calculatedFieldsEntityTypeList, + CalculatedFieldType, + calculatedFieldTypes, + CalculatedFieldTypeTranslations, + OutputStrategyType } from '@shared/models/calculated-field.models'; import { oneSpaceInsideRegex } from '@shared/models/regex.constants'; import { isDefined } from '@core/utils'; @@ -36,13 +36,14 @@ import { EntityId } from '@shared/models/id/entity-id'; import { pairwise, switchMap } from 'rxjs/operators'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { BaseData } from '@shared/models/base-data'; -import { Observable, of } from 'rxjs'; +import { Observable } from 'rxjs'; import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { CalculatedFieldsTableConfig, CalculatedFieldsTableEntity } from '@home/components/calculated-fields/calculated-fields-table-config'; +import { TenantId } from '@shared/models/id/tenant-id'; @Component({ selector: 'tb-calculated-field', @@ -60,7 +61,7 @@ export class CalculatedFieldComponent extends EntityComponent this.entitiesTableConfig.additionalDebugActionConfig.action( + { id: this.entity.id, ...this.entityFormValue() }, false, + (expression) => { + if (expression) { + this.entityForm.get('configuration').setValue({...this.entityFormValue().configuration, expression}); + this.entityForm.get('configuration').markAsDirty(); + } + }), + }; get entityId(): EntityId { return this.entityForm.get('entityId').value; @@ -114,9 +123,11 @@ export class CalculatedFieldComponent extends EntityComponent { - if (![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(prevType) || - ![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(nextType)) { - form.get('configuration').setValue(({} as CalculatedFieldConfiguration), {emitEvent: false}); + if (this.isEditValue) { + if (![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(prevType) || + ![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(nextType)) { + form.get('configuration').setValue(({} as CalculatedFieldConfiguration), {emitEvent: false}); + } } }); return form; @@ -129,8 +140,11 @@ export class CalculatedFieldComponent extends EntityComponent this.entityForm.get('type').updateValueAndValidity({onlySelf: true})); + this.entityForm.patchValue({ type }, {emitEvent: false}); + setTimeout(() => { + this.entityForm.patchValue({ configuration, debugSettings, entityId, ...value }, {emitEvent: false}); + this.entityForm.get('type').updateValueAndValidity({onlySelf: true}); + }); if (!entityId) { this.entityForm.get('configuration').disable({emitEvent: false}); this.disabledConfiguration = true; @@ -138,7 +152,7 @@ export class CalculatedFieldComponent extends EntityComponent { - const calculatedFieldId = this.entityId?.id; + const calculatedFieldId = this.entity?.id?.id; if (calculatedFieldId) { return this.calculatedFieldsService.getLatestCalculatedFieldDebugEvent(calculatedFieldId, {ignoreLoading: true}) .pipe( diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts index 8a9241970d..2bcfcd7a1b 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts @@ -59,7 +59,7 @@ import { CalculatedFieldComponent } from '@home/components/calculated-fields/cal CalculatedFieldTestArgumentsComponent, CalculatedFieldsHeaderComponent, CalculatedFieldsFilterConfigComponent, - CalculatedFieldComponent + CalculatedFieldComponent, ], imports: [ CommonModule, @@ -74,6 +74,7 @@ import { CalculatedFieldComponent } from '@home/components/calculated-fields/cal exports: [ CalculatedFieldDialogComponent, CalculatedFieldScriptTestDialogComponent, + CalculatedFieldComponent, ] }) export class CalculatedFieldsModule {} 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 e56944e00f..5841d9e511 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 @@ -46,6 +46,7 @@ import { CalculatedFieldsQuery, CalculatedFieldType, CalculatedFieldTypeTranslations, + debugCfActionEnabled, getCalculatedFieldArgumentsEditorCompleter, getCalculatedFieldArgumentsHighlights, PropagationWithExpression, @@ -62,7 +63,7 @@ import { EntityDebugSettingsService } from '@home/components/entity/debug/entity import { DatePipe } from '@angular/common'; import { UtilsService } from "@core/services/utils.service"; import { ActionNotificationShow } from "@core/notification/notification.actions"; -import { CalculatedFieldEventBody, DebugEventType, Event as DebugEvent, EventType } from '@shared/models/event.models'; +import { CalculatedFieldEventBody, DebugEventType, EventType } from '@shared/models/event.models'; import { EventsDialogComponent, EventsDialogData } from '@home/dialogs/events-dialog.component'; import { CalculatedFieldsHeaderComponent @@ -70,6 +71,7 @@ import { import { EntityAction } from '@home/models/entity/entity-component.models'; import { CalculatedFieldComponent } from '@home/components/calculated-fields/calculated-field.component'; import { Router } from '@angular/router'; +import { CalculatedFieldsTabsComponent } from '@home/pages/calculated-fields/calculated-fields-tabs.component'; export type CalculatedFieldsTableEntity = CalculatedField | CalculatedFieldInfo; @@ -78,7 +80,9 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig this.openDebugEventsDialog.call(this, null, calculatedField), + action: (calculatedField: CalculatedFieldsTableEntity, + openCalculatedFieldEdit = true, + afterCloseCallback?: (expression: string) => void) => this.openDebugEventsDialog.call(this, null, calculatedField, openCalculatedFieldEdit, afterCloseCallback), }; calculatedFieldFilterConfig: CalculatedFieldsQuery; @@ -104,6 +108,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig this.fetchCalculatedFields(pageLink); this.addEntity = this.getCalculatedFieldDialog.bind(this); + this.saveEntity = (cf) => this.calculatedFieldsService.saveCalculatedField(cf); this.loadEntity = id => this.calculatedFieldsService.getCalculatedFieldById(id.id); this.deleteEntityTitle = (field) => this.translate.instant('calculated-fields.delete-title', {title: field.name}); this.deleteEntityContent = () => this.translate.instant('calculated-fields.delete-text'); @@ -253,17 +259,11 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig void ): void { $event?.stopPropagation(); - const debugActionEnabledFn = (event: DebugEvent) => { - return (calculatedField.type === CalculatedFieldType.SCRIPT || - (calculatedField.type === CalculatedFieldType.PROPAGATION && - calculatedField.configuration.applyExpressionToResolvedArguments) - ) && !!(event as DebugEvent).body.arguments; - }; const onDebugEventSelected = (event: CalculatedFieldEventBody, dialogRef: MatDialogRef) => { - this.getTestScriptDialog(calculatedField, JSON.parse(event.arguments)) + this.getTestScriptDialog(calculatedField, JSON.parse(event.arguments), openCalculatedFieldEdit) .subscribe(expression => dialogRef.close(expression)); }; @@ -278,11 +278,15 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig { + if (afterCloseCallback) { + afterCloseCallback(value) + } + }); } private exportCalculatedField($event: Event, calculatedField: CalculatedFieldsTableEntity): void { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts index e3655f5370..6f281b8e25 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts @@ -182,10 +182,12 @@ export class EntityAggregationComponentComponent implements ControlValueAccessor interval: {...value.interval, allowOffsetSec: isDefinedAndNotNull(value?.interval?.offsetSec)} } this.entityAggregationConfiguration.patchValue(data, {emitEvent: false}); - this.checkAggIntervalType(this.entityAggregationConfiguration.get('interval.type').value); - this.checkIntervalDuration(this.entityAggregationConfiguration.get('interval.allowOffsetSec').value); - this.checkWatermark(this.entityAggregationConfiguration.get('allowWatermark').value); - this.checkProduceIntermediate(); + if (this.entityAggregationConfiguration.enabled) { + this.checkAggIntervalType(this.entityAggregationConfiguration.get('interval.type').value); + this.checkIntervalDuration(this.entityAggregationConfiguration.get('interval.allowOffsetSec').value); + this.checkWatermark(this.entityAggregationConfiguration.get('allowWatermark').value); + this.checkProduceIntermediate(); + } this.updatedOffsetHint(); setTimeout(() => { this.entityAggregationConfiguration.get('arguments').updateValueAndValidity({onlySelf: true}); diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/geofencing-configuration.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/geofencing-configuration.component.ts index 3406922b9e..9df60b6094 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/geofencing-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/geofencing-configuration.component.ts @@ -124,7 +124,9 @@ export class GeofencingConfigurationComponent implements ControlValueAccessor, V writeValue(config: CalculatedFieldGeofencingConfiguration): void { this.geofencingConfiguration.patchValue(config, {emitEvent: false}); this.checkRelatedEntity(this.geofencingConfiguration.get('zoneGroups').value); - this.checkScheduledUpdateEnabled(this.geofencingConfiguration.get('scheduledUpdateEnabled').value); + if (this.geofencingConfiguration.enabled) { + this.checkScheduledUpdateEnabled(this.geofencingConfiguration.get('scheduledUpdateEnabled').value); + } } registerOnChange(fn: (config: CalculatedFieldGeofencingConfiguration) => void): void { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts index c254dcd2b4..00fea861f6 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts @@ -148,7 +148,7 @@ export class CalculatedFieldOutputComponent implements ControlValueAccessor, Val for (const propName of Object.keys(changes)) { const change = changes[propName]; if (change.currentValue !== change.previousValue) { - if (propName === 'simpleMode') { + if (propName === 'simpleMode' && !this.disabled) { this.updatedFormWithMode(); if (!change.firstChange) { this.outputForm.updateValueAndValidity(); diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.ts index 1c493148b3..33acbcf86d 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.ts @@ -131,9 +131,11 @@ export class PropagationConfigurationComponent implements ControlValueAccessor, writeValue(value: PropagationWithExpression): void { value.expression = value.expression ?? calculatedFieldDefaultScript; this.propagateConfiguration.patchValue(value, {emitEvent: false}); - this.updatedFormWithScript(); + if (!this.disabled) { + this.updatedFormWithScript(); + } setTimeout(() => { - this.propagateConfiguration.get('arguments').updateValueAndValidity({onlySelf: true}); + this.propagateConfiguration.get('arguments').updateValueAndValidity({onlySelf: true, emitEvent: false}); }); } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts index 2771795635..ec07fcae03 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts @@ -109,7 +109,6 @@ export class SimpleConfigurationComponent implements ControlValueAccessor, Valid private propagateChange: (config: SimpeConfiguration) => void = () => { }; constructor(private fb: FormBuilder) { - this.updatedFormWithScript(); this.simpleConfiguration.get('output').valueChanges.pipe( takeUntilDestroyed(), ).subscribe(() => { diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts index 3d297f0317..066ef268e5 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts @@ -16,7 +16,7 @@ import { BaseData, HasId } from '@shared/models/base-data'; import { PageComponent } from '@shared/components/page.component'; -import { AfterViewInit, Directive, Input, OnInit, QueryList, ViewChildren } from '@angular/core'; +import { AfterViewInit, Directive, inject, Input, OnInit, QueryList, ViewChildren } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; @@ -100,9 +100,11 @@ export abstract class EntityTabsComponent, entityTabsChanged = this.entityTabsSubject.asObservable(); - protected constructor(protected store: Store) { - super(store); - this.authUser = getCurrentAuthUser(store); + protected store: Store = inject(Store); + + protected constructor(...args: unknown[]) { + super(); + this.authUser = getCurrentAuthUser(this.store); } ngOnInit() { diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts index 03fc641735..accc3d7060 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts @@ -22,7 +22,14 @@ import { EntityTableColumn, EntityTableConfig } from '@home/models/entity/entities-table-config.models'; -import { DebugEventType, Event, EventBody, EventType, FilterEventBody } from '@shared/models/event.models'; +import { + DebugEventType, + Event as DebugEvent, + Event, + EventBody, + EventType, + FilterEventBody +} from '@shared/models/event.models'; import { TimePageLink } from '@shared/models/page/page-link'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; @@ -95,7 +102,8 @@ export class EventTableConfig extends EntityTableConfig { private store: Store, public testButtonLabel?: string, private debugEventSelected?: EventEmitter, - public hideClearEventAction = false) { + public hideClearEventAction = false, + public disableDebugEventAction = false) { super(); this.loadDataOnInit = false; this.tableTitle = ''; @@ -453,7 +461,7 @@ export class EventTableConfig extends EntityTableConfig { this.cellActionDescriptors.push({ name: this.translate.instant('rulenode.test-with-this-message', {test: this.translate.instant(this.testButtonLabel)}), icon: 'bug_report', - isEnabled: (entity) => entity.body.type === 'IN' || entity.body.error !== undefined, + isEnabled: (entity) => (entity.body.type === 'IN' || entity.body.error !== undefined) && !this.disableDebugEventAction, onAction: ($event, entity) => { this.debugEventSelected.next(entity.body); } @@ -464,7 +472,7 @@ export class EventTableConfig extends EntityTableConfig { this.cellActionDescriptors.push({ name: this.translate.instant('calculated-fields.test-with-this-message'), icon: 'bug_report', - isEnabled: () => true, + isEnabled: (event) => !this.disableDebugEventAction && !!(event as DebugEvent).body.arguments, onAction: (_, entity) => this.debugEventSelected.next(entity.body) }); break; diff --git a/ui-ngx/src/app/modules/home/components/event/event-table.component.ts b/ui-ngx/src/app/modules/home/components/event/event-table.component.ts index 53cb8e1b96..7011c23449 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table.component.ts @@ -17,10 +17,12 @@ import { AfterViewInit, ChangeDetectorRef, - Component, EventEmitter, + Component, + EventEmitter, Input, OnDestroy, - OnInit, Output, + OnInit, + Output, ViewChild, ViewContainerRef } from '@angular/core'; @@ -61,6 +63,21 @@ export class EventTableComponent implements OnInit, AfterViewInit, OnDestroy { @Input() hideClearEventAction: boolean = false; + private disableDebugEventActionValue = false; + + get disableDebugEventAction() { + return this.disableDebugEventActionValue; + } + + @Input() + set disableDebugEventAction(value) { + this.disableDebugEventActionValue = value; + if (this.eventTableConfig) { + this.eventTableConfig.disableDebugEventAction = this.disableDebugEventAction; + this.eventTableConfig.updateCellAction(); + } + }; + activeValue = false; dirtyValue = false; entityIdValue: EntityId; @@ -151,7 +168,8 @@ export class EventTableComponent implements OnInit, AfterViewInit, OnDestroy { this.store, this.functionTestButtonLabel, this.debugEventSelected, - this.hideClearEventAction + this.hideClearEventAction, + this.disableDebugEventAction ); } diff --git a/ui-ngx/src/app/modules/home/dialogs/events-dialog.component.html b/ui-ngx/src/app/modules/home/dialogs/events-dialog.component.html index 655c25545d..ab48435b2d 100644 --- a/ui-ngx/src/app/modules/home/dialogs/events-dialog.component.html +++ b/ui-ngx/src/app/modules/home/dialogs/events-dialog.component.html @@ -34,6 +34,7 @@ [tenantId]="data.tenantId" [entityId]="data.entityId" [functionTestButtonLabel]="data.functionTestButtonLabel ?? ''" + [disableDebugEventAction]="data.debugActionDisabled" (debugEventSelected)="this.onDebugEventSelected($event)" />
diff --git a/ui-ngx/src/app/modules/home/dialogs/events-dialog.component.ts b/ui-ngx/src/app/modules/home/dialogs/events-dialog.component.ts index d5339de3b2..8008e74523 100644 --- a/ui-ngx/src/app/modules/home/dialogs/events-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/dialogs/events-dialog.component.ts @@ -23,7 +23,6 @@ import { AppState } from '@core/core.state'; import { Router } from '@angular/router'; import { DebugEventType, EventType } from '@shared/models/event.models'; import { EntityId } from '@shared/models/id/entity-id'; -import { BaseData, HasId } from '@shared/models/base-data'; export interface EventsDialogData { title: string; @@ -34,7 +33,7 @@ export interface EventsDialogData { disabledEventTypes?: Array; functionTestButtonLabel?: string; onDebugEventSelected?: (event: any, dialogRef: MatDialogRef) => void; - debugActionEnabledFn?: (event: BaseData) => boolean; + debugActionDisabled?: boolean; } @Component({ @@ -54,9 +53,6 @@ export class EventsDialogComponent extends DialogComponent 0) { - this.eventsTable.entitiesTable.cellActionDescriptors[0].isEnabled = this.data.debugActionEnabledFn; - } this.eventsTable.entitiesTable.updateData(); } diff --git a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields.module.ts b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-field-page.module.ts similarity index 77% rename from ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields.module.ts rename to ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-field-page.module.ts index 01ed3577e8..f0672c2e5e 100644 --- a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields.module.ts +++ b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-field-page.module.ts @@ -20,15 +20,19 @@ import { SharedModule } from '@shared/shared.module'; import { HomeDialogsModule } from '../../dialogs/home-dialogs.module'; import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { CalculatedFieldsRoutingModule } from '@home/pages/calculated-fields/calculated-fields-routing.module'; +import { CalculatedFieldsModule } from '@home/components/calculated-fields/calculated-field.module'; +import { CalculatedFieldsTabsComponent } from '@home/pages/calculated-fields/calculated-fields-tabs.component'; @NgModule({ - declarations: [], + declarations: [ + CalculatedFieldsTabsComponent + ], imports: [ CommonModule, SharedModule, HomeComponentsModule, HomeDialogsModule, - CalculatedFieldsRoutingModule + CalculatedFieldsRoutingModule, ] }) -export class CalculatedFieldsModule { } \ No newline at end of file +export class CalculatedFieldPageModule { } \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts index 03be3d86f6..e703445155 100644 --- a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { DestroyRef, inject, NgModule, Renderer2 } from '@angular/core'; +import { DestroyRef, inject, NgModule } from '@angular/core'; import { ActivatedRouteSnapshot, ResolveFn, Router, RouterModule, RouterStateSnapshot, Routes } from '@angular/router'; import { Authority } from '@shared/models/authority.enum'; import { MenuId } from '@core/services/menu.models'; @@ -63,7 +63,7 @@ export const CalculatedFieldsTableConfigResolver: ResolveFn +@if (entity) { + + + +} diff --git a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts new file mode 100644 index 0000000000..0c41ddc33d --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts @@ -0,0 +1,49 @@ +/// +/// Copyright © 2016-2025 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. +/// + +import { Component } from '@angular/core'; +import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; +import { CalculatedFieldEventBody, DebugEventType, EventType } from '@shared/models/event.models'; +import type { + CalculatedFieldsTableConfig, + CalculatedFieldsTableEntity +} from '@home/components/calculated-fields/calculated-fields-table-config'; +import { debugCfActionEnabled } from '@shared/models/calculated-field.models'; + +@Component({ + selector: 'tb-calculated-fields-tabs', + templateUrl: './calculated-fields-tabs.component.html', + styleUrls: [] +}) +export class CalculatedFieldsTabsComponent extends EntityTabsComponent { + + readonly DebugEventType = DebugEventType; + readonly EventType = EventType; + + constructor() { + super(); + } + + get debugActionDisabled(): boolean { + return !debugCfActionEnabled(this.entity); + }; + + onDebugEventSelected(event: CalculatedFieldEventBody) { + (this.entitiesTableConfig as CalculatedFieldsTableConfig).getTestScriptDialog(this.entity, JSON.parse(event.arguments)) + .subscribe((expression) => { + }); + }; +} diff --git a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts index c43b89d141..d84b646525 100644 --- a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts @@ -27,7 +27,7 @@ import { UserModule } from '@modules/home/pages/user/user.module'; import { DeviceModule } from '@modules/home/pages/device/device.module'; import { AssetModule } from '@modules/home/pages/asset/asset.module'; import { EntityViewModule } from '@modules/home/pages/entity-view/entity-view.module'; -import { CalculatedFieldsModule } from '@home/pages/calculated-fields/calculated-fields.module'; +import { CalculatedFieldPageModule } from '@home/pages/calculated-fields/calculated-field-page.module'; import { RuleChainModule } from '@modules/home/pages/rulechain/rulechain.module'; import { WidgetLibraryModule } from '@modules/home/pages/widget/widget-library.module'; import { DashboardModule } from '@modules/home/pages/dashboard/dashboard.module'; @@ -70,7 +70,7 @@ import { AiModelModule } from '@home/pages/ai-model/ai-model.module'; EdgeModule, EntityViewModule, CustomerModule, - CalculatedFieldsModule, + CalculatedFieldPageModule, RuleChainModule, WidgetLibraryModule, DashboardModule, 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 825c08a69f..30f5a27686 100644 --- a/ui-ngx/src/app/shared/models/calculated-field.models.ts +++ b/ui-ngx/src/app/shared/models/calculated-field.models.ts @@ -540,6 +540,12 @@ export const getCalculatedFieldCurrentEntityFilter = (entityName: string, entity } } +export const debugCfActionEnabled = (cf: CalculatedField) => { + return (cf.type === CalculatedFieldType.SCRIPT || + (cf.type === CalculatedFieldType.PROPAGATION && cf.configuration.applyExpressionToResolvedArguments) + ); +} + export interface CalculatedFieldArgumentValueBase { argumentName: string; type: ArgumentType; 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 73a8bbf23f..8c2ecc2890 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1221,6 +1221,7 @@ "calculated-field-filter-title": "Calculated field filter", "filter-title": "Filter", "calculated-field-types": "Calculated field types", + "events": "Events", "any-type": "Any type", "metrics": { "metrics": "Metrics", From c50b24d014bead4905627a7e2e6269020e97a73c Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 24 Dec 2025 19:12:06 +0200 Subject: [PATCH 4/6] UI: Extract same method in cf for service --- .../calculated-field-form.service.ts | 94 +++++++++++++++++++ .../calculated-field.component.ts | 74 ++++----------- .../calculated-field-dialog.component.html | 30 ++---- .../calculated-field-dialog.component.ts | 78 ++++----------- .../entity/entity-table-component.models.ts | 9 +- .../calculated-fields-tabs.component.ts | 10 +- .../entity/entity-select.component.ts | 30 ++++-- 7 files changed, 179 insertions(+), 146 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field-form.service.ts diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field-form.service.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field-form.service.ts new file mode 100644 index 0000000000..6ef02f0cf8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field-form.service.ts @@ -0,0 +1,94 @@ +/// +/// Copyright © 2016-2025 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. +/// + +import { DestroyRef, inject, Injectable } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { pairwise, switchMap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { + CalculatedField, + CalculatedFieldConfiguration, + CalculatedFieldEventArguments, + CalculatedFieldType, + OutputStrategyType +} from '@shared/models/calculated-field.models'; +import { oneSpaceInsideRegex } from '@shared/models/regex.constants'; +import { isDefined } from '@core/utils'; +import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; +import { CalculatedFieldsTableEntity } from '@home/components/calculated-fields/calculated-fields-table-config'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { EntityType } from '@shared/models/entity-type.models'; + +@Injectable({ providedIn: 'root' }) +export class CalculatedFieldFormService { + private fb = inject(FormBuilder); + private calculatedFieldsService = inject(CalculatedFieldsService); + + buildForm(): FormGroup { + return this.fb.group({ + name: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]], + entityId: [{type: EntityType.DEVICE_PROFILE, id: null}, Validators.required], + type: [CalculatedFieldType.SIMPLE], + debugSettings: [], + configuration: this.fb.control({} as CalculatedFieldConfiguration), + }); + } + + setupTypeChange(form: FormGroup, destroyRef: DestroyRef, isEditActive?: () => boolean): void { + form.get('type').valueChanges.pipe( + pairwise(), + takeUntilDestroyed(destroyRef) + ).subscribe(([prevType, nextType]) => { + const shouldCheck = isEditActive ? isEditActive() : true; + if (shouldCheck) { + if (![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(prevType) || + ![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(nextType)) { + form.get('configuration').setValue({} as CalculatedFieldConfiguration, { emitEvent: false }); + } + } + }); + } + + prepareConfig(configuration: CalculatedFieldConfiguration): CalculatedFieldConfiguration { + const config = configuration || ({} as CalculatedFieldConfiguration); + if (config.type !== CalculatedFieldType.ALARM) { + if (isDefined(config?.output) && !config?.output?.strategy) { + config.output.strategy = { type: OutputStrategyType.RULE_CHAIN }; + } + } + return config; + } + + testScript( + calculatedFieldId: string, + formValue: CalculatedField, + testDialogFn: (calculatedField: CalculatedFieldsTableEntity, argumentsObj?: CalculatedFieldEventArguments, openCalculatedFieldEdit?: boolean, expression?: string) => Observable, + destroyRef: DestroyRef, + expression?: string, + ): Observable { + if (calculatedFieldId) { + return this.calculatedFieldsService.getLatestCalculatedFieldDebugEvent(calculatedFieldId, {ignoreLoading: true}) + .pipe( + switchMap(event => { + const args = event?.arguments ? JSON.parse(event.arguments) : null; + return testDialogFn(formValue, args, false, expression); + }), + takeUntilDestroyed(destroyRef) + ); + } + return testDialogFn(formValue, null, false, expression); + } +} \ No newline at end of file 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 308e350116..01d7a8de7a 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 @@ -14,11 +14,11 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component, DestroyRef, Inject, Input } from '@angular/core'; +import { ChangeDetectorRef, Component, DestroyRef, inject, Inject, Input } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityComponent } from '../../components/entity/entity.component'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { FormBuilder, FormGroup } from '@angular/forms'; import { EntityType } from '@shared/models/entity-type.models'; import { TranslateService } from '@ngx-translate/core'; import { @@ -27,23 +27,18 @@ import { calculatedFieldsEntityTypeList, CalculatedFieldType, calculatedFieldTypes, - CalculatedFieldTypeTranslations, - OutputStrategyType + CalculatedFieldTypeTranslations } from '@shared/models/calculated-field.models'; -import { oneSpaceInsideRegex } from '@shared/models/regex.constants'; -import { isDefined } from '@core/utils'; import { EntityId } from '@shared/models/id/entity-id'; -import { pairwise, switchMap } from 'rxjs/operators'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { BaseData } from '@shared/models/base-data'; import { Observable } from 'rxjs'; -import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { CalculatedFieldsTableConfig, CalculatedFieldsTableEntity } from '@home/components/calculated-fields/calculated-fields-table-config'; import { TenantId } from '@shared/models/id/tenant-id'; +import { CalculatedFieldFormService } from '@home/components/calculated-fields/calculated-field-form.service'; @Component({ selector: 'tb-calculated-field', @@ -68,14 +63,15 @@ export class CalculatedFieldComponent extends EntityComponent, protected translate: TranslateService, @Inject('entity') protected entityValue: CalculatedFieldInfo, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: CalculatedFieldsTableConfig, protected fb: FormBuilder, - protected cd: ChangeDetectorRef, - private destroyRef: DestroyRef, - private calculatedFieldsService: CalculatedFieldsService) { + protected cd: ChangeDetectorRef) { super(store, fb, entityValue, entitiesTableConfigValue, cd); } @@ -111,60 +107,30 @@ export class CalculatedFieldComponent extends EntityComponent({} as CalculatedFieldConfiguration), - }); - form.get('type').valueChanges.pipe( - pairwise(), - takeUntilDestroyed(this.destroyRef) - ).subscribe(([prevType, nextType]) => { - if (this.isEditValue) { - if (![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(prevType) || - ![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(nextType)) { - form.get('configuration').setValue(({} as CalculatedFieldConfiguration), {emitEvent: false}); - } - } - }); + buildForm(_entity?: CalculatedFieldInfo): FormGroup { + const form = inject(CalculatedFieldFormService).buildForm(); + inject(CalculatedFieldFormService).setupTypeChange(form, inject(DestroyRef), () => this.isEditValue); return form; } updateForm(entity: CalculatedFieldInfo) { const { configuration = {} as CalculatedFieldConfiguration, type = CalculatedFieldType.SIMPLE, debugSettings = { failuresEnabled: true, allEnabled: true }, entityId = this.entityId, ...value } = entity ?? {}; - if (configuration.type !== CalculatedFieldType.ALARM) { - if (isDefined(configuration?.output) && !configuration?.output?.strategy) { - configuration.output.strategy = {type: OutputStrategyType.RULE_CHAIN}; - } - } + const preparedConfig = this.cfFormService.prepareConfig(configuration); this.entityForm.patchValue({ type }, {emitEvent: false}); setTimeout(() => { - this.entityForm.patchValue({ configuration, debugSettings, entityId, ...value }, {emitEvent: false}); + this.entityForm.patchValue({ configuration: preparedConfig, debugSettings, entityId, ...value }, {emitEvent: false}); this.entityForm.get('type').updateValueAndValidity({onlySelf: true}); }); - if (!entityId) { - this.entityForm.get('configuration').disable({emitEvent: false}); - this.disabledConfiguration = true; - } } onTestScript(expression?: string): Observable { - const calculatedFieldId = this.entity?.id?.id; - if (calculatedFieldId) { - return this.calculatedFieldsService.getLatestCalculatedFieldDebugEvent(calculatedFieldId, {ignoreLoading: true}) - .pipe( - switchMap(event => { - const args = event?.arguments ? JSON.parse(event.arguments) : null; - return this.entitiesTableConfig.getTestScriptDialog(this.entityFormValue(), args, false, expression); - }), - takeUntilDestroyed(this.destroyRef) - ) - } - - return this.entitiesTableConfig.getTestScriptDialog(this.entityFormValue(), null, false, expression); + return this.cfFormService.testScript( + this.entity?.id?.id, + this.entityFormValue(), + this.entitiesTableConfig.getTestScriptDialog.bind(this.entitiesTableConfig), + this.destroyRef, + expression + ); } updateFormState() { 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 3d669b02c6..24dd114963 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 @@ -56,29 +56,13 @@ />
@if (!data.entityId) { -
- - - @if (fieldFormGroup.get('entityId.entityType').value) { - - } -
+ + } {{ 'common.type' | translate }} 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 4d2df6e416..3370e65f5b 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 @@ -14,11 +14,10 @@ /// limitations under the License. /// -import { Component, DestroyRef, Inject, ViewChild, ViewEncapsulation } from '@angular/core'; +import { Component, DestroyRef, Inject, ViewEncapsulation } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { FormBuilder, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { DialogComponent } from '@shared/components/dialog.component'; import { @@ -28,21 +27,18 @@ import { CalculatedFieldTestScriptFn, CalculatedFieldType, calculatedFieldTypes, - CalculatedFieldTypeTranslations, - OutputStrategyType + CalculatedFieldTypeTranslations } from '@shared/models/calculated-field.models'; -import { oneSpaceInsideRegex } from '@shared/models/regex.constants'; -import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; -import { pairwise, switchMap } from 'rxjs/operators'; +import { EntityType } from '@shared/models/entity-type.models'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; import { Observable } from 'rxjs'; import { EntityId } from '@shared/models/id/entity-id'; import { AdditionalDebugActionConfig } from '@home/components/entity/debug/entity-debug-settings.model'; -import { deepTrim, isDefined } from '@core/utils'; -import { EntityTypeSelectComponent } from '@shared/components/entity/entity-type-select.component'; -import { EntityAutocompleteComponent } from '@shared/components/entity/entity-autocomplete.component'; +import { deepTrim } from '@core/utils'; import { BaseData } from '@shared/models/base-data'; +import { CalculatedFieldFormService } from '@home/components/calculated-fields/calculated-field-form.service'; +import { FormGroup } from '@angular/forms'; export interface CalculatedFieldDialogData { value?: CalculatedField; @@ -64,16 +60,7 @@ export interface CalculatedFieldDialogData { }) export class CalculatedFieldDialogComponent extends DialogComponent { - fieldFormGroup = this.fb.group({ - name: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]], - entityId: this.fb.group({ - entityType: this.fb.control(EntityType.DEVICE_PROFILE, Validators.required), - id: [null as null | string, Validators.required], - }), - type: [CalculatedFieldType.SIMPLE], - debugSettings: [], - configuration: this.fb.control({} as CalculatedFieldConfiguration), - }); + fieldFormGroup: FormGroup; additionalDebugActionConfig = this.data.value?.id ? { ...this.data.additionalDebugActionConfig, @@ -91,18 +78,16 @@ export class CalculatedFieldDialogComponent extends DialogComponent, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: CalculatedFieldDialogData, protected dialogRef: MatDialogRef, private calculatedFieldsService: CalculatedFieldsService, private destroyRef: DestroyRef, - private fb: FormBuilder) { + private cfFormService: CalculatedFieldFormService) { super(store, router, dialogRef); - this.observeType(); + this.fieldFormGroup = this.cfFormService.buildForm(); + this.cfFormService.setupTypeChange(this.fieldFormGroup, this.destroyRef); this.applyDialogData(); if (this.data.isDirty) { @@ -110,7 +95,7 @@ export class CalculatedFieldDialogComponent extends DialogComponent { this.disabledConfiguration = !entityId; @@ -142,24 +127,17 @@ export class CalculatedFieldDialogComponent extends DialogComponent { - const calculatedFieldId = this.data.value?.id?.id; - if (calculatedFieldId) { - return this.calculatedFieldsService.getLatestCalculatedFieldDebugEvent(calculatedFieldId, {ignoreLoading: true}) - .pipe( - switchMap(event => { - const args = event?.arguments ? JSON.parse(event.arguments) : null; - return this.data.getTestScriptDialogFn(this.fromGroupValue, args, false, expression); - }), - takeUntilDestroyed(this.destroyRef) - ) - } - return this.data.getTestScriptDialogFn(this.fromGroupValue, null, false, expression); + return this.cfFormService.testScript( + this.data.value?.id?.id, + this.fromGroupValue, + this.data.getTestScriptDialogFn, + this.destroyRef, + expression + ); } changeEntity(entity: BaseData): void { @@ -172,28 +150,12 @@ export class CalculatedFieldDialogComponent extends DialogComponent this.fieldFormGroup.get('type').updateValueAndValidity({onlySelf: true})); if (!this.data.entityId) { this.fieldFormGroup.get('configuration').disable({emitEvent: false}); this.disabledConfiguration = true; } } - - private observeType(): void { - this.fieldFormGroup.get('type').valueChanges.pipe( - pairwise(), - takeUntilDestroyed(this.destroyRef) - ).subscribe(([prevType, nextType]) => { - if (![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(prevType) || - ![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(nextType)) { - this.fieldFormGroup.get('configuration').setValue(({} as CalculatedFieldConfiguration), {emitEvent: false}); - } - }); - } } diff --git a/ui-ngx/src/app/modules/home/models/entity/entity-table-component.models.ts b/ui-ngx/src/app/modules/home/models/entity/entity-table-component.models.ts index e3d0bb19e6..9e2d22f8a9 100644 --- a/ui-ngx/src/app/modules/home/models/entity/entity-table-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/entity/entity-table-component.models.ts @@ -20,18 +20,22 @@ import { SafeHtml } from '@angular/platform-browser'; import { PageLink } from '@shared/models/page/page-link'; import { Timewindow } from '@shared/models/time/time.models'; import { EntitiesDataSource } from '@home/models/datasource/entity-datasource'; -import { ElementRef, EventEmitter, Renderer2, ViewContainerRef } from '@angular/core'; +import { ElementRef, EventEmitter, ViewContainerRef } from '@angular/core'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { EntityAction } from '@home/models/entity/entity-component.models'; import { - CellActionDescriptor, EntityActionTableColumn, EntityColumn, EntityTableColumn, + CellActionDescriptor, + EntityActionTableColumn, + EntityColumn, + EntityTableColumn, EntityTableConfig, GroupActionDescriptor, HeaderActionDescriptor } from '@home/models/entity/entities-table-config.models'; import { ActivatedRoute } from '@angular/router'; +import type { EntityDetailsPanelComponent } from '@home/components/entity/entity-details-panel.component'; export type EntitiesTableAction = 'add'; @@ -64,6 +68,7 @@ export interface IEntitiesTableComponent { paginator: MatPaginator; sort: MatSort; route: ActivatedRoute; + entityDetailsPanel: EntityDetailsPanelComponent; viewContainerRef: ViewContainerRef; addEnabled(): boolean; diff --git a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts index 0c41ddc33d..54e26db1be 100644 --- a/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts @@ -42,8 +42,16 @@ export class CalculatedFieldsTabsComponent extends EntityTabsComponent { + (this.entitiesTableConfig as CalculatedFieldsTableConfig).getTable(); + const entityDetailsPanel = this.entitiesTableConfig.getTable().entityDetailsPanel; + entityDetailsPanel.onToggleEditMode(true); + entityDetailsPanel.selectedTab = 0; + setTimeout(() => { + entityDetailsPanel.detailsForm.get('configuration').setValue({...this.entity.configuration, expression}); + entityDetailsPanel.detailsForm.get('configuration').markAsDirty(); + }); }); }; } diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index fbfb7ae453..57d2380705 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { AfterViewInit, Component, DestroyRef, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; +import { Component, DestroyRef, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -38,7 +38,7 @@ import { BaseData } from '@shared/models/base-data'; multi: true }] }) -export class EntitySelectComponent implements ControlValueAccessor, OnInit, AfterViewInit { +export class EntitySelectComponent implements ControlValueAccessor, OnInit { entitySelectFormGroup: UntypedFormGroup; @@ -70,6 +70,9 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte @Input() filterAllowedEntityTypes: boolean; + @Input() + defaultEntityType: AliasEntityType | EntityType; + @Output() entityChanged = new EventEmitter>(); @@ -81,8 +84,6 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte AliasEntityType.CURRENT_TENANT, AliasEntityType.CURRENT_USER, AliasEntityType.CURRENT_USER_OWNER ]); - private readonly defaultEntityType: EntityType | AliasEntityType = null; - private propagateChange = (v: any) => { }; constructor(private store: Store, @@ -93,15 +94,18 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte const entityTypes = this.entityService.prepareAllowedEntityTypesList(this.allowedEntityTypes, this.useAliasEntityTypes); + + let defaultEntityType: EntityType | AliasEntityType = null + if (entityTypes.length === 1) { this.displayEntityTypeSelect = false; - this.defaultEntityType = entityTypes[0]; + defaultEntityType = entityTypes[0]; } else { this.displayEntityTypeSelect = true; } this.entitySelectFormGroup = this.fb.group({ - entityType: [this.defaultEntityType], + entityType: [defaultEntityType], entityId: [null] }); } @@ -133,9 +137,19 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte if (additionNullUIIDEntityTypes.length > 0) { additionNullUIIDEntityTypes.forEach((entityType) => this.entityTypeNullUUID.add(entityType)); } - } - ngAfterViewInit(): void { + if (this.filterAllowedEntityTypes === false) { + if (this.allowedEntityTypes?.length === 1) { + this.displayEntityTypeSelect = false; + this.entitySelectFormGroup.get('entityType').setValue(this.allowedEntityTypes[0]) + } else { + this.displayEntityTypeSelect = true; + } + } + + if (this.defaultEntityType) { + this.entitySelectFormGroup.get('entityType').setValue(this.defaultEntityType); + } } setDisabledState(isDisabled: boolean): void { From e083aaa4c3691c85c938470eaea7c3910c9bc83a Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 26 Dec 2025 11:34:43 +0200 Subject: [PATCH 5/6] UI: Fixed calculated fields panel dirty state --- .../calculated-fields/calculated-field.component.ts | 3 +-- .../calculated-field-arguments-table.component.ts | 5 +++-- ...alculated-field-geofencing-zone-groups-table.component.ts | 4 ++-- .../metrics/calculated-field-metrics-table.component.ts | 4 ++-- .../components/output/calculated-field-output.component.ts | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) 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 01d7a8de7a..01cc53609d 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 @@ -116,10 +116,9 @@ export class CalculatedFieldComponent extends EntityComponent { this.entityForm.patchValue({ configuration: preparedConfig, debugSettings, entityId, ...value }, {emitEvent: false}); - this.entityForm.get('type').updateValueAndValidity({onlySelf: true}); }); } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts index 9a8395a557..89c87c8e27 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts @@ -239,7 +239,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces } writeValue(argumentsObj: Record): void { - this.argumentsFormArray.clear(); + this.argumentsFormArray.clear({emitEvent: false}); this.populateArgumentsFormArray(argumentsObj); this.updateEntityNameMap(this.argumentsFormArray.value); } @@ -264,7 +264,8 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces }; this.argumentsFormArray.push(this.fb.control(value), { emitEvent: false }); }); - this.argumentsFormArray.updateValueAndValidity(); + this.updateDataSource(this.argumentsFormArray.value); + // this.argumentsFormArray.updateValueAndValidity({emitEvent: false}); } private updateEntityNameMap(values: CalculatedFieldArgumentValue[]): void { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.ts index 228d08fd90..e102033f18 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.ts @@ -214,7 +214,7 @@ export class CalculatedFieldGeofencingZoneGroupsTableComponent implements Contro } writeValue(zonesObj: Record): void { - this.zoneGroupsFormArray.clear(); + this.zoneGroupsFormArray.clear({emitEvent: false}); this.populateZonesFormArray(zonesObj); this.updateEntityNameMap(this.zoneGroupsFormArray.value); } @@ -231,7 +231,7 @@ export class CalculatedFieldGeofencingZoneGroupsTableComponent implements Contro }; this.zoneGroupsFormArray.push(this.fb.control(value), { emitEvent: false }); }); - this.zoneGroupsFormArray.updateValueAndValidity(); + this.updateDataSource(this.zoneGroupsFormArray.value); } private updateEntityNameMap(values: CalculatedFieldGeofencingValue[]): void { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.ts index 21b21d07ac..531f8d1014 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.ts @@ -218,7 +218,7 @@ export class CalculatedFieldMetricsTableComponent implements OnInit, ControlValu } writeValue(metrics: Record): void { - this.metricsFormArray.clear(); + this.metricsFormArray.clear({emitEvent: false}); this.populateZonesFormArray(metrics); } @@ -230,7 +230,7 @@ export class CalculatedFieldMetricsTableComponent implements OnInit, ControlValu }; this.metricsFormArray.push(this.fb.control(value), { emitEvent: false }); }); - this.metricsFormArray.updateValueAndValidity(); + this.updateDataSource(this.metricsFormArray.value); } private getSortValue(metric: CalculatedFieldAggMetricValue, column: string): string { diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts index 00fea861f6..63e3faf131 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts @@ -253,7 +253,7 @@ export class CalculatedFieldOutputComponent implements ControlValueAccessor, Val private updateTimeSeriesTtl(value: boolean) { if (value) { - this.outputForm.get('strategy.useCustomTtl').enable({emitEvent: true}); + this.outputForm.get('strategy.useCustomTtl').enable({emitEvent: false}); } else { this.outputForm.get('strategy.useCustomTtl').disable({emitEvent: false}); this.outputForm.get('strategy.ttl').disable({emitEvent: false}); From 3c5df439ccbbe228ef74c85f1c9984cd323c01ed Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 26 Dec 2025 12:42:23 +0200 Subject: [PATCH 6/6] UI: Fixed minor problem in calculated fields panel --- .../calculated-fields-table-config.ts | 23 ++++++++++++++++--- ...culated-field-arguments-table.component.ts | 3 +-- 2 files changed, 21 insertions(+), 5 deletions(-) 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 5841d9e511..e5e4063c21 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 @@ -35,7 +35,7 @@ import { DestroyRef, Renderer2 } from '@angular/core'; import { EntityDebugSettings } from '@shared/models/entity.models'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; -import { catchError, filter, switchMap, tap } from 'rxjs/operators'; +import { catchError, filter, first, switchMap, tap } from 'rxjs/operators'; import { ArgumentEntityType, ArgumentType, @@ -175,7 +175,8 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig true, - onAction: ($event, entity) => this.openDebugEventsDialog($event, entity), + onAction: ($event, entity) => + this.pageMode ? this.openDebugTab($event, entity) : this.openDebugEventsDialog($event, entity), }, { name: '', @@ -202,7 +203,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig 1) { + table.entityDetailsPanel.matTabGroup.selectedIndex = 1; + } else { + table.entityDetailsPanel.matTabGroup._tabs.changes.pipe( + first() + ).subscribe(() => { + table.entityDetailsPanel.matTabGroup.selectedIndex = 1; + }) + } + } + } + private editCalculatedField($event: Event, calculatedField: CalculatedFieldsTableEntity, isDirty = false): void { $event?.stopPropagation(); this.getCalculatedFieldDialog(calculatedField, 'action.apply', isDirty) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts index 89c87c8e27..983609dfa7 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts @@ -249,7 +249,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces } protected changeIsScriptMode(): void { - this.argumentsFormArray.updateValueAndValidity(); + this.argumentsFormArray.updateValueAndValidity({emitEvent: !this.disable}); } protected isEditButtonShowBadge(argument: CalculatedFieldArgumentValue): boolean { @@ -265,7 +265,6 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces this.argumentsFormArray.push(this.fb.control(value), { emitEvent: false }); }); this.updateDataSource(this.argumentsFormArray.value); - // this.argumentsFormArray.updateValueAndValidity({emitEvent: false}); } private updateEntityNameMap(values: CalculatedFieldArgumentValue[]): void {