Browse Source

Merge pull request #14691 from vvlladd28/improvement/calculated-fields/details-page

Add calculated fields details page
pull/14692/head
Vladyslav Prykhodko 9 months ago
committed by GitHub
parent
commit
9409eff1e9
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts
  2. 94
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field-form.service.ts
  3. 144
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html
  4. 52
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss
  5. 145
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts
  6. 5
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts
  7. 105
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts
  8. 4
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts
  9. 6
      ui-ngx/src/app/modules/home/components/calculated-fields/components/calculated-field-arguments/calculated-field-arguments-table.component.ts
  10. 30
      ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html
  11. 78
      ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts
  12. 10
      ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts
  13. 4
      ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/calculated-field-geofencing-zone-groups-table.component.ts
  14. 4
      ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/geofencing-configuration.component.ts
  15. 4
      ui-ngx/src/app/modules/home/components/calculated-fields/components/metrics/calculated-field-metrics-table.component.ts
  16. 6
      ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts
  17. 6
      ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.ts
  18. 1
      ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts
  19. 4
      ui-ngx/src/app/modules/home/components/entity/debug/entity-debug-settings.service.ts
  20. 10
      ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts
  21. 16
      ui-ngx/src/app/modules/home/components/event/event-table-config.ts
  22. 24
      ui-ngx/src/app/modules/home/components/event/event-table.component.ts
  23. 1
      ui-ngx/src/app/modules/home/dialogs/events-dialog.component.html
  24. 6
      ui-ngx/src/app/modules/home/dialogs/events-dialog.component.ts
  25. 9
      ui-ngx/src/app/modules/home/models/entity/entity-table-component.models.ts
  26. 10
      ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-field-page.module.ts
  27. 84
      ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts
  28. 30
      ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.html
  29. 57
      ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts
  30. 4
      ui-ngx/src/app/modules/home/pages/home-pages.module.ts
  31. 2
      ui-ngx/src/app/shared/components/entity/entity-select.component.html
  32. 41
      ui-ngx/src/app/shared/components/entity/entity-select.component.ts
  33. 6
      ui-ngx/src/app/shared/models/calculated-field.models.ts
  34. 8
      ui-ngx/src/app/shared/models/entity-type.models.ts
  35. 4
      ui-ngx/src/assets/locale/locale.constant-en_US.json

2
ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts

@ -288,7 +288,7 @@ export class AlarmRulesTableConfig extends EntityTableConfig<AlarmRuleTableEntit
debugEventTypes:[DebugEventType.DEBUG_CALCULATED_FIELD],
disabledEventTypes:[EventType.LC_EVENT, EventType.ERROR, EventType.STATS],
defaultEventType: DebugEventType.DEBUG_CALCULATED_FIELD,
debugActionEnabledFn: () => false
debugActionDisabled: true
}
})
.afterClosed()

94
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<CalculatedFieldConfiguration>({} 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<string>,
destroyRef: DestroyRef,
expression?: string,
): Observable<string> {
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);
}
}

144
ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html

@ -0,0 +1,144 @@
<!--
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.
-->
<div class="tb-details-buttons xs:flex xs:flex-col" *ngIf="!standalone">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[class.!hidden]="isEdit || isDetailsPage">
{{ 'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'export')"
[class.!hidden]="isEdit">
{{ 'action.export' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')"
[class.!hidden]="hideDelete() || isEdit">
{{ 'action.delete' | translate }}
</button>
<!-- <div class="flex flex-row xs:flex-col">-->
<!-- <button mat-raised-button-->
<!-- ngxClipboard-->
<!-- (cbOnSuccess)="onAssetProfileIdCopied($event)"-->
<!-- [cbContent]="entity?.id?.id"-->
<!-- [class.!hidden]="isEdit">-->
<!-- <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>-->
<!-- <span translate>asset-profile.copyId</span>-->
<!-- </button>-->
<!-- </div>-->
</div>
<div class="mat-padding flex flex-1 flex-col" [formGroup]="entityForm">
<div class="tb-form-panel no-border no-padding">
<div class="tb-form-panel no-gap">
<div class="tb-form-panel-title mb-4">{{ 'common.general' | translate }}</div>
<div class="flex items-center gap-2">
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label>{{ 'entity-field.title' | translate }}</mat-label>
<input matInput maxlength="255" formControlName="name" required>
@if (entityForm.get('name').errors && entityForm.get('name').touched) {
<mat-error>
@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 }}
}
</mat-error>
}
<mat-hint></mat-hint>
</mat-form-field>
<tb-entity-debug-settings-button
formControlName="debugSettings"
style="margin-bottom: 22px"
[entityType]="EntityType.CALCULATED_FIELD"
[additionalActionConfig]="additionalDebugActionConfig"
/>
</div>
<tb-entity-select formControlName="entityId"
appearance="outline"
[filterAllowedEntityTypes]="false"
[allowedEntityTypes]="calculatedFieldsEntityTypeList"
(entityChanged)="changeEntity($event)">
</tb-entity-select>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-label>{{ 'common.type' | translate }}</mat-label>
<mat-select formControlName="type">
@for (type of fieldTypes; track type) {
<mat-option [value]="type">{{ CalculatedFieldTypeTranslations.get(type).name | translate }}</mat-option>
}
</mat-select>
@if (CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint) {
<mat-hint>{{ CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint | translate }}</mat-hint>
}
</mat-form-field>
</div>
@switch (entityForm.get('type').value) {
@case (CalculatedFieldType.GEOFENCING) {
<tb-geofencing-configuration formControlName="configuration"
[class.tb-form-panel-disabled]="disabledConfiguration"
[entityId]="entityId"
[entityName]="entityName"
[ownerId]="ownerId"
[tenantId]="tenantId"
></tb-geofencing-configuration>
}
@case (CalculatedFieldType.PROPAGATION) {
<tb-propagation-configuration formControlName="configuration"
[class.tb-form-panel-disabled]="disabledConfiguration"
[entityId]="entityId"
[entityName]="entityName"
[tenantId]="tenantId"
[ownerId]="ownerId"
[testScript]="onTestScript.bind(this)"
></tb-propagation-configuration>
}
@case (CalculatedFieldType.RELATED_ENTITIES_AGGREGATION) {
<tb-related-entities-aggregation-component formControlName="configuration"
[class.tb-form-panel-disabled]="disabledConfiguration"
[entityId]="entityId"
[entityName]="entityName"
[tenantId]="tenantId"
[testScript]="onTestScript.bind(this)"
></tb-related-entities-aggregation-component>
}
@case (CalculatedFieldType.ENTITY_AGGREGATION) {
<tb-entity-aggregation-component formControlName="configuration"
[class.tb-form-panel-disabled]="disabledConfiguration"
[entityId]="entityId"
[entityName]="entityName"
[tenantId]="tenantId"
></tb-entity-aggregation-component>
}
@default {
<tb-simple-configuration formControlName="configuration"
[class.tb-form-panel-disabled]="disabledConfiguration"
[entityId]="entityId"
[entityName]="entityName"
[ownerId]="ownerId"
[tenantId]="tenantId"
[isScript]="entityForm.get('type').value === CalculatedFieldType.SCRIPT"
[testScript]="onTestScript.bind(this)"
></tb-simple-configuration>
}
}
</div>
</div>

52
ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss

@ -0,0 +1,52 @@
/**
* 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 ::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;
}
}
}
}
}

145
ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts

@ -0,0 +1,145 @@
///
/// 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, 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 } from '@angular/forms';
import { EntityType } from '@shared/models/entity-type.models';
import { TranslateService } from '@ngx-translate/core';
import {
CalculatedFieldConfiguration,
CalculatedFieldInfo,
calculatedFieldsEntityTypeList,
CalculatedFieldType,
calculatedFieldTypes,
CalculatedFieldTypeTranslations
} from '@shared/models/calculated-field.models';
import { EntityId } from '@shared/models/id/entity-id';
import { BaseData } from '@shared/models/base-data';
import { Observable } from 'rxjs';
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',
templateUrl: './calculated-field.component.html',
styleUrls: ['./calculated-field.component.scss']
})
export class CalculatedFieldComponent extends EntityComponent<CalculatedFieldsTableEntity> {
@Input()
standalone = false;
@Input()
entityName: string;
disabledConfiguration = false;
readonly tenantId = getCurrentAuthUser(this.store).tenantId;
readonly ownerId = new TenantId(getCurrentAuthUser(this.store).tenantId);
readonly EntityType = EntityType;
readonly calculatedFieldsEntityTypeList = calculatedFieldsEntityTypeList;
readonly CalculatedFieldType = CalculatedFieldType;
readonly fieldTypes = calculatedFieldTypes;
readonly CalculatedFieldTypeTranslations = CalculatedFieldTypeTranslations;
private cfFormService = inject(CalculatedFieldFormService);
private destroyRef = inject(DestroyRef);
constructor(protected store: Store<AppState>,
protected translate: TranslateService,
@Inject('entity') protected entityValue: CalculatedFieldInfo,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: CalculatedFieldsTableConfig,
protected fb: FormBuilder,
protected cd: ChangeDetectorRef) {
super(store, fb, entityValue, entitiesTableConfigValue, cd);
}
hideDelete() {
if (this.entitiesTableConfig) {
return !this.entitiesTableConfig.deleteEnabled(this.entity);
} else {
return false;
}
}
additionalDebugActionConfig = {
...this.entitiesTableConfig.additionalDebugActionConfig,
action: () => 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;
}
get entitiesTableConfig(): CalculatedFieldsTableConfig {
return this.entitiesTableConfigValue;
}
changeEntity(entity: BaseData<EntityId>): void {
this.entityName = entity?.name;
}
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 ?? {};
const preparedConfig = this.cfFormService.prepareConfig(configuration);
this.entityForm.patchValue({ type }, {emitEvent: false, onlySelf: true});
setTimeout(() => {
this.entityForm.patchValue({ configuration: preparedConfig, debugSettings, entityId, ...value }, {emitEvent: false});
});
}
onTestScript(expression?: string): Observable<string> {
return this.cfFormService.testScript(
this.entity?.id?.id,
this.entityFormValue(),
this.entitiesTableConfig.getTestScriptDialog.bind(this.entitiesTableConfig),
this.destroyRef,
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});
}
}
}
}

5
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,
@ -72,6 +74,7 @@ import {
exports: [
CalculatedFieldDialogComponent,
CalculatedFieldScriptTestDialogComponent,
CalculatedFieldComponent,
]
})
export class CalculatedFieldsModule {}

105
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';
@ -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,
@ -46,6 +46,7 @@ import {
CalculatedFieldsQuery,
CalculatedFieldType,
CalculatedFieldTypeTranslations,
debugCfActionEnabled,
getCalculatedFieldArgumentsEditorCompleter,
getCalculatedFieldArgumentsHighlights,
PropagationWithExpression,
@ -62,20 +63,26 @@ 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
} 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';
import { CalculatedFieldsTabsComponent } from '@home/pages/calculated-fields/calculated-fields-tabs.component';
type CalculatedFieldsTableEntity = CalculatedField | CalculatedFieldInfo;
export type CalculatedFieldsTableEntity = CalculatedField | CalculatedFieldInfo;
export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFieldsTableEntity> {
readonly tenantId = getCurrentAuthUser(this.store).tenantId;
additionalDebugActionConfig = {
title: this.translate.instant('action.see-debug-events'),
action: (calculatedField: CalculatedFieldsTableEntity) => 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;
@ -93,30 +100,35 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
private importExportService: ImportExportService,
private entityDebugSettingsService: EntityDebugSettingsService,
private utilsService: UtilsService,
private router: Router,
public pageMode = false,
) {
super();
if (this.pageMode) {
this.headerComponent = CalculatedFieldsHeaderComponent;
this.handleRowClick = ($event, entity) => {
this.editCalculatedField($event, entity);
this.rowPointer = true;
return true;
};
this.entityComponent = CalculatedFieldComponent;
this.entityTabsComponent = CalculatedFieldsTabsComponent;
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.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');
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'),
@ -163,7 +175,8 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
name: this.translate.instant('entity-view.events'),
icon: 'mdi:clipboard-text-clock',
isEnabled: () => true,
onAction: ($event, entity) => this.openDebugEventsDialog($event, entity),
onAction: ($event, entity) =>
this.pageMode ? this.openDebugTab($event, entity) : this.openDebugEventsDialog($event, entity),
},
{
name: '',
@ -172,14 +185,16 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
isEnabled: () => 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<PageData<CalculatedFieldsTableEntity>> {
@ -188,7 +203,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
this.calculatedFieldsService.getCalculatedFieldsByEntityId(this.entityId, pageLink);
}
onOpenDebugConfig($event: Event, calculatedField: CalculatedFieldsTableEntity): void {
private onOpenDebugConfig($event: Event, calculatedField: CalculatedFieldsTableEntity): void {
$event?.stopPropagation();
const { debugSettings = {}, id } = calculatedField;
const additionalActionConfig = {
@ -212,6 +227,22 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
}, $event.target as Element);
}
private openDebugTab($event: Event, calculatedField: CalculatedFieldsTableEntity) {
const table = this.getTable();
if (!table.isDetailsOpen) {
table.toggleEntityDetails($event, calculatedField);
if (table.entityDetailsPanel.matTabGroup._tabs.length > 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)
@ -234,7 +265,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
entityId,
entityName,
tenantId: this.tenantId,
ownerId: this.ownerId,
ownerId: this.ownerId ?? {entityType: EntityType.TENANT, id: this.tenantId},
additionalDebugActionConfig: this.additionalDebugActionConfig,
getTestScriptDialogFn: this.getTestScriptDialog.bind(this),
isDirty,
@ -245,17 +276,11 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
.pipe(filter(Boolean));
}
private openDebugEventsDialog($event: Event, calculatedField: CalculatedFieldsTableEntity): void {
private openDebugEventsDialog($event: Event, calculatedField: CalculatedFieldsTableEntity, openCalculatedFieldEdit = true, afterCloseCallback?: (expression: string) => 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<EventsDialogComponent, string>) => {
this.getTestScriptDialog(calculatedField, JSON.parse(event.arguments))
this.getTestScriptDialog(calculatedField, JSON.parse(event.arguments), openCalculatedFieldEdit)
.subscribe(expression => dialogRef.close(expression));
};
@ -270,11 +295,15 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
disabledEventTypes:[EventType.LC_EVENT, EventType.ERROR, EventType.STATS],
defaultEventType: DebugEventType.DEBUG_CALCULATED_FIELD,
onDebugEventSelected,
debugActionEnabledFn
debugActionDisabled: !debugCfActionEnabled(calculatedField)
}
})
.afterClosed()
.subscribe();
.subscribe(value => {
if (afterCloseCallback) {
afterCloseCallback(value)
}
});
}
private exportCalculatedField($event: Event, calculatedField: CalculatedFieldsTableEntity): void {
@ -354,7 +383,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
).subscribe(() => this.updateData());
}
private getTestScriptDialog(calculatedField: CalculatedFieldsTableEntity, argumentsObj?: CalculatedFieldEventArguments, openCalculatedFieldEdit = true, expression?: string): Observable<string> {
getTestScriptDialog(calculatedField: CalculatedFieldsTableEntity, argumentsObj?: CalculatedFieldEventArguments, openCalculatedFieldEdit = true, expression?: string): Observable<string> {
if (
calculatedField.type === CalculatedFieldType.SCRIPT ||
calculatedField.type === CalculatedFieldType.RELATED_ENTITIES_AGGREGATION ||
@ -394,4 +423,22 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
return of(null);
}
}
private openCalculatedField($event: Event, entity: CalculatedFieldsTableEntity) {
$event?.stopPropagation();
const url = this.router.createUrlTree(['calculatedFields', entity.id.id]);
this.router.navigateByUrl(url);
}
private onCFAction(action: EntityAction<CalculatedFieldsTableEntity>): 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;
}
}

4
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();

6
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<string, CalculatedFieldArgument>): void {
this.argumentsFormArray.clear();
this.argumentsFormArray.clear({emitEvent: false});
this.populateArgumentsFormArray(argumentsObj);
this.updateEntityNameMap(this.argumentsFormArray.value);
}
@ -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 {
@ -264,7 +264,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
};
this.argumentsFormArray.push(this.fb.control(value), { emitEvent: false });
});
this.argumentsFormArray.updateValueAndValidity();
this.updateDataSource(this.argumentsFormArray.value);
}
private updateEntityNameMap(values: CalculatedFieldArgumentValue[]): void {

30
ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html

@ -56,29 +56,13 @@
/>
</div>
@if (!data.entityId) {
<div class="flex flex-row gap-4 xs:flex-col" formGroupName="entityId">
<tb-entity-type-select #entityTypeSelect
appearance="outline"
class="flex-1"
showLabel
required
label="{{ 'entity.entity-type' | translate }}"
[filterAllowedEntityTypes]="false"
[allowedEntityTypes]="calculatedFieldsEntityTypeList"
formControlName="entityType">
</tb-entity-type-select>
@if (fieldFormGroup.get('entityId.entityType').value) {
<tb-entity-autocomplete #entityAutocompleteComponent
formControlName="id"
appearance="outline"
class="flex-1"
[placeholder]="'action.set' | translate"
[required]="true"
[entityType]="fieldFormGroup.get('entityId.entityType').value"
(entityChanged)="changeEntity($event)"
/>
}
</div>
<tb-entity-select formControlName="entityId"
appearance="outline"
[filterAllowedEntityTypes]="false"
[allowedEntityTypes]="calculatedFieldsEntityTypeList"
[defaultEntityType]="EntityType.DEVICE_PROFILE"
(entityChanged)="changeEntity($event)">
</tb-entity-select>
}
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-label>{{ 'common.type' | translate }}</mat-label>

78
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<CalculatedFieldDialogComponent, CalculatedField> {
fieldFormGroup = this.fb.group({
name: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]],
entityId: this.fb.group({
entityType: this.fb.control<EntityType | AliasEntityType | null>(EntityType.DEVICE_PROFILE, Validators.required),
id: [null as null | string, Validators.required],
}),
type: [CalculatedFieldType.SIMPLE],
debugSettings: [],
configuration: this.fb.control<CalculatedFieldConfiguration>({} as CalculatedFieldConfiguration),
});
fieldFormGroup: FormGroup;
additionalDebugActionConfig = this.data.value?.id ? {
...this.data.additionalDebugActionConfig,
@ -91,18 +78,16 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
readonly fieldTypes = calculatedFieldTypes;
readonly CalculatedFieldTypeTranslations = CalculatedFieldTypeTranslations;
@ViewChild('entityTypeSelect') entityTypeSelect: EntityTypeSelectComponent;
@ViewChild('entityAutocompleteComponent') entityAutocompleteComponent: EntityAutocompleteComponent;
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: CalculatedFieldDialogData,
protected dialogRef: MatDialogRef<CalculatedFieldDialogComponent, CalculatedField>,
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<CalculatedFi
}
if (!this.data.entityId) {
this.fieldFormGroup.get('entityId.id').valueChanges.pipe(
this.fieldFormGroup.get('entityId').valueChanges.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe((entityId) => {
this.disabledConfiguration = !entityId;
@ -142,24 +127,17 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
});
} else {
this.fieldFormGroup.get('name').markAsTouched();
this.entityTypeSelect?.markAsTouched();
this.entityAutocompleteComponent?.markAsTouched();
}
}
onTestScript(expression?: string): Observable<string> {
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<EntityId>): void {
@ -172,28 +150,12 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
private applyDialogData(): void {
const { configuration = {} as CalculatedFieldConfiguration, type = CalculatedFieldType.SIMPLE, debugSettings = { failuresEnabled: true, allEnabled: true }, entityId = this.data.entityId, ...value } = this.data.value ?? {};
if (configuration.type !== CalculatedFieldType.ALARM) {
if (isDefined(configuration?.output) && !configuration?.output?.strategy) {
configuration.output.strategy = {type: OutputStrategyType.RULE_CHAIN};
}
}
this.fieldFormGroup.patchValue({ configuration, type, debugSettings, entityId, ...value }, {emitEvent: false});
const preparedConfig = this.cfFormService.prepareConfig(configuration);
this.fieldFormGroup.patchValue({ configuration: preparedConfig, type, debugSettings, entityId, ...value }, {emitEvent: false});
setTimeout(() => 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});
}
});
}
}

10
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});

4
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<string, CalculatedFieldGeofencing>): 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 {

4
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 {

4
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<string, CalculatedFieldAggMetric>): 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 {

6
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();
@ -167,7 +167,7 @@ export class CalculatedFieldOutputComponent implements ControlValueAccessor, Val
if (value.type === OutputType.Timeseries && value.strategy?.type === OutputStrategyType.IMMEDIATE && value.strategy?.ttl) {
this.outputForm.get('strategy.useCustomTtl').setValue(true, {emitEvent: false});
}
this.outputForm.get('type').updateValueAndValidity({onlySelf: true});
this.outputForm.get('type').updateValueAndValidity({onlySelf: true, emitEvent: false});
}
registerOnChange(fn: (config: CalculatedFieldOutput | CalculatedFieldSimpleOutput) => void): void {
@ -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});

6
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});
});
}

1
ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts

@ -151,7 +151,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});
});

4
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(

10
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<T extends BaseData<HasId>,
entityTabsChanged = this.entityTabsSubject.asObservable();
protected constructor(protected store: Store<AppState>) {
super(store);
this.authUser = getCurrentAuthUser(store);
protected store: Store<AppState> = inject(Store<AppState>);
protected constructor(...args: unknown[]) {
super();
this.authUser = getCurrentAuthUser(this.store);
}
ngOnInit() {

16
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<Event, TimePageLink> {
private store: Store<AppState>,
public testButtonLabel?: string,
private debugEventSelected?: EventEmitter<EventBody>,
public hideClearEventAction = false) {
public hideClearEventAction = false,
public disableDebugEventAction = false) {
super();
this.loadDataOnInit = false;
this.tableTitle = '';
@ -453,7 +461,7 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
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<Event, TimePageLink> {
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;

24
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
);
}

1
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)"
/>
</div>

6
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<EventType | DebugEventType>;
functionTestButtonLabel?: string;
onDebugEventSelected?: (event: any, dialogRef: MatDialogRef<EventsDialogComponent, string>) => void;
debugActionEnabledFn?: (event: BaseData<HasId>) => boolean;
debugActionDisabled?: boolean;
}
@Component({
@ -54,9 +53,6 @@ export class EventsDialogComponent extends DialogComponent<EventsDialogComponent
}
ngAfterViewInit() {
if (this.data.debugActionEnabledFn && this.eventsTable.entitiesTable.cellActionDescriptors?.length > 0) {
this.eventsTable.entitiesTable.cellActionDescriptors[0].isEnabled = this.data.debugActionEnabledFn;
}
this.eventsTable.entitiesTable.updateData();
}

9
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;

10
ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields.module.ts → 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 { }
export class CalculatedFieldPageModule { }

84
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 } 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<CalculatedFieldsTableConfig> =
(_route: ActivatedRouteSnapshot,
_state: RouterStateSnapshot,
calculatedFieldsService = inject(CalculatedFieldsService),
translate = inject(TranslateService),
dialog = inject(MatDialog),
store = inject(Store<AppState>),
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,
true,
);
};
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<EntityDetailsPageComponent>,
auth: [Authority.TENANT_ADMIN],
title: 'entity.type-calculated-fields',
},
resolve: {
entitiesTableConfig: CalculatedFieldsTableConfigResolver
}
}
]
}
];

30
ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.html

@ -0,0 +1,30 @@
<!--
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.
-->
@if (entity) {
<mat-tab label="{{ 'calculated-fields.events' | translate }}" #eventsTab="matTab">
<tb-event-table [defaultEventType]="DebugEventType.DEBUG_CALCULATED_FIELD"
[disabledEventTypes]="[EventType.LC_EVENT, EventType.ERROR, EventType.STATS]"
[debugEventTypes]="[DebugEventType.DEBUG_CALCULATED_FIELD]"
[active]="eventsTab.isActive"
[tenantId]="entity.tenantId.id"
[entityId]="entity.id"
[disableDebugEventAction]="debugActionDisabled"
(debugEventSelected)="onDebugEventSelected($event)"
></tb-event-table>
</mat-tab>
}

57
ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts

@ -0,0 +1,57 @@
///
/// 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<CalculatedFieldsTableEntity> {
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), false)
.subscribe((expression) => {
(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();
});
});
};
}

4
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,

2
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">
</tb-entity-type-select>
<tb-entity-autocomplete
@ -34,6 +35,7 @@
[required]="required"
[entityType]="modelValue.entityType"
[useEntityDisplayName]="useEntityDisplayName"
(entityChanged)="changeEntity($event)"
formControlName="entityId">
</tb-entity-autocomplete>
</div>

41
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 { 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',
@ -37,7 +38,7 @@ import { MatFormFieldAppearance } from '@angular/material/form-field';
multi: true
}]
})
export class EntitySelectComponent implements ControlValueAccessor, OnInit, AfterViewInit {
export class EntitySelectComponent implements ControlValueAccessor, OnInit {
entitySelectFormGroup: UntypedFormGroup;
@ -66,6 +67,15 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte
@coerceBoolean()
useEntityDisplayName = false;
@Input()
filterAllowedEntityTypes: boolean;
@Input()
defaultEntityType: AliasEntityType | EntityType;
@Output()
entityChanged = new EventEmitter<BaseData<EntityId>>();
displayEntityTypeSelect: boolean;
AliasEntityType = AliasEntityType;
@ -74,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<AppState>,
@ -86,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]
});
}
@ -126,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 {
@ -176,4 +197,8 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte
}
}
}
changeEntity(entity: BaseData<EntityId>): void {
this.entityChanged.emit(entity);
}
}

6
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;

8
ui-ngx/src/app/shared/models/entity-type.models.ts

@ -490,7 +490,7 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti
type: 'entity.type-calculated-field',
typePlural: 'entity.type-calculated-fields',
list: 'calculated-fields.list',
add: 'action.add',
details: 'calculated-fields.calculated-field-details',
noEntities: 'calculated-fields.no-found',
search: 'action.search',
selectedEntities: 'calculated-fields.selected-fields'
@ -658,6 +658,12 @@ export const entityTypeResources = new Map<EntityType, EntityTypeResource<BaseDa
helpLinkId: 'aiModels'
}
],
[
EntityType.CALCULATED_FIELD,
{
helpLinkId: 'calculatedField'
}
],
[
EntityType.API_KEY,
{

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

@ -1086,7 +1086,7 @@
"filter-types": "Audit log types"
},
"debug-settings": {
"label": "Debug Configuration",
"label": "Debug configuration",
"on-failure": "Failures only (24/7)",
"all-messages": "All messages ({{time}})",
"failures": "Failures",
@ -1115,6 +1115,7 @@
"arguments": "Arguments",
"decimals-by-default": "Decimals by default",
"debugging": "Calculated field debugging",
"calculated-field-details": "Calculated field details",
"argument-name": "Argument name",
"name": "Name",
"datasource": "Datasource",
@ -1220,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",

Loading…
Cancel
Save