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],
disabledEventTypes:[EventType.LC_EVENT, EventType.ERROR, EventType.STATS],
defaultEventType: DebugEventType.DEBUG_CALCULATED_FIELD,
debugActionEnabledFn: () => false
debugActionDisabled: true
}
})
.afterClosed()

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

@ -20,178 +20,125 @@
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[class.!hidden]="isEdit || isDetailsPage">
{{'common.open-details-page' | translate }}
{{ 'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'export')"
[class.!hidden]="isEdit">
{{'action.export' | translate }}
{{ 'action.export' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')"
[class.!hidden]="hideDelete() || isEdit">
{{'action.delete' | translate }}
{{ '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 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-col">
<!-- <mat-form-field class="mat-block"-->
<!-- [class.!hidden]="isEdit || !isAssignedToCustomer(entity)-->
<!-- || entity?.customerIsPublic || assetScope !== 'tenant'">-->
<!-- <mat-label translate>asset.assignedToCustomer</mat-label>-->
<!-- <input matInput disabled [ngModel]="entity?.customerTitle">-->
<!-- </mat-form-field>-->
<!-- <div class="tb-small" style="padding-bottom: 10px; padding-left: 2px;"-->
<!-- [class.!hidden]="isEdit || !entity?.customerIsPublic || (assetScope !== 'customer' && assetScope !== 'tenant')">-->
<!-- {{ 'asset.asset-public' | translate }}-->
<!-- </div>-->
<!-- <form [formGroup]="entityForm">-->
<!-- <fieldset [disabled]="(isLoading$ | async) || !isEdit">-->
<!-- <mat-form-field class="mat-block">-->
<!-- <mat-label translate>asset.name</mat-label>-->
<!-- <input matInput formControlName="name" required>-->
<!-- <mat-error *ngIf="entityForm.get('name').hasError('required')">-->
<!-- {{ 'asset.name-required' | translate }}-->
<!-- </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>
<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>
@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>
<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>

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

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

@ -46,6 +46,7 @@ import {
CalculatedFieldsQuery,
CalculatedFieldType,
CalculatedFieldTypeTranslations,
debugCfActionEnabled,
getCalculatedFieldArgumentsEditorCompleter,
getCalculatedFieldArgumentsHighlights,
PropagationWithExpression,
@ -62,7 +63,7 @@ import { EntityDebugSettingsService } from '@home/components/entity/debug/entity
import { DatePipe } from '@angular/common';
import { UtilsService } from "@core/services/utils.service";
import { ActionNotificationShow } from "@core/notification/notification.actions";
import { CalculatedFieldEventBody, DebugEventType, Event as DebugEvent, EventType } from '@shared/models/event.models';
import { CalculatedFieldEventBody, DebugEventType, EventType } from '@shared/models/event.models';
import { EventsDialogComponent, EventsDialogData } from '@home/dialogs/events-dialog.component';
import {
CalculatedFieldsHeaderComponent
@ -70,6 +71,7 @@ import {
import { EntityAction } from '@home/models/entity/entity-component.models';
import { CalculatedFieldComponent } from '@home/components/calculated-fields/calculated-field.component';
import { Router } from '@angular/router';
import { CalculatedFieldsTabsComponent } from '@home/pages/calculated-fields/calculated-fields-tabs.component';
export type CalculatedFieldsTableEntity = CalculatedField | CalculatedFieldInfo;
@ -78,7 +80,9 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
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;
@ -104,6 +108,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
this.headerComponent = CalculatedFieldsHeaderComponent;
this.entityComponent = CalculatedFieldComponent;
this.entityTabsComponent = CalculatedFieldsTabsComponent;
this.rowPointer = true;
}
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.addEntity = this.getCalculatedFieldDialog.bind(this);
this.saveEntity = (cf) => this.calculatedFieldsService.saveCalculatedField(cf);
this.loadEntity = id => this.calculatedFieldsService.getCalculatedFieldById(id.id);
this.deleteEntityTitle = (field) => this.translate.instant('calculated-fields.delete-title', {title: field.name});
this.deleteEntityContent = () => this.translate.instant('calculated-fields.delete-text');
@ -253,17 +259,11 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<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));
};
@ -278,11 +278,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 {

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

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

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

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

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

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

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

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

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;

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

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

Loading…
Cancel
Save