Browse Source

UI: Add cf details panel tabs refactor edit value

pull/14691/head
Vladyslav_Prykhodko 9 months ago
parent
commit
2ee94a616f
  1. 2
      ui-ngx/src/app/modules/home/components/alarm-rules/alarm-rules-table-config.ts
  2. 259
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.html
  3. 36
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.scss
  4. 52
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.component.ts
  5. 3
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-field.module.ts
  6. 28
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts
  7. 10
      ui-ngx/src/app/modules/home/components/calculated-fields/components/entity-aggregation-configuration/entity-aggregation-component.component.ts
  8. 4
      ui-ngx/src/app/modules/home/components/calculated-fields/components/geofencing-configuration/geofencing-configuration.component.ts
  9. 2
      ui-ngx/src/app/modules/home/components/calculated-fields/components/output/calculated-field-output.component.ts
  10. 6
      ui-ngx/src/app/modules/home/components/calculated-fields/components/propagation-configuration/propagation-configuration.component.ts
  11. 1
      ui-ngx/src/app/modules/home/components/calculated-fields/components/simple-configuration/simple-configuration.component.ts
  12. 10
      ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts
  13. 16
      ui-ngx/src/app/modules/home/components/event/event-table-config.ts
  14. 24
      ui-ngx/src/app/modules/home/components/event/event-table.component.ts
  15. 1
      ui-ngx/src/app/modules/home/dialogs/events-dialog.component.html
  16. 6
      ui-ngx/src/app/modules/home/dialogs/events-dialog.component.ts
  17. 10
      ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-field-page.module.ts
  18. 4
      ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts
  19. 30
      ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.html
  20. 49
      ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-tabs.component.ts
  21. 4
      ui-ngx/src/app/modules/home/pages/home-pages.module.ts
  22. 6
      ui-ngx/src/app/shared/models/calculated-field.models.ts
  23. 1
      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], debugEventTypes:[DebugEventType.DEBUG_CALCULATED_FIELD],
disabledEventTypes:[EventType.LC_EVENT, EventType.ERROR, EventType.STATS], disabledEventTypes:[EventType.LC_EVENT, EventType.ERROR, EventType.STATS],
defaultEventType: DebugEventType.DEBUG_CALCULATED_FIELD, defaultEventType: DebugEventType.DEBUG_CALCULATED_FIELD,
debugActionEnabledFn: () => false debugActionDisabled: true
} }
}) })
.afterClosed() .afterClosed()

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

@ -20,178 +20,125 @@
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')" (click)="onEntityAction($event, 'open')"
[class.!hidden]="isEdit || isDetailsPage"> [class.!hidden]="isEdit || isDetailsPage">
{{'common.open-details-page' | translate }} {{ 'common.open-details-page' | translate }}
</button> </button>
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'export')" (click)="onEntityAction($event, 'export')"
[class.!hidden]="isEdit"> [class.!hidden]="isEdit">
{{'action.export' | translate }} {{ 'action.export' | translate }}
</button> </button>
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')" (click)="onEntityAction($event, 'delete')"
[class.!hidden]="hideDelete() || isEdit"> [class.!hidden]="hideDelete() || isEdit">
{{'action.delete' | translate }} {{ 'action.delete' | translate }}
</button> </button>
<!-- <div class="flex flex-row xs:flex-col">--> <!-- <div class="flex flex-row xs:flex-col">-->
<!-- <button mat-raised-button--> <!-- <button mat-raised-button-->
<!-- ngxClipboard--> <!-- ngxClipboard-->
<!-- (cbOnSuccess)="onAssetProfileIdCopied($event)"--> <!-- (cbOnSuccess)="onAssetProfileIdCopied($event)"-->
<!-- [cbContent]="entity?.id?.id"--> <!-- [cbContent]="entity?.id?.id"-->
<!-- [class.!hidden]="isEdit">--> <!-- [class.!hidden]="isEdit">-->
<!-- <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>--> <!-- <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>-->
<!-- <span translate>asset-profile.copyId</span>--> <!-- <span translate>asset-profile.copyId</span>-->
<!-- </button>--> <!-- </button>-->
<!-- </div>--> <!-- </div>-->
</div> </div>
<div class="mat-padding flex flex-col"> <div class="mat-padding flex flex-1 flex-col" [formGroup]="entityForm">
<!-- <mat-form-field class="mat-block"--> <div class="tb-form-panel no-border no-padding">
<!-- [class.!hidden]="isEdit || !isAssignedToCustomer(entity)--> <div class="tb-form-panel no-gap">
<!-- || entity?.customerIsPublic || assetScope !== 'tenant'">--> <div class="tb-form-panel-title mb-4">{{ 'common.general' | translate }}</div>
<!-- <mat-label translate>asset.assignedToCustomer</mat-label>--> <div class="flex items-center gap-2">
<!-- <input matInput disabled [ngModel]="entity?.customerTitle">--> <mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<!-- </mat-form-field>--> <mat-label>{{ 'entity-field.title' | translate }}</mat-label>
<!-- <div class="tb-small" style="padding-bottom: 10px; padding-left: 2px;"--> <input matInput maxlength="255" formControlName="name" required>
<!-- [class.!hidden]="isEdit || !entity?.customerIsPublic || (assetScope !== 'customer' && assetScope !== 'tenant')">--> @if (entityForm.get('name').errors && entityForm.get('name').touched) {
<!-- {{ 'asset.asset-public' | translate }}--> <mat-error>
<!-- </div>--> @if (entityForm.get('name').hasError('required')) {
<!-- <form [formGroup]="entityForm">--> {{ 'common.hint.title-required' | translate }}
<!-- <fieldset [disabled]="(isLoading$ | async) || !isEdit">--> } @else if (entityForm.get('name').hasError('pattern')) {
<!-- <mat-form-field class="mat-block">--> {{ 'common.hint.title-pattern' | translate }}
<!-- <mat-label translate>asset.name</mat-label>--> } @else if (entityForm.get('name').hasError('maxlength')) {
<!-- <input matInput formControlName="name" required>--> {{ 'common.hint.title-max-length' | translate }}
<!-- <mat-error *ngIf="entityForm.get('name').hasError('required')">--> }
<!-- {{ 'asset.name-required' | translate }}--> </mat-error>
<!-- </mat-error>-->
<!-- <mat-error *ngIf="entityForm.get('name').hasError('maxlength')">-->
<!-- {{ 'asset.name-max-length' | translate }}-->
<!-- </mat-error>-->
<!-- </mat-form-field>-->
<!-- <mat-form-field class="mat-block">-->
<!-- <mat-label translate>asset.label</mat-label>-->
<!-- <input matInput formControlName="label">-->
<!-- <mat-error *ngIf="entityForm.get('label').hasError('maxlength')">-->
<!-- {{ 'asset.label-max-length' | translate }}-->
<!-- </mat-error>-->
<!-- </mat-form-field>-->
<!-- <tb-asset-profile-autocomplete-->
<!-- [selectDefaultProfile]="isAdd"-->
<!-- required-->
<!-- formControlName="assetProfileId"-->
<!-- [showDetailsPageLink]="true"-->
<!-- (assetProfileUpdated)="onAssetProfileUpdated()">-->
<!-- </tb-asset-profile-autocomplete>-->
<!-- <tb-entity-autocomplete-->
<!-- *ngIf="isAdd"-->
<!-- useFullEntityId-->
<!-- formControlName="customerId"-->
<!-- labelText="asset.assign-to-customer"-->
<!-- [entityType]="entityType.CUSTOMER">-->
<!-- </tb-entity-autocomplete>-->
<!-- <div formGroupName="additionalInfo">-->
<!-- <mat-form-field class="mat-block">-->
<!-- <mat-label translate>asset.description</mat-label>-->
<!-- <textarea matInput formControlName="description" rows="2"></textarea>-->
<!-- </mat-form-field>-->
<!-- </div>-->
<!-- </fieldset>-->
<!-- </form>-->
<div mat-dialog-content class="flex-1" [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]="entitiesTableConfig.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-hint></mat-hint>
</mat-form-field> </mat-form-field>
<tb-entity-debug-settings-button
formControlName="debugSettings"
style="margin-bottom: 22px"
[entityType]="EntityType.CALCULATED_FIELD"
[additionalActionConfig]="additionalDebugActionConfig"
/>
</div> </div>
@switch (entityForm.get('type').value) { <tb-entity-select formControlName="entityId"
@case (CalculatedFieldType.GEOFENCING) { appearance="outline"
<tb-geofencing-configuration formControlName="configuration" [filterAllowedEntityTypes]="false"
[class.tb-form-panel-disabled]="disabledConfiguration" [allowedEntityTypes]="calculatedFieldsEntityTypeList"
[entityId]="entityId" (entityChanged)="changeEntity($event)">
[entityName]="entityName" </tb-entity-select>
[ownerId]="ownerId" <mat-form-field appearance="outline" subscriptSizing="dynamic">
[tenantId]="tenantId" <mat-label>{{ 'common.type' | translate }}</mat-label>
></tb-geofencing-configuration> <mat-select formControlName="type">
} @for (type of fieldTypes; track type) {
@case (CalculatedFieldType.PROPAGATION) { <mat-option [value]="type">{{ CalculatedFieldTypeTranslations.get(type).name | translate }}</mat-option>
<tb-propagation-configuration formControlName="configuration" }
[class.tb-form-panel-disabled]="disabledConfiguration" </mat-select>
[entityId]="entityId" @if (CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint) {
[entityName]="entityName" <mat-hint>{{ CalculatedFieldTypeTranslations.get(entityForm.get('type').value).hint | translate }}</mat-hint>
[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>
} }
} </mat-form-field>
</div> </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>
</div> </div>

36
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 * See the License for the specific language governing permissions and
* limitations under the License. * 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;
}
}
}
}
} }

52
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 { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { EntityComponent } from '../../components/entity/entity.component'; import { EntityComponent } from '../../components/entity/entity.component';
import { FormBuilder, FormGroup, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; import { 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 { TranslateService } from '@ngx-translate/core';
import { AssetInfo } from '@app/shared/models/asset.models';
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
import { import {
CalculatedFieldConfiguration, CalculatedFieldConfiguration,
CalculatedFieldInfo, calculatedFieldsEntityTypeList, CalculatedFieldInfo,
CalculatedFieldType, calculatedFieldTypes, CalculatedFieldTypeTranslations, OutputStrategyType calculatedFieldsEntityTypeList,
CalculatedFieldType,
calculatedFieldTypes,
CalculatedFieldTypeTranslations,
OutputStrategyType
} from '@shared/models/calculated-field.models'; } from '@shared/models/calculated-field.models';
import { oneSpaceInsideRegex } from '@shared/models/regex.constants'; import { oneSpaceInsideRegex } from '@shared/models/regex.constants';
import { isDefined } from '@core/utils'; import { isDefined } from '@core/utils';
@ -36,13 +36,14 @@ import { EntityId } from '@shared/models/id/entity-id';
import { pairwise, switchMap } from 'rxjs/operators'; import { pairwise, switchMap } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { BaseData } from '@shared/models/base-data'; 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 { CalculatedFieldsService } from '@core/http/calculated-fields.service';
import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { import {
CalculatedFieldsTableConfig, CalculatedFieldsTableConfig,
CalculatedFieldsTableEntity CalculatedFieldsTableEntity
} from '@home/components/calculated-fields/calculated-fields-table-config'; } from '@home/components/calculated-fields/calculated-fields-table-config';
import { TenantId } from '@shared/models/id/tenant-id';
@Component({ @Component({
selector: 'tb-calculated-field', selector: 'tb-calculated-field',
@ -60,7 +61,7 @@ export class CalculatedFieldComponent extends EntityComponent<CalculatedFieldsTa
disabledConfiguration = false; disabledConfiguration = false;
readonly tenantId = getCurrentAuthUser(this.store).tenantId; readonly tenantId = getCurrentAuthUser(this.store).tenantId;
readonly ownerId: EntityId = {entityType: EntityType.TENANT, id: getCurrentAuthUser(this.store).tenantId}; readonly ownerId = new TenantId(getCurrentAuthUser(this.store).tenantId);
readonly EntityType = EntityType; readonly EntityType = EntityType;
readonly calculatedFieldsEntityTypeList = calculatedFieldsEntityTypeList; readonly calculatedFieldsEntityTypeList = calculatedFieldsEntityTypeList;
readonly CalculatedFieldType = CalculatedFieldType; readonly CalculatedFieldType = CalculatedFieldType;
@ -86,9 +87,17 @@ export class CalculatedFieldComponent extends EntityComponent<CalculatedFieldsTa
} }
} }
isAssignedToCustomer(entity: AssetInfo): boolean { additionalDebugActionConfig = {
return entity && entity.customerId && entity.customerId.id !== NULL_UUID; ...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 { get entityId(): EntityId {
return this.entityForm.get('entityId').value; return this.entityForm.get('entityId').value;
@ -114,9 +123,11 @@ export class CalculatedFieldComponent extends EntityComponent<CalculatedFieldsTa
pairwise(), pairwise(),
takeUntilDestroyed(this.destroyRef) takeUntilDestroyed(this.destroyRef)
).subscribe(([prevType, nextType]) => { ).subscribe(([prevType, nextType]) => {
if (![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(prevType) || if (this.isEditValue) {
![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(nextType)) { if (![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(prevType) ||
form.get('configuration').setValue(({} as CalculatedFieldConfiguration), {emitEvent: false}); ![CalculatedFieldType.SIMPLE, CalculatedFieldType.SCRIPT].includes(nextType)) {
form.get('configuration').setValue(({} as CalculatedFieldConfiguration), {emitEvent: false});
}
} }
}); });
return form; return form;
@ -129,8 +140,11 @@ export class CalculatedFieldComponent extends EntityComponent<CalculatedFieldsTa
configuration.output.strategy = {type: OutputStrategyType.RULE_CHAIN}; configuration.output.strategy = {type: OutputStrategyType.RULE_CHAIN};
} }
} }
this.entityForm.patchValue({ configuration, type, debugSettings, entityId, ...value }, {emitEvent: false}); this.entityForm.patchValue({ type }, {emitEvent: false});
setTimeout(() => this.entityForm.get('type').updateValueAndValidity({onlySelf: true})); setTimeout(() => {
this.entityForm.patchValue({ configuration, debugSettings, entityId, ...value }, {emitEvent: false});
this.entityForm.get('type').updateValueAndValidity({onlySelf: true});
});
if (!entityId) { if (!entityId) {
this.entityForm.get('configuration').disable({emitEvent: false}); this.entityForm.get('configuration').disable({emitEvent: false});
this.disabledConfiguration = true; this.disabledConfiguration = true;
@ -138,7 +152,7 @@ export class CalculatedFieldComponent extends EntityComponent<CalculatedFieldsTa
} }
onTestScript(expression?: string): Observable<string> { onTestScript(expression?: string): Observable<string> {
const calculatedFieldId = this.entityId?.id; const calculatedFieldId = this.entity?.id?.id;
if (calculatedFieldId) { if (calculatedFieldId) {
return this.calculatedFieldsService.getLatestCalculatedFieldDebugEvent(calculatedFieldId, {ignoreLoading: true}) return this.calculatedFieldsService.getLatestCalculatedFieldDebugEvent(calculatedFieldId, {ignoreLoading: true})
.pipe( .pipe(

3
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, CalculatedFieldTestArgumentsComponent,
CalculatedFieldsHeaderComponent, CalculatedFieldsHeaderComponent,
CalculatedFieldsFilterConfigComponent, CalculatedFieldsFilterConfigComponent,
CalculatedFieldComponent CalculatedFieldComponent,
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -74,6 +74,7 @@ import { CalculatedFieldComponent } from '@home/components/calculated-fields/cal
exports: [ exports: [
CalculatedFieldDialogComponent, CalculatedFieldDialogComponent,
CalculatedFieldScriptTestDialogComponent, CalculatedFieldScriptTestDialogComponent,
CalculatedFieldComponent,
] ]
}) })
export class CalculatedFieldsModule {} export class CalculatedFieldsModule {}

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

@ -46,6 +46,7 @@ import {
CalculatedFieldsQuery, CalculatedFieldsQuery,
CalculatedFieldType, CalculatedFieldType,
CalculatedFieldTypeTranslations, CalculatedFieldTypeTranslations,
debugCfActionEnabled,
getCalculatedFieldArgumentsEditorCompleter, getCalculatedFieldArgumentsEditorCompleter,
getCalculatedFieldArgumentsHighlights, getCalculatedFieldArgumentsHighlights,
PropagationWithExpression, PropagationWithExpression,
@ -62,7 +63,7 @@ import { EntityDebugSettingsService } from '@home/components/entity/debug/entity
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { UtilsService } from "@core/services/utils.service"; import { UtilsService } from "@core/services/utils.service";
import { ActionNotificationShow } from "@core/notification/notification.actions"; 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 { EventsDialogComponent, EventsDialogData } from '@home/dialogs/events-dialog.component';
import { import {
CalculatedFieldsHeaderComponent CalculatedFieldsHeaderComponent
@ -70,6 +71,7 @@ import {
import { EntityAction } from '@home/models/entity/entity-component.models'; import { EntityAction } from '@home/models/entity/entity-component.models';
import { CalculatedFieldComponent } from '@home/components/calculated-fields/calculated-field.component'; import { CalculatedFieldComponent } from '@home/components/calculated-fields/calculated-field.component';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { CalculatedFieldsTabsComponent } from '@home/pages/calculated-fields/calculated-fields-tabs.component';
export type CalculatedFieldsTableEntity = CalculatedField | CalculatedFieldInfo; export type CalculatedFieldsTableEntity = CalculatedField | CalculatedFieldInfo;
@ -78,7 +80,9 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
readonly tenantId = getCurrentAuthUser(this.store).tenantId; readonly tenantId = getCurrentAuthUser(this.store).tenantId;
additionalDebugActionConfig = { additionalDebugActionConfig = {
title: this.translate.instant('action.see-debug-events'), 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; calculatedFieldFilterConfig: CalculatedFieldsQuery;
@ -104,6 +108,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
this.headerComponent = CalculatedFieldsHeaderComponent; this.headerComponent = CalculatedFieldsHeaderComponent;
this.entityComponent = CalculatedFieldComponent; this.entityComponent = CalculatedFieldComponent;
this.entityTabsComponent = CalculatedFieldsTabsComponent;
this.rowPointer = true; this.rowPointer = true;
} }
this.tableTitle = this.pageMode ? '' : this.translate.instant('entity.type-calculated-fields'); this.tableTitle = this.pageMode ? '' : this.translate.instant('entity.type-calculated-fields');
@ -114,6 +119,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
this.entitiesFetchFunction = (pageLink: PageLink) => this.fetchCalculatedFields(pageLink); this.entitiesFetchFunction = (pageLink: PageLink) => this.fetchCalculatedFields(pageLink);
this.addEntity = this.getCalculatedFieldDialog.bind(this); this.addEntity = this.getCalculatedFieldDialog.bind(this);
this.saveEntity = (cf) => this.calculatedFieldsService.saveCalculatedField(cf);
this.loadEntity = id => this.calculatedFieldsService.getCalculatedFieldById(id.id); this.loadEntity = id => this.calculatedFieldsService.getCalculatedFieldById(id.id);
this.deleteEntityTitle = (field) => this.translate.instant('calculated-fields.delete-title', {title: field.name}); this.deleteEntityTitle = (field) => this.translate.instant('calculated-fields.delete-title', {title: field.name});
this.deleteEntityContent = () => this.translate.instant('calculated-fields.delete-text'); this.deleteEntityContent = () => this.translate.instant('calculated-fields.delete-text');
@ -253,17 +259,11 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
.pipe(filter(Boolean)); .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(); $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>) => { 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)); .subscribe(expression => dialogRef.close(expression));
}; };
@ -278,11 +278,15 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
disabledEventTypes:[EventType.LC_EVENT, EventType.ERROR, EventType.STATS], disabledEventTypes:[EventType.LC_EVENT, EventType.ERROR, EventType.STATS],
defaultEventType: DebugEventType.DEBUG_CALCULATED_FIELD, defaultEventType: DebugEventType.DEBUG_CALCULATED_FIELD,
onDebugEventSelected, onDebugEventSelected,
debugActionEnabledFn debugActionDisabled: !debugCfActionEnabled(calculatedField)
} }
}) })
.afterClosed() .afterClosed()
.subscribe(); .subscribe(value => {
if (afterCloseCallback) {
afterCloseCallback(value)
}
});
} }
private exportCalculatedField($event: Event, calculatedField: CalculatedFieldsTableEntity): void { private exportCalculatedField($event: Event, calculatedField: CalculatedFieldsTableEntity): void {

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)} interval: {...value.interval, allowOffsetSec: isDefinedAndNotNull(value?.interval?.offsetSec)}
} }
this.entityAggregationConfiguration.patchValue(data, {emitEvent: false}); this.entityAggregationConfiguration.patchValue(data, {emitEvent: false});
this.checkAggIntervalType(this.entityAggregationConfiguration.get('interval.type').value); if (this.entityAggregationConfiguration.enabled) {
this.checkIntervalDuration(this.entityAggregationConfiguration.get('interval.allowOffsetSec').value); this.checkAggIntervalType(this.entityAggregationConfiguration.get('interval.type').value);
this.checkWatermark(this.entityAggregationConfiguration.get('allowWatermark').value); this.checkIntervalDuration(this.entityAggregationConfiguration.get('interval.allowOffsetSec').value);
this.checkProduceIntermediate(); this.checkWatermark(this.entityAggregationConfiguration.get('allowWatermark').value);
this.checkProduceIntermediate();
}
this.updatedOffsetHint(); this.updatedOffsetHint();
setTimeout(() => { setTimeout(() => {
this.entityAggregationConfiguration.get('arguments').updateValueAndValidity({onlySelf: true}); this.entityAggregationConfiguration.get('arguments').updateValueAndValidity({onlySelf: true});

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 { writeValue(config: CalculatedFieldGeofencingConfiguration): void {
this.geofencingConfiguration.patchValue(config, {emitEvent: false}); this.geofencingConfiguration.patchValue(config, {emitEvent: false});
this.checkRelatedEntity(this.geofencingConfiguration.get('zoneGroups').value); 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 { registerOnChange(fn: (config: CalculatedFieldGeofencingConfiguration) => void): void {

2
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)) { for (const propName of Object.keys(changes)) {
const change = changes[propName]; const change = changes[propName];
if (change.currentValue !== change.previousValue) { if (change.currentValue !== change.previousValue) {
if (propName === 'simpleMode') { if (propName === 'simpleMode' && !this.disabled) {
this.updatedFormWithMode(); this.updatedFormWithMode();
if (!change.firstChange) { if (!change.firstChange) {
this.outputForm.updateValueAndValidity(); this.outputForm.updateValueAndValidity();

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 { writeValue(value: PropagationWithExpression): void {
value.expression = value.expression ?? calculatedFieldDefaultScript; value.expression = value.expression ?? calculatedFieldDefaultScript;
this.propagateConfiguration.patchValue(value, {emitEvent: false}); this.propagateConfiguration.patchValue(value, {emitEvent: false});
this.updatedFormWithScript(); if (!this.disabled) {
this.updatedFormWithScript();
}
setTimeout(() => { 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

@ -109,7 +109,6 @@ export class SimpleConfigurationComponent implements ControlValueAccessor, Valid
private propagateChange: (config: SimpeConfiguration) => void = () => { }; private propagateChange: (config: SimpeConfiguration) => void = () => { };
constructor(private fb: FormBuilder) { constructor(private fb: FormBuilder) {
this.updatedFormWithScript();
this.simpleConfiguration.get('output').valueChanges.pipe( this.simpleConfiguration.get('output').valueChanges.pipe(
takeUntilDestroyed(), takeUntilDestroyed(),
).subscribe(() => { ).subscribe(() => {

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 { BaseData, HasId } from '@shared/models/base-data';
import { PageComponent } from '@shared/components/page.component'; 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 { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; 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(); entityTabsChanged = this.entityTabsSubject.asObservable();
protected constructor(protected store: Store<AppState>) { protected store: Store<AppState> = inject(Store<AppState>);
super(store);
this.authUser = getCurrentAuthUser(store); protected constructor(...args: unknown[]) {
super();
this.authUser = getCurrentAuthUser(this.store);
} }
ngOnInit() { ngOnInit() {

16
ui-ngx/src/app/modules/home/components/event/event-table-config.ts

@ -22,7 +22,14 @@ import {
EntityTableColumn, EntityTableColumn,
EntityTableConfig EntityTableConfig
} from '@home/models/entity/entities-table-config.models'; } 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 { TimePageLink } from '@shared/models/page/page-link';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
@ -95,7 +102,8 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
private store: Store<AppState>, private store: Store<AppState>,
public testButtonLabel?: string, public testButtonLabel?: string,
private debugEventSelected?: EventEmitter<EventBody>, private debugEventSelected?: EventEmitter<EventBody>,
public hideClearEventAction = false) { public hideClearEventAction = false,
public disableDebugEventAction = false) {
super(); super();
this.loadDataOnInit = false; this.loadDataOnInit = false;
this.tableTitle = ''; this.tableTitle = '';
@ -453,7 +461,7 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
this.cellActionDescriptors.push({ this.cellActionDescriptors.push({
name: this.translate.instant('rulenode.test-with-this-message', {test: this.translate.instant(this.testButtonLabel)}), name: this.translate.instant('rulenode.test-with-this-message', {test: this.translate.instant(this.testButtonLabel)}),
icon: 'bug_report', 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) => { onAction: ($event, entity) => {
this.debugEventSelected.next(entity.body); this.debugEventSelected.next(entity.body);
} }
@ -464,7 +472,7 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
this.cellActionDescriptors.push({ this.cellActionDescriptors.push({
name: this.translate.instant('calculated-fields.test-with-this-message'), name: this.translate.instant('calculated-fields.test-with-this-message'),
icon: 'bug_report', icon: 'bug_report',
isEnabled: () => true, isEnabled: (event) => !this.disableDebugEventAction && !!(event as DebugEvent).body.arguments,
onAction: (_, entity) => this.debugEventSelected.next(entity.body) onAction: (_, entity) => this.debugEventSelected.next(entity.body)
}); });
break; break;

24
ui-ngx/src/app/modules/home/components/event/event-table.component.ts

@ -17,10 +17,12 @@
import { import {
AfterViewInit, AfterViewInit,
ChangeDetectorRef, ChangeDetectorRef,
Component, EventEmitter, Component,
EventEmitter,
Input, Input,
OnDestroy, OnDestroy,
OnInit, Output, OnInit,
Output,
ViewChild, ViewChild,
ViewContainerRef ViewContainerRef
} from '@angular/core'; } from '@angular/core';
@ -61,6 +63,21 @@ export class EventTableComponent implements OnInit, AfterViewInit, OnDestroy {
@Input() @Input()
hideClearEventAction: boolean = false; 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; activeValue = false;
dirtyValue = false; dirtyValue = false;
entityIdValue: EntityId; entityIdValue: EntityId;
@ -151,7 +168,8 @@ export class EventTableComponent implements OnInit, AfterViewInit, OnDestroy {
this.store, this.store,
this.functionTestButtonLabel, this.functionTestButtonLabel,
this.debugEventSelected, 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" [tenantId]="data.tenantId"
[entityId]="data.entityId" [entityId]="data.entityId"
[functionTestButtonLabel]="data.functionTestButtonLabel ?? ''" [functionTestButtonLabel]="data.functionTestButtonLabel ?? ''"
[disableDebugEventAction]="data.debugActionDisabled"
(debugEventSelected)="this.onDebugEventSelected($event)" (debugEventSelected)="this.onDebugEventSelected($event)"
/> />
</div> </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 { Router } from '@angular/router';
import { DebugEventType, EventType } from '@shared/models/event.models'; import { DebugEventType, EventType } from '@shared/models/event.models';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { BaseData, HasId } from '@shared/models/base-data';
export interface EventsDialogData { export interface EventsDialogData {
title: string; title: string;
@ -34,7 +33,7 @@ export interface EventsDialogData {
disabledEventTypes?: Array<EventType | DebugEventType>; disabledEventTypes?: Array<EventType | DebugEventType>;
functionTestButtonLabel?: string; functionTestButtonLabel?: string;
onDebugEventSelected?: (event: any, dialogRef: MatDialogRef<EventsDialogComponent, string>) => void; onDebugEventSelected?: (event: any, dialogRef: MatDialogRef<EventsDialogComponent, string>) => void;
debugActionEnabledFn?: (event: BaseData<HasId>) => boolean; debugActionDisabled?: boolean;
} }
@Component({ @Component({
@ -54,9 +53,6 @@ export class EventsDialogComponent extends DialogComponent<EventsDialogComponent
} }
ngAfterViewInit() { ngAfterViewInit() {
if (this.data.debugActionEnabledFn && this.eventsTable.entitiesTable.cellActionDescriptors?.length > 0) {
this.eventsTable.entitiesTable.cellActionDescriptors[0].isEnabled = this.data.debugActionEnabledFn;
}
this.eventsTable.entitiesTable.updateData(); this.eventsTable.entitiesTable.updateData();
} }

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 { HomeDialogsModule } from '../../dialogs/home-dialogs.module';
import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { HomeComponentsModule } from '@modules/home/components/home-components.module';
import { CalculatedFieldsRoutingModule } from '@home/pages/calculated-fields/calculated-fields-routing.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({ @NgModule({
declarations: [], declarations: [
CalculatedFieldsTabsComponent
],
imports: [ imports: [
CommonModule, CommonModule,
SharedModule, SharedModule,
HomeComponentsModule, HomeComponentsModule,
HomeDialogsModule, HomeDialogsModule,
CalculatedFieldsRoutingModule CalculatedFieldsRoutingModule,
] ]
}) })
export class CalculatedFieldsModule { } export class CalculatedFieldPageModule { }

4
ui-ngx/src/app/modules/home/pages/calculated-fields/calculated-fields-routing.module.ts

@ -14,7 +14,7 @@
/// limitations under the License. /// 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 { ActivatedRouteSnapshot, ResolveFn, Router, RouterModule, RouterStateSnapshot, Routes } from '@angular/router';
import { Authority } from '@shared/models/authority.enum'; import { Authority } from '@shared/models/authority.enum';
import { MenuId } from '@core/services/menu.models'; import { MenuId } from '@core/services/menu.models';
@ -63,7 +63,7 @@ export const CalculatedFieldsTableConfigResolver: ResolveFn<CalculatedFieldsTabl
entityDebugSettingsService, entityDebugSettingsService,
utilsService, utilsService,
router, router,
false, true,
); );
}; };

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>
}

49
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<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))
.subscribe((expression) => {
});
};
}

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 { DeviceModule } from '@modules/home/pages/device/device.module';
import { AssetModule } from '@modules/home/pages/asset/asset.module'; import { AssetModule } from '@modules/home/pages/asset/asset.module';
import { EntityViewModule } from '@modules/home/pages/entity-view/entity-view.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 { RuleChainModule } from '@modules/home/pages/rulechain/rulechain.module';
import { WidgetLibraryModule } from '@modules/home/pages/widget/widget-library.module'; import { WidgetLibraryModule } from '@modules/home/pages/widget/widget-library.module';
import { DashboardModule } from '@modules/home/pages/dashboard/dashboard.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, EdgeModule,
EntityViewModule, EntityViewModule,
CustomerModule, CustomerModule,
CalculatedFieldsModule, CalculatedFieldPageModule,
RuleChainModule, RuleChainModule,
WidgetLibraryModule, WidgetLibraryModule,
DashboardModule, DashboardModule,

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 { export interface CalculatedFieldArgumentValueBase {
argumentName: string; argumentName: string;
type: ArgumentType; type: ArgumentType;

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

@ -1221,6 +1221,7 @@
"calculated-field-filter-title": "Calculated field filter", "calculated-field-filter-title": "Calculated field filter",
"filter-title": "Filter", "filter-title": "Filter",
"calculated-field-types": "Calculated field types", "calculated-field-types": "Calculated field types",
"events": "Events",
"any-type": "Any type", "any-type": "Any type",
"metrics": { "metrics": {
"metrics": "Metrics", "metrics": "Metrics",

Loading…
Cancel
Save