Browse Source

Merge branch 'feature/calculated-fields' of github.com:thingsboard/thingsboard into calculated-fields

pull/12678/head
IrynaMatveieva 2 years ago
parent
commit
9f356e0a80
  1. 21
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts
  2. 22
      ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html
  3. 24
      ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.scss
  4. 4
      ui-ngx/src/app/modules/home/components/calculated-fields/components/debug-dialog/calculated-field-debug-dialog.component.html
  5. 11
      ui-ngx/src/app/modules/home/components/calculated-fields/components/debug-dialog/calculated-field-debug-dialog.component.scss
  6. 30
      ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html
  7. 22
      ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.scss
  8. 8
      ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts
  9. 2
      ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.html
  10. 28
      ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.scss
  11. 1
      ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.ts
  12. 4
      ui-ngx/src/app/modules/home/components/calculated-fields/components/test-arguments/calculated-field-test-arguments.component.html
  13. 29
      ui-ngx/src/app/modules/home/components/calculated-fields/components/test-arguments/calculated-field-test-arguments.component.scss
  14. 1
      ui-ngx/src/app/modules/home/components/calculated-fields/components/test-arguments/calculated-field-test-arguments.component.ts
  15. 7
      ui-ngx/src/app/modules/home/components/calculated-fields/components/test-dialog/calculated-field-script-test-dialog.component.html
  16. 1
      ui-ngx/src/app/modules/home/components/calculated-fields/components/test-dialog/calculated-field-script-test-dialog.component.scss
  17. 81
      ui-ngx/src/app/modules/home/components/calculated-fields/components/test-dialog/calculated-field-script-test-dialog.component.ts
  18. 2
      ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts
  19. 27
      ui-ngx/src/app/modules/home/components/event/event-table-config.ts
  20. 6
      ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts
  21. 8
      ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html
  22. 3
      ui-ngx/src/app/shared/components/js-func.component.ts
  23. 111
      ui-ngx/src/app/shared/models/calculated-field.models.ts

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

@ -37,9 +37,11 @@ import { CalculatedFieldsService } from '@core/http/calculated-fields.service';
import { catchError, filter, switchMap, tap } from 'rxjs/operators';
import {
CalculatedField,
CalculatedFieldEventArguments,
CalculatedFieldDebugDialogData,
CalculatedFieldDialogData,
CalculatedFieldTestScriptDialogData,
getCalculatedFieldArgumentsEditorCompleter,
} from '@shared/models/calculated-field.models';
import {
CalculatedFieldDebugDialogComponent,
@ -58,7 +60,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
readonly tenantId = getCurrentAuthUser(this.store).tenantId;
additionalDebugActionConfig = {
title: this.translate.instant('calculated-fields.see-debug-events'),
action: (calculatedField: CalculatedField) => this.openDebugDialog.call(this, calculatedField),
action: (calculatedField: CalculatedField) => this.openDebugEventsDialog.call(this, calculatedField),
};
constructor(private calculatedFieldsService: CalculatedFieldsService,
@ -118,6 +120,12 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
isEnabled: () => true,
onAction: (event$, entity) => this.exportCalculatedField(event$, entity),
},
{
name: this.translate.instant('entity-view.events'),
icon: 'history',
isEnabled: () => true,
onAction: (_, entity) => this.openDebugEventsDialog(entity),
},
{
name: '',
nameFunction: entity => this.getDebugConfigLabel(entity?.debugSettings),
@ -143,7 +151,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
const { debugSettings = {}, id } = calculatedField;
const additionalActionConfig = {
...this.additionalDebugActionConfig,
action: () => this.openDebugDialog(calculatedField)
action: () => this.openDebugEventsDialog(calculatedField)
};
const { viewContainerRef } = this.getTable();
if ($event) {
@ -205,14 +213,14 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
entityName: this.entityName,
additionalDebugActionConfig: this.additionalDebugActionConfig,
getTestScriptDialogFn: this.getTestScriptDialog.bind(this),
isDirty
isDirty,
},
enterAnimationDuration: isDirty ? 0 : null,
})
.afterClosed();
}
private openDebugDialog(calculatedField: CalculatedField): void {
private openDebugEventsDialog(calculatedField: CalculatedField): void {
this.dialog.open<CalculatedFieldDebugDialogComponent, CalculatedFieldDebugDialogData, null>(CalculatedFieldDebugDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
@ -261,7 +269,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
).subscribe(() => this.updateData());
}
private getTestScriptDialog(calculatedField: CalculatedField, argumentsObj?: Record<string, unknown>, openCalculatedFieldEdit = true): Observable<string> {
private getTestScriptDialog(calculatedField: CalculatedField, argumentsObj?: CalculatedFieldEventArguments, openCalculatedFieldEdit = true): Observable<string> {
const resultArguments = Object.keys(calculatedField.configuration.arguments).reduce((acc, key) => {
acc[key] = isObject(argumentsObj) && argumentsObj.hasOwnProperty(key) ? argumentsObj[key] : '';
return acc;
@ -273,6 +281,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
data: {
arguments: resultArguments,
expression: calculatedField.configuration.expression,
argumentsEditorCompleter: getCalculatedFieldArgumentsEditorCompleter(calculatedField.configuration.arguments),
openCalculatedFieldEdit
}
}).afterClosed()
@ -280,7 +289,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
filter(Boolean),
tap(expression => {
if (openCalculatedFieldEdit) {
this.editCalculatedField({...calculatedField, configuration: {...calculatedField.configuration, expression } }, true)
this.editCalculatedField({ entityId: this.entityId, ...calculatedField, configuration: {...calculatedField.configuration, expression } }, true)
}
}),
);

22
ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html

@ -18,19 +18,19 @@
<div class="flex flex-col gap-3">
<div class="tb-form-table">
<div class="tb-form-table-header">
<div class="tb-form-table-header-cell w-1/6">{{ 'calculated-fields.argument-name' | translate }}</div>
<div class="tb-form-table-header-cell w-1/3">{{ 'calculated-fields.datasource' | translate }}</div>
<div class="tb-form-table-header-cell w-1/6">{{ 'common.type' | translate }}</div>
<div class="tb-form-table-header-cell w-1/6">{{ 'entity.key' | translate }}</div>
<div class="tb-form-table-header-cell w-24 min-w-24"></div>
<div class="argument-name-field tb-form-table-header-cell w-1/6 xs:w-1/3 sm:w-1/4" tbTruncateWithTooltip>{{ 'calculated-fields.argument-name' | translate }}</div>
<div class="tb-form-table-header-cell w-1/3 xs:hidden">{{ 'calculated-fields.datasource' | translate }}</div>
<div class="tb-form-table-header-cell w-1/6 lt-md:hidden">{{ 'common.type' | translate }}</div>
<div class="tb-form-table-header-cell w-1/6 xs:w-1/3">{{ 'entity.key' | translate }}</div>
<div class="tb-form-table-header-cell w-20 min-w-20"></div>
</div>
<div class="tb-form-table-body">
@for (group of argumentsFormArray.controls; track group) {
<div [formGroup]="group" class="tb-form-table-row">
<mat-form-field appearance="outline" class="tb-inline-field w-1/6" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="argument-name-field tb-inline-field w-1/6 xs:w-1/3 sm:w-1/4" subscriptSizing="dynamic">
<input matInput formControlName="argumentName" placeholder="{{ 'action.set' | translate }}">
</mat-form-field>
<section class="flex w-1/3 gap-2">
<section class="datasource-field flex w-1/3 gap-2 xs:hidden">
@if (group.get('refEntityId')?.get('id')?.value) {
<ng-container [formGroup]="group.get('refEntityId')">
<mat-form-field appearance="outline" class="tb-inline-field flex-1" subscriptSizing="dynamic">
@ -63,7 +63,7 @@
}
</section>
<ng-container [formGroup]="group.get('refEntityKey')">
<mat-form-field appearance="outline" class="tb-inline-field w-1/6" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="tb-inline-field w-1/6 lt-md:hidden" subscriptSizing="dynamic">
@if (group.get('refEntityKey').get('type').value; as type) {
<mat-select [value]="type" formControlName="type">
<mat-option [value]="type">
@ -72,15 +72,15 @@
</mat-select>
}
</mat-form-field>
<mat-chip-listbox formControlName="key" class="tb-inline-field w-1/6">
<mat-chip-listbox formControlName="key" class="tb-inline-field w-1/6 xs:w-1/3">
<mat-chip>
<div tbTruncateWithTooltip class="max-w-25">
<div tbTruncateWithTooltip>
{{ group.get('refEntityKey').get('key').value }}
</div>
</mat-chip>
</mat-chip-listbox>
</ng-container>
<div class="tb-form-table-row-cell-buttons flex">
<div class="tb-form-table-row-cell-buttons flex w-20 min-w-20">
<button type="button"
mat-icon-button
#button

24
ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.scss

@ -13,12 +13,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@use '../../../../../../../scss/constants' as constants;
:host {
.tb-form-table-row-cell-buttons {
--mat-badge-legacy-small-size-container-size: 8px;
--mat-badge-small-size-container-overlap-offset: -5px;
--mat-badge-small-size-text-size: 0;
}
.argument-name-field {
@media #{constants.$mat-sm} {
min-width: 25%;
max-width: 25%;
}
@media #{constants.$mat-xs} {
min-width: 33%;
max-width: 33%;
}
}
.datasource-field {
min-width: 33%;
max-width: 33%;
}
}
:host ::ng-deep {
@ -27,4 +45,10 @@
font-size: 14px;
}
}
.mat-mdc-standard-chip {
.mdc-evolution-chip__cell--primary, .mdc-evolution-chip__action--primary, .mdc-evolution-chip__text-label {
overflow: hidden;
}
}
}

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

@ -15,7 +15,7 @@
limitations under the License.
-->
<div class="w-screen max-w-5xl">
<div class="debug-dialog-container flex flex-col xs:h-full">
<mat-toolbar color="primary">
<h2>{{ 'calculated-fields.debugging' | translate}}</h2>
<span class="flex-1"></span>
@ -25,7 +25,7 @@
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<div mat-dialog-content class="tb-form-panel stroked debug-dialog-content">
<div mat-dialog-content class="tb-form-panel stroked debug-dialog-content xs:flex-1">
<tb-event-table
[tenantId]="data.tenantId"
[debugEventTypes]="[debugEventTypes.DEBUG_CALCULATED_FIELD]"

11
ui-ngx/src/app/modules/home/components/calculated-fields/components/debug-dialog/calculated-field-debug-dialog.component.scss

@ -14,8 +14,13 @@
* limitations under the License.
*/
:host {
.debug-dialog-content {
height: 65vh;
border-radius: 0;
.debug-dialog-container {
width: 1080px;
max-width: 100%;
.debug-dialog-content {
height: 65vh;
border-radius: 0;
}
}
}

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

@ -15,7 +15,7 @@
limitations under the License.
-->
<div [formGroup]="fieldFormGroup" class="h-full w-screen min-w-80 max-w-4xl">
<div [formGroup]="fieldFormGroup" class="dialog-container flex size-full max-w-4xl flex-col">
<mat-toolbar color="primary">
<h2>{{ 'entity.type-calculated-field' | translate}}</h2>
<span class="flex-1"></span>
@ -26,7 +26,7 @@
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<div mat-dialog-content>
<div mat-dialog-content class="flex-1">
<div class="tb-form-panel no-border no-padding">
<div class="tb-form-panel">
<div class="tb-form-panel-title">{{ 'common.general' | translate }}</div>
@ -99,13 +99,24 @@
[functionArgs]="functionArgs$ | async"
[disableUndefinedCheck]="true"
[scriptLanguage]="ScriptLanguage.TBEL"
[editorCompleter]="argumentsEditorCompleter$ | async"
helpId="calculated-field/expression_fn"
/>
>
<button toolbarSuffixButton
mat-icon-button
matTooltip="{{ 'common.test-function' | translate }}"
matTooltipPosition="above"
class="tb-mat-32"
[disabled]="configFormGroup.get('expressionSCRIPT').invalid || configFormGroup.get('arguments').invalid"
(click)="onTestScript()">
<mat-icon class="material-icons" color="primary">bug_report</mat-icon>
</button>
</tb-js-func>
<div>
<button mat-button mat-raised-button color="primary"
type="button"
(click)="onTestScript()"
[disabled]="configFormGroup.get('expressionSCRIPT').invalid || configFormGroup.get('arguments').invalid ">
[disabled]="configFormGroup.get('expressionSCRIPT').invalid || configFormGroup.get('arguments').invalid">
{{ 'common.test-function' | translate }}
</button>
</div>
@ -122,18 +133,17 @@
}
</mat-select>
</mat-form-field>
@if (outputFormGroup.get('type').value === OutputType.Attribute) {
@if (outputFormGroup.get('type').value === OutputType.Attribute
&& (data.entityId.entityType === EntityType.DEVICE || data.entityId.entityType === EntityType.DEVICE_PROFILE)) {
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label>{{ 'calculated-fields.attribute-scope' | translate }}</mat-label>
<mat-select formControlName="scope" class="w-full">
<mat-option [value]="AttributeScope.SERVER_SCOPE">
{{ 'calculated-fields.server-attributes' | translate }}
</mat-option>
@if (data.entityId.entityType === EntityType.DEVICE) {
<mat-option [value]="AttributeScope.SHARED_SCOPE">
{{ 'calculated-fields.shared-attributes' | translate }}
</mat-option>
}
<mat-option [value]="AttributeScope.SHARED_SCOPE">
{{ 'calculated-fields.shared-attributes' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
}

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

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
.dialog-container {
width: 869px;
max-width: 100%;
}
}

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

@ -27,6 +27,7 @@ import {
CalculatedFieldDialogData,
CalculatedFieldType,
CalculatedFieldTypeTranslations,
getCalculatedFieldArgumentsEditorCompleter,
OutputType,
OutputTypeTranslations
} from '@shared/models/calculated-field.models';
@ -40,6 +41,7 @@ import { ScriptLanguage } from '@shared/models/rule-node.models';
@Component({
selector: 'tb-calculated-field-dialog',
templateUrl: './calculated-field-dialog.component.html',
styleUrls: ['./calculated-field-dialog.component.scss'],
})
export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFieldDialogComponent, CalculatedField> implements AfterViewInit {
@ -65,6 +67,12 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
map(argumentsObj => Object.keys(argumentsObj))
);
argumentsEditorCompleter$ = this.configFormGroup.get('arguments').valueChanges
.pipe(
startWith(this.data.value?.configuration?.arguments ?? {}),
map(argumentsObj => getCalculatedFieldArgumentsEditorCompleter(argumentsObj))
);
additionalDebugActionConfig = this.data.value?.id ? {
...this.data.additionalDebugActionConfig,
action: () => this.data.additionalDebugActionConfig.action({ id: this.data.value.id, ...this.fromGroupValue }),

2
ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<div class="w-screen max-w-xl" [formGroup]="argumentFormGroup">
<div class="w-full max-w-xl" [formGroup]="argumentFormGroup">
<div class="tb-form-panel no-border no-padding mb-2">
<div class="tb-form-panel-title">{{ 'calculated-fields.argument-settings' | translate }}</div>
<div class="tb-form-panel no-border no-padding">

28
ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.scss

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2024 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.
*/
@use '../../../../../../../scss/constants' as constants;
:host {
display: flex;
width: 520px;
max-width: 100%;
.fixed-title-width {
@media #{constants.$mat-lt-sm} {
min-width: 120px;
}
}
}

1
ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.ts

@ -42,6 +42,7 @@ import { MINUTE } from '@shared/models/time/time.models';
@Component({
selector: 'tb-calculated-field-argument-panel',
templateUrl: './calculated-field-argument-panel.component.html',
styleUrls: ['./calculated-field-argument-panel.component.scss']
})
export class CalculatedFieldArgumentPanelComponent implements OnInit {

4
ui-ngx/src/app/modules/home/components/calculated-fields/components/test-arguments/calculated-field-test-arguments.component.html

@ -19,7 +19,7 @@
<div>{{ 'calculated-fields.arguments' | translate }}</div>
<div class="tb-form-table">
<div class="tb-form-table-header">
<div class="tb-form-table-header-cell w-1/4">{{ 'calculated-fields.argument-name' | translate }}</div>
<div class="tb-form-table-header-cell w-1/4" tbTruncateWithTooltip>{{ 'calculated-fields.argument-name' | translate }}</div>
<div class="tb-form-table-header-cell flex-1">{{ 'common.value' | translate }}</div>
</div>
<div class="tb-form-table-body">
@ -28,7 +28,7 @@
<mat-form-field appearance="outline" class="tb-inline-field w-1/4" subscriptSizing="dynamic">
<input matInput formControlName="argumentName" placeholder="{{ 'action.set' | translate }}">
</mat-form-field>
<tb-value-input class="flex-1" [required]="false" formControlName="value"/>
<tb-value-input class="argument-value flex-1" [required]="false" [shortBooleanField]="true" formControlName="value"/>
</div>
}
</div>

29
ui-ngx/src/app/modules/home/components/calculated-fields/components/test-arguments/calculated-field-test-arguments.component.scss

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2024 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.
*/
@use '../../../../../../../scss/constants' as constants;
:host::ng-deep {
.tb-form-table-row {
.argument-value {
.tb-value-type.row {
@media #{constants.$mat-lt-sm} {
width: 100px;
min-width: 100px;
}
}
}
}
}

1
ui-ngx/src/app/modules/home/components/calculated-fields/components/test-arguments/calculated-field-test-arguments.component.ts

@ -30,6 +30,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'tb-calculated-field-test-arguments',
templateUrl: './calculated-field-test-arguments.component.html',
styleUrls: ['./calculated-field-test-arguments.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,

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

@ -15,7 +15,7 @@
limitations under the License.
-->
<form class="test-dialog-container size-full" [formGroup]="calculatedFieldScriptTestFormGroup">
<form #testScriptContainer class="test-dialog-container size-full" [formGroup]="calculatedFieldScriptTestFormGroup">
<mat-toolbar class="flex justify-between" color="primary">
<h2>{{ 'calculated-fields.test-script-function' | translate }} ({{ 'api-usage.tbel' | translate }})</h2>
<button mat-icon-button
@ -26,7 +26,7 @@
</mat-toolbar>
<div mat-dialog-content class="relative">
<div class="tb-absolute-fill">
<div class="tb-fullscreen-panel flex size-full flex-row">
<div class="tb-fullscreen-panel flex size-full flex-row lt-md:flex-col">
<div #leftPanel class="test-block-content overflow-hidden">
<div class="relative size-full min-w-64">
<div class="block-label-container left">
@ -40,6 +40,7 @@
[disableUndefinedCheck]="true"
[fillHeight]="true"
[scriptLanguage]="ScriptLanguage.TBEL"
[editorCompleter]="data.argumentsEditorCompleter"
resultType="object"
helpId="calculated-field/test-expression_fn"
/>
@ -47,7 +48,7 @@
</div>
<div #rightPanel>
<div #topRightPanel class="test-block-content">
<div class="relative flex size-full min-w-96 gap-2">
<div class="relative flex size-full min-w-80 gap-2">
<div class="block-label-container right-top">
<span class="block-label">{{ 'calculated-fields.arguments' | translate }}</span>
</div>

1
ui-ngx/src/app/modules/home/components/calculated-fields/components/test-dialog/calculated-field-script-test-dialog.component.scss

@ -26,6 +26,7 @@
padding-top: 5px;
padding-left: 5px;
border: 1px solid #c0c0c0;
overflow: scroll;
}
.block-label-container {

81
ui-ngx/src/app/modules/home/components/calculated-fields/components/test-dialog/calculated-field-script-test-dialog.component.ts

@ -20,6 +20,7 @@ import {
DestroyRef,
ElementRef,
Inject,
OnDestroy,
ViewChild,
} from '@angular/core';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
@ -45,12 +46,13 @@ import { CalculatedFieldTestScriptDialogData } from '@shared/models/calculated-f
styleUrls: ['./calculated-field-script-test-dialog.component.scss'],
})
export class CalculatedFieldScriptTestDialogComponent extends DialogComponent<CalculatedFieldScriptTestDialogComponent,
string> implements AfterViewInit {
string> implements AfterViewInit, OnDestroy {
@ViewChild('leftPanel', {static: true}) leftPanelElmRef: ElementRef<HTMLElement>;
@ViewChild('rightPanel', {static: true}) rightPanelElmRef: ElementRef<HTMLElement>;
@ViewChild('topRightPanel', {static: true}) topRightPanelElmRef: ElementRef<HTMLElement>;
@ViewChild('bottomRightPanel', {static: true}) bottomRightPanelElmRef: ElementRef<HTMLElement>;
@ViewChild('testScriptContainer', {static: true}) testScriptContainer: ElementRef<HTMLElement>;
@ViewChild('expressionContent', {static: true}) expressionContent: JsonContentComponent;
@ -64,6 +66,9 @@ export class CalculatedFieldScriptTestDialogComponent extends DialogComponent<Ca
readonly ScriptLanguage = ScriptLanguage;
readonly functionArgs = Object.keys(this.data.arguments);
private testScriptResize: ResizeObserver;
private splitObjects: SplitObject[] = [];
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: CalculatedFieldTestScriptDialogData,
@ -80,12 +85,12 @@ export class CalculatedFieldScriptTestDialogComponent extends DialogComponent<Ca
}
ngAfterViewInit(): void {
this.initSplitLayout(
this.leftPanelElmRef.nativeElement,
this.rightPanelElmRef.nativeElement,
this.topRightPanelElmRef.nativeElement,
this.bottomRightPanelElmRef.nativeElement
);
this.observeResize();
}
ngOnDestroy(): void {
super.ngOnDestroy();
this.testScriptResize.disconnect();
}
cancel(): void {
@ -140,18 +145,58 @@ export class CalculatedFieldScriptTestDialogComponent extends DialogComponent<Ca
return !this.calculatedFieldScriptTestFormGroup.get('expression').invalid;
}
private initSplitLayout(leftPanel, rightPanel, topRightPanel, bottomRightPanel): void {
Split([leftPanel, rightPanel], {
sizes: [50, 50],
gutterSize: 8,
cursor: 'col-resize'
private observeResize(): void {
this.testScriptResize = new ResizeObserver(() => {
this.updateSizes();
});
Split([topRightPanel, bottomRightPanel], {
sizes: [50, 50],
gutterSize: 8,
cursor: 'row-resize',
direction: 'vertical'
});
this.testScriptResize.observe(this.testScriptContainer.nativeElement);
}
private updateSizes(): void {
this.initSplitLayout(this.testScriptContainer.nativeElement.clientWidth <= 960);
}
private initSplitLayout(smallMode = false): void {
const [leftPanel, rightPanel, topRightPanel, bottomRightPanel] = [
this.leftPanelElmRef.nativeElement,
this.rightPanelElmRef.nativeElement,
this.topRightPanelElmRef.nativeElement,
this.bottomRightPanelElmRef.nativeElement
] as unknown as string[];
this.splitObjects.forEach(obj => obj.destroy());
this.splitObjects = [];
if (smallMode) {
this.splitObjects.push(
Split([leftPanel, rightPanel], {
sizes: [33, 67],
gutterSize: 8,
cursor: 'row-resize',
direction: 'vertical'
}),
Split([topRightPanel, bottomRightPanel], {
sizes: [50, 50],
gutterSize: 8,
cursor: 'row-resize',
direction: 'vertical'
}),
);
} else {
this.splitObjects.push(
Split([leftPanel, rightPanel], {
sizes: [50, 50],
gutterSize: 8,
cursor: 'col-resize'
}),
Split([topRightPanel, bottomRightPanel], {
sizes: [50, 50],
gutterSize: 8,
cursor: 'row-resize',
direction: 'vertical'
})
);
}
}
}

2
ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts

@ -689,7 +689,7 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa
}
cellTooltip(entity: BaseData<HasId>, column: EntityColumn<BaseData<HasId>>, row: number) {
if (column instanceof EntityTableColumn) {
if (column instanceof EntityTableColumn || column instanceof EntityLinkTableColumn) {
const col = this.entitiesTableConfig.columns.indexOf(column);
const index = row * this.entitiesTableConfig.columns.length + col;
let res = this.cellTooltipCache[index];

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

@ -18,6 +18,7 @@ import {
CellActionDescriptorType,
DateEntityTableColumn,
EntityActionTableColumn,
EntityLinkTableColumn,
EntityTableColumn,
EntityTableConfig
} from '@home/models/entity/entities-table-config.models';
@ -29,7 +30,7 @@ import { MatDialog } from '@angular/material/dialog';
import { EntityId } from '@shared/models/id/entity-id';
import { EventService } from '@app/core/http/event.service';
import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component';
import { EntityTypeResource } from '@shared/models/entity-type.models';
import { EntityType, EntityTypeResource } from '@shared/models/entity-type.models';
import { fromEvent, Observable } from 'rxjs';
import { PageData } from '@shared/models/page/page-data';
import { Direction } from '@shared/models/page/sort-order';
@ -39,7 +40,7 @@ import {
EventContentDialogComponent,
EventContentDialogData
} from '@home/components/event/event-content-dialog.component';
import { isEqual, sortObjectKeys } from '@core/utils';
import { getEntityDetailsPageURL, isEqual, sortObjectKeys } from '@core/utils';
import { DAY, historyInterval, MINUTE } from '@shared/models/time/time.models';
import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { ChangeDetectorRef, EventEmitter, Injector, StaticProvider, ViewContainerRef } from '@angular/core';
@ -359,13 +360,13 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
this.columns[0].width = '80px';
this.columns[1].width = '100px';
this.columns.push(
new EntityTableColumn<Event>('entityId', 'event.entity-id', '85px',
new EntityLinkTableColumn<Event>('entityId', 'event.entity-id', '100px',
(entity) => `<span style="display: inline-block; width: 9ch">${entity.body.entityId.substring(0, 8)}…</span>`,
() => ({padding: '0 12px 0 0'}),
(entity) => getEntityDetailsPageURL(entity.body.entityId, entity.body.entityType as EntityType),
false,
() => ({padding: '0 12px 0 0'}),
() => undefined,
false,
() => ({padding: '0 12px 0 0'}),
(entity) => entity.body.entityId,
{
name: this.translate.instant('event.copy-entity-id'),
icon: 'content_paste',
@ -379,12 +380,12 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
type: CellActionDescriptorType.COPY_BUTTON
}
),
new EntityTableColumn<Event>('messageId', 'event.message-id', '85px',
(entity) => `<span style="display: inline-block; width: 9ch">${entity.body.msgId?.substring(0, 8)}…</span>`,
new EntityTableColumn<Event>('messageId', 'event.message-id', '100px',
(entity) => entity.body.msgId ? `<span style="display: inline-block; width: 9ch">${entity.body.msgId?.substring(0, 8)}…</span>` : '-',
() => ({padding: '0 12px 0 0'}),
false,
() => ({padding: '0 12px 0 0'}),
() => undefined,
(entity) => entity.body.msgId,
false,
{
name: this.translate.instant('event.copy-message-id'),
@ -394,13 +395,13 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
'font-size': '16px',
color: 'rgba(0,0,0,.87)'
},
isEnabled: () => true,
onAction: ($event, entity) => entity.body.msgId,
isEnabled: (entity) => !!entity.body.msgId,
onAction: (_, entity) => entity.body.msgId,
type: CellActionDescriptorType.COPY_BUTTON
}
),
new EntityTableColumn<Event>('messageType', 'event.message-type', '100px',
(entity) => entity.body.msgType,
(entity) => entity.body.msgType ?? '-',
() => ({padding: '0 12px 0 0'}),
false
),
@ -540,6 +541,8 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
{key: 'entityId', title: 'event.entity-id'},
{key: 'messageId', title: 'event.message-id'},
{key: 'messageType', title: 'event.message-type'},
{key: 'arguments', title: 'event.arguments'},
{key: 'result', title: 'event.result'},
{key: 'isError', title: 'event.error'},
{key: 'errorStr', title: 'event.error'}
);

6
ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts

@ -121,7 +121,11 @@ export class EntityLinkTableColumn<T extends BaseData<HasId>> extends BaseEntity
public width: string = '0px',
public cellContentFunction: CellContentFunction<T> = (entity, property) => entity[property] ? entity[property] : '',
public entityURL: (entity) => string,
public sortable: boolean = true) {
public sortable: boolean = true,
public cellStyleFunction: CellStyleFunction<T> = () => ({}),
public headerCellStyleFunction: HeaderCellStyleFunction<T> = () => ({}),
public cellTooltipFunction: CellTooltipFunction<T> = () => undefined,
public actionCell: CellActionDescriptor<T> = null) {
super('link', key, title, width, sortable);
}
}

8
ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html

@ -40,6 +40,10 @@
</div>
</div>
</mat-tab>
<mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN && !isEdit"
label="{{ 'entity.type-calculated-fields' | translate }}" #calculatedFieldsTab="matTab">
<tb-calculated-fields-table [active]="calculatedFieldsTab.isActive" [entityId]="entity.id" [entityName]="entity.name"/>
</mat-tab>
<mat-tab *ngIf="entity" #alarmRules="matTab"
label="{{'device-profile.alarm-rules-with-count' | translate:
{count: this.detailsForm.get('profileData.alarms').value?.length ? this.detailsForm.get('profileData.alarms').value.length : 0}
@ -69,10 +73,6 @@
</div>
</div>
</mat-tab>
<mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN && !isEdit"
label="{{ 'entity.type-calculated-fields' | translate }}" #calculatedFieldsTab="matTab">
<tb-calculated-fields-table [active]="calculatedFieldsTab.isActive" [entityId]="entity.id" [entityName]="entity.name"/>
</mat-tab>
<mat-tab *ngIf="entity && !isEdit" #auditLogsTab="matTab"
label="{{ 'audit-log.audit-logs' | translate }}">
<tb-audit-log-table detailsMode="true" [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id"></tb-audit-log-table>

3
ui-ngx/src/app/shared/components/js-func.component.ts

@ -184,6 +184,9 @@ export class JsFuncComponent implements OnInit, OnChanges, OnDestroy, ControlVal
this.updateFunctionArgsString();
this.updateFunctionLabel();
}
if (changes.editorCompleter) {
this.updateCompleters();
}
}
ngOnInit(): void {

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

@ -27,6 +27,7 @@ import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
import { EntityType } from '@shared/models/entity-type.models';
import { AliasFilterType } from '@shared/models/alias.models';
import { Observable } from 'rxjs';
import { TbEditorCompleter } from '@shared/models/ace/completion.models';
export interface CalculatedField extends Omit<BaseData<CalculatedFieldId>, 'label'>, HasVersion, HasTenantId, ExportableEntity<CalculatedFieldId> {
debugSettings?: EntityDebugSettings;
@ -153,6 +154,7 @@ export interface CalculatedFieldTestScriptInputParams {
}
export interface CalculatedFieldTestScriptDialogData extends CalculatedFieldTestScriptInputParams {
argumentsEditorCompleter: TbEditorCompleter
openCalculatedFieldEdit?: boolean;
}
@ -186,3 +188,112 @@ export const getCalculatedFieldCurrentEntityFilter = (entityName: string, entity
};
}
}
export interface CalculatedFieldAttributeArgumentValue<ValueType = unknown> {
ts: number;
value: ValueType;
}
export interface CalculatedFieldLatestTelemetryArgumentValue<ValueType = unknown> {
ts: number;
value: ValueType;
}
export interface CalculatedFieldRollingTelemetryArgumentValue<ValueType = unknown> {
timewindow: { startTs: number; endTs: number; limit: number };
values: CalculatedFieldSingleArgumentValue<ValueType>[];
}
export type CalculatedFieldSingleArgumentValue<ValueType = unknown> = CalculatedFieldAttributeArgumentValue<ValueType> & CalculatedFieldLatestTelemetryArgumentValue<ValueType>;
export type CalculatedFieldArgumentEventValue<ValueType = unknown> = CalculatedFieldAttributeArgumentValue<ValueType> | CalculatedFieldLatestTelemetryArgumentValue<ValueType> | CalculatedFieldRollingTelemetryArgumentValue<ValueType>;
export type CalculatedFieldEventArguments<ValueType = unknown> = Record<string, CalculatedFieldArgumentEventValue<ValueType>>;
export const CalculatedFieldLatestTelemetryArgumentAutocomplete = {
meta: 'object',
type: '{ ts: number; value: any; }',
description: 'Calculated field latest telemetry value argument.',
children: {
ts: {
meta: 'number',
type: 'number',
description: 'Time stamp',
},
value: {
meta: 'any',
type: 'any',
description: 'Value',
}
},
};
export const CalculatedFieldAttributeValueArgumentAutocomplete = {
meta: 'object',
type: '{ ts: number; value: any; }',
description: 'Calculated field attribute value argument.',
children: {
ts: {
meta: 'number',
type: 'number',
description: 'Time stamp',
},
value: {
meta: 'any',
type: 'any',
description: 'Value',
}
},
};
export const CalculatedFieldRollingValueArgumentAutocomplete = {
meta: 'object',
type: '{ values: { ts: number; value: any; }[]; timewindow: { startTs: number; endTs: number; limit: number } }; }',
description: 'Calculated field rolling value argument.',
children: {
values: {
meta: 'array',
type: '{ ts: number; value: any; }[]',
description: 'Values array',
},
timewindow: {
meta: 'object',
type: '{ startTs: number; endTs: number; limit: number }',
description: 'Time window configuration',
children: {
startTs: {
meta: 'number',
type: 'number',
description: 'Start time stamp',
},
endTs: {
meta: 'number',
type: 'number',
description: 'End time stamp',
},
limit: {
meta: 'number',
type: 'number',
description: 'Limit',
}
}
}
},
};
export const getCalculatedFieldArgumentsEditorCompleter = (argumentsObj: Record<string, CalculatedFieldArgument>): TbEditorCompleter => {
return new TbEditorCompleter(Object.keys(argumentsObj).reduce((acc, key) => {
switch (argumentsObj[key].refEntityKey.type) {
case ArgumentType.Attribute:
acc[key] = CalculatedFieldAttributeValueArgumentAutocomplete;
break;
case ArgumentType.LatestTelemetry:
acc[key] = CalculatedFieldLatestTelemetryArgumentAutocomplete;
break;
case ArgumentType.Rolling:
acc[key] = CalculatedFieldRollingValueArgumentAutocomplete;
break;
}
return acc;
}, {}))
}

Loading…
Cancel
Save