Merge pull request #12820 from maxunbearable/adjustments/calculated-fields-04-03
Calculated field adjustments
This commit is contained in:
commit
e878fb7fe2
@ -14,7 +14,11 @@
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
import { EntityTableColumn, EntityTableConfig } from '@home/models/entity/entities-table-config.models';
|
import {
|
||||||
|
DateEntityTableColumn,
|
||||||
|
EntityTableColumn,
|
||||||
|
EntityTableConfig
|
||||||
|
} from '@home/models/entity/entities-table-config.models';
|
||||||
import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models';
|
import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Direction } from '@shared/models/page/sort-order';
|
import { Direction } from '@shared/models/page/sort-order';
|
||||||
@ -45,6 +49,7 @@ import {
|
|||||||
getCalculatedFieldArgumentsEditorCompleter,
|
getCalculatedFieldArgumentsEditorCompleter,
|
||||||
getCalculatedFieldArgumentsHighlights,
|
getCalculatedFieldArgumentsHighlights,
|
||||||
CalculatedFieldTypeTranslations,
|
CalculatedFieldTypeTranslations,
|
||||||
|
CalculatedFieldType,
|
||||||
} from '@shared/models/calculated-field.models';
|
} from '@shared/models/calculated-field.models';
|
||||||
import {
|
import {
|
||||||
CalculatedFieldDebugDialogComponent,
|
CalculatedFieldDebugDialogComponent,
|
||||||
@ -53,6 +58,7 @@ import {
|
|||||||
} from './components/public-api';
|
} from './components/public-api';
|
||||||
import { ImportExportService } from '@shared/import-export/import-export.service';
|
import { ImportExportService } from '@shared/import-export/import-export.service';
|
||||||
import { isObject } from '@core/utils';
|
import { isObject } from '@core/utils';
|
||||||
|
import { DatePipe } from '@angular/common';
|
||||||
|
|
||||||
export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedField, PageLink> {
|
export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedField, PageLink> {
|
||||||
|
|
||||||
@ -69,6 +75,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
|
|||||||
constructor(private calculatedFieldsService: CalculatedFieldsService,
|
constructor(private calculatedFieldsService: CalculatedFieldsService,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private dialog: MatDialog,
|
private dialog: MatDialog,
|
||||||
|
private datePipe: DatePipe,
|
||||||
public entityId: EntityId = null,
|
public entityId: EntityId = null,
|
||||||
private store: Store<AppState>,
|
private store: Store<AppState>,
|
||||||
private durationLeft: DurationLeftPipe,
|
private durationLeft: DurationLeftPipe,
|
||||||
@ -107,11 +114,20 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
this.defaultSortOrder = {property: 'name', direction: Direction.DESC};
|
this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC};
|
||||||
|
|
||||||
const expressionColumn = new EntityTableColumn<CalculatedField>('expression', 'calculated-fields.expression', '33%', entity => entity.configuration?.expression);
|
const expressionColumn = new EntityTableColumn<CalculatedField>('expression', 'calculated-fields.expression', '300px');
|
||||||
expressionColumn.sortable = false;
|
expressionColumn.sortable = false;
|
||||||
|
expressionColumn.cellContentFunction = entity => {
|
||||||
|
const expressionLabel = this.getExpressionLabel(entity);
|
||||||
|
return expressionLabel.length < 45 ? expressionLabel : `<span style="display: inline-block; width: 45ch">${expressionLabel.substring(0, 44)}…</span>`;
|
||||||
|
}
|
||||||
|
expressionColumn.cellTooltipFunction = entity => {
|
||||||
|
const expressionLabel = this.getExpressionLabel(entity);
|
||||||
|
return expressionLabel.length < 45 ? null : expressionLabel
|
||||||
|
};
|
||||||
|
|
||||||
|
this.columns.push(new DateEntityTableColumn<CalculatedField>('createdTime', 'common.created-time', this.datePipe, '150px'));
|
||||||
this.columns.push(new EntityTableColumn<CalculatedField>('name', 'common.name', '33%'));
|
this.columns.push(new EntityTableColumn<CalculatedField>('name', 'common.name', '33%'));
|
||||||
this.columns.push(new EntityTableColumn<CalculatedField>('type', 'common.type', '50px', entity => this.translate.instant(CalculatedFieldTypeTranslations.get(entity.type))));
|
this.columns.push(new EntityTableColumn<CalculatedField>('type', 'common.type', '50px', entity => this.translate.instant(CalculatedFieldTypeTranslations.get(entity.type))));
|
||||||
this.columns.push(expressionColumn);
|
this.columns.push(expressionColumn);
|
||||||
@ -146,6 +162,14 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getExpressionLabel(entity: CalculatedField): string {
|
||||||
|
if (entity.type === CalculatedFieldType.SCRIPT) {
|
||||||
|
return 'function calculate(' + Object.keys(entity.configuration.arguments).join(', ') + ')';
|
||||||
|
} else {
|
||||||
|
return entity.configuration.expression;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fetchCalculatedFields(pageLink: PageLink): Observable<PageData<CalculatedField>> {
|
fetchCalculatedFields(pageLink: PageLink): Observable<PageData<CalculatedField>> {
|
||||||
return this.calculatedFieldsService.getCalculatedFields(this.entityId, pageLink);
|
return this.calculatedFieldsService.getCalculatedFields(this.entityId, pageLink);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import { DurationLeftPipe } from '@shared/pipe/duration-left.pipe';
|
|||||||
import { TbPopoverService } from '@shared/components/popover.service';
|
import { TbPopoverService } from '@shared/components/popover.service';
|
||||||
import { CalculatedFieldsService } from '@core/http/calculated-fields.service';
|
import { CalculatedFieldsService } from '@core/http/calculated-fields.service';
|
||||||
import { ImportExportService } from '@shared/import-export/import-export.service';
|
import { ImportExportService } from '@shared/import-export/import-export.service';
|
||||||
|
import { DatePipe } from '@angular/common';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tb-calculated-fields-table',
|
selector: 'tb-calculated-fields-table',
|
||||||
@ -56,6 +57,7 @@ export class CalculatedFieldsTableComponent {
|
|||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private dialog: MatDialog,
|
private dialog: MatDialog,
|
||||||
private store: Store<AppState>,
|
private store: Store<AppState>,
|
||||||
|
private datePipe: DatePipe,
|
||||||
private durationLeft: DurationLeftPipe,
|
private durationLeft: DurationLeftPipe,
|
||||||
private popoverService: TbPopoverService,
|
private popoverService: TbPopoverService,
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
@ -69,6 +71,7 @@ export class CalculatedFieldsTableComponent {
|
|||||||
this.calculatedFieldsService,
|
this.calculatedFieldsService,
|
||||||
this.translate,
|
this.translate,
|
||||||
this.dialog,
|
this.dialog,
|
||||||
|
this.datePipe,
|
||||||
this.entityId(),
|
this.entityId(),
|
||||||
this.store,
|
this.store,
|
||||||
this.durationLeft,
|
this.durationLeft,
|
||||||
|
|||||||
@ -20,18 +20,27 @@
|
|||||||
<table mat-table [dataSource]="dataSource" class="overflow-hidden bg-transparent" matSort
|
<table mat-table [dataSource]="dataSource" class="overflow-hidden bg-transparent" matSort
|
||||||
[matSortActive]="sortOrder.property" [matSortDirection]="sortOrder.direction" matSortDisableClear>
|
[matSortActive]="sortOrder.property" [matSortDirection]="sortOrder.direction" matSortDisableClear>
|
||||||
<ng-container [matColumnDef]="'name'">
|
<ng-container [matColumnDef]="'name'">
|
||||||
<mat-header-cell mat-sort-header *matHeaderCellDef class="!w-1/4 xs:!w-1/2">
|
<mat-header-cell mat-sort-header *matHeaderCellDef class="!w-1/3 xs:!w-1/2">
|
||||||
<div tbTruncateWithTooltip>{{ 'common.name' | translate }}</div>
|
<div tbTruncateWithTooltip>{{ 'common.name' | translate }}</div>
|
||||||
</mat-header-cell>
|
</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let argument" class="w-1/4 xs:w-1/2">
|
<mat-cell *matCellDef="let argument" class="argument-name-cell w-1/3 xs:w-1/2">
|
||||||
<div tbTruncateWithTooltip>{{ argument.argumentName }}</div>
|
<div class="flex items-center">
|
||||||
|
<div tbTruncateWithTooltip class="flex-1">{{ argument.argumentName }}</div>
|
||||||
|
<tb-copy-button
|
||||||
|
class="copy-argument-name"
|
||||||
|
[copyText]="argument.argumentName"
|
||||||
|
tooltipText="{{ 'calculated-fields.copy-argument-name' | translate }}"
|
||||||
|
tooltipPosition="above"
|
||||||
|
icon="content_copy"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container [matColumnDef]="'entityType'">
|
<ng-container [matColumnDef]="'entityType'">
|
||||||
<mat-header-cell mat-sort-header *matHeaderCellDef class="entity-type-header w-1/4 xs:hidden">
|
<mat-header-cell mat-sort-header *matHeaderCellDef class="entity-type-header w-1/5 xs:hidden">
|
||||||
{{ 'entity.entity-type' | translate }}
|
{{ 'entity.entity-type' | translate }}
|
||||||
</mat-header-cell>
|
</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let argument" class="w-1/4 xs:hidden">
|
<mat-cell *matCellDef="let argument" class="w-1/5 xs:hidden">
|
||||||
<div tbTruncateWithTooltip>
|
<div tbTruncateWithTooltip>
|
||||||
@if (argument.refEntityId?.entityType === ArgumentEntityType.Tenant) {
|
@if (argument.refEntityId?.entityType === ArgumentEntityType.Tenant) {
|
||||||
{{ 'calculated-fields.argument-current-tenant' | translate }}
|
{{ 'calculated-fields.argument-current-tenant' | translate }}
|
||||||
@ -98,7 +107,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
(click)="onDelete($event, $index)"
|
(click)="onDelete($event, argument)"
|
||||||
[matTooltip]="'action.delete' | translate"
|
[matTooltip]="'action.delete' | translate"
|
||||||
matTooltipPosition="above">
|
matTooltipPosition="above">
|
||||||
<mat-icon>delete</mat-icon>
|
<mat-icon>delete</mat-icon>
|
||||||
|
|||||||
@ -28,6 +28,17 @@
|
|||||||
.key-text {
|
.key-text {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copy-argument-name {
|
||||||
|
visibility: hidden;
|
||||||
|
transition: visibility 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.argument-name-cell:hover {
|
||||||
|
.copy-argument-name {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.max-args-warning {
|
.max-args-warning {
|
||||||
@ -61,4 +72,11 @@
|
|||||||
padding: 0 28px 0 0;
|
padding: 0 28px 0 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copy-argument-name {
|
||||||
|
.mat-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,7 +50,7 @@ import { TbPopoverService } from '@shared/components/popover.service';
|
|||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
import { EntityId } from '@shared/models/id/entity-id';
|
import { EntityId } from '@shared/models/id/entity-id';
|
||||||
import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models';
|
import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models';
|
||||||
import { getEntityDetailsPageURL, isDefined, isDefinedAndNotNull } from '@core/utils';
|
import { getEntityDetailsPageURL, isDefined, isDefinedAndNotNull, isEqual } from '@core/utils';
|
||||||
import { TbPopoverComponent } from '@shared/components/popover.component';
|
import { TbPopoverComponent } from '@shared/components/popover.component';
|
||||||
import { TbTableDatasource } from '@shared/components/table/table-datasource.abstract';
|
import { TbTableDatasource } from '@shared/components/table/table-datasource.abstract';
|
||||||
import { EntityService } from '@core/http/entity.service';
|
import { EntityService } from '@core/http/entity.service';
|
||||||
@ -144,8 +144,9 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
|
|||||||
return this.errorText ? { argumentsFormArray: false } : null;
|
return this.errorText ? { argumentsFormArray: false } : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
onDelete($event: Event, index: number): void {
|
onDelete($event: Event, argument: CalculatedFieldArgumentValue): void {
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
|
const index = this.argumentsFormArray.controls.findIndex(control => isEqual(control.value, argument));
|
||||||
this.argumentsFormArray.removeAt(index);
|
this.argumentsFormArray.removeAt(index);
|
||||||
this.argumentsFormArray.markAsDirty();
|
this.argumentsFormArray.markAsDirty();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
-->
|
-->
|
||||||
<div [formGroup]="fieldFormGroup" class="dialog-container flex size-full max-w-4xl flex-col">
|
<div [formGroup]="fieldFormGroup" class="calculated-field-dialog-container flex size-full max-w-4xl flex-col">
|
||||||
<mat-toolbar color="primary">
|
<mat-toolbar color="primary">
|
||||||
<h2>{{ 'entity.type-calculated-field' | translate}}</h2>
|
<h2>{{ 'entity.type-calculated-field' | translate}}</h2>
|
||||||
<span class="flex-1"></span>
|
<span class="flex-1"></span>
|
||||||
@ -76,29 +76,27 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="tb-form-panel no-gap">
|
<div class="tb-form-panel no-gap">
|
||||||
<div class="tb-form-panel-title tb-required">{{ 'calculated-fields.expression' | translate }}</div>
|
<div class="tb-form-panel-title tb-required">{{ 'calculated-fields.expression' | translate }}</div>
|
||||||
@if (fieldFormGroup.get('type').value === CalculatedFieldType.SIMPLE) {
|
<mat-form-field class="mt-3" appearance="outline" subscriptSizing="dynamic" [class.hidden]="fieldFormGroup.get('type').value !== CalculatedFieldType.SIMPLE">
|
||||||
<mat-form-field class="mt-3" appearance="outline" subscriptSizing="dynamic">
|
<input matInput formControlName="expressionSIMPLE" maxlength="255" [placeholder]="'(temperature - 32) / 1.8'" required>
|
||||||
<input matInput formControlName="expressionSIMPLE" maxlength="255" [placeholder]="'(temperature - 32) / 1.8'" required>
|
@if (configFormGroup.get('expressionSIMPLE').errors && configFormGroup.get('expressionSIMPLE').touched) {
|
||||||
@if (configFormGroup.get('expressionSIMPLE').errors && configFormGroup.get('expressionSIMPLE').touched) {
|
<mat-error>
|
||||||
<mat-error>
|
@if (configFormGroup.get('expressionSIMPLE').hasError('required')) {
|
||||||
@if (configFormGroup.get('expressionSIMPLE').hasError('required')) {
|
{{ 'calculated-fields.hint.expression-required' | translate }}
|
||||||
{{ 'calculated-fields.hint.expression-required' | translate }}
|
} @else if (configFormGroup.get('expressionSIMPLE').hasError('pattern')) {
|
||||||
} @else if (configFormGroup.get('expressionSIMPLE').hasError('pattern')) {
|
{{ 'calculated-fields.hint.expression-invalid' | translate }}
|
||||||
{{ 'calculated-fields.hint.expression-invalid' | translate }}
|
} @else if (configFormGroup.get('expressionSIMPLE').hasError('maxLength')) {
|
||||||
} @else if (configFormGroup.get('expressionSIMPLE').hasError('maxLength')) {
|
{{ 'calculated-fields.hint.expression-max-length' | translate }}
|
||||||
{{ 'calculated-fields.hint.expression-max-length' | translate }}
|
}
|
||||||
}
|
</mat-error>
|
||||||
</mat-error>
|
} @else {
|
||||||
} @else {
|
<mat-hint>{{ 'calculated-fields.hint.expression' | translate }}</mat-hint>
|
||||||
<mat-hint>{{ 'calculated-fields.hint.expression' | translate }}</mat-hint>
|
}
|
||||||
}
|
</mat-form-field>
|
||||||
</mat-form-field>
|
<div [class.hidden]="fieldFormGroup.get('type').value !== CalculatedFieldType.SCRIPT">
|
||||||
} @else {
|
|
||||||
<tb-js-func
|
<tb-js-func
|
||||||
required
|
required
|
||||||
formControlName="expressionSCRIPT"
|
formControlName="expressionSCRIPT"
|
||||||
functionName="calculate"
|
functionName="calculate"
|
||||||
class="expression-edit"
|
|
||||||
[functionArgs]="functionArgs$ | async"
|
[functionArgs]="functionArgs$ | async"
|
||||||
[disableUndefinedCheck]="true"
|
[disableUndefinedCheck]="true"
|
||||||
[scriptLanguage]="ScriptLanguage.TBEL"
|
[scriptLanguage]="ScriptLanguage.TBEL"
|
||||||
@ -106,7 +104,7 @@
|
|||||||
[editorCompleter]="argumentsEditorCompleter$ | async"
|
[editorCompleter]="argumentsEditorCompleter$ | async"
|
||||||
helpId="calculated-field/expression_fn"
|
helpId="calculated-field/expression_fn"
|
||||||
>
|
>
|
||||||
<div toolbarPrefixButton class="tb-primary-background script-lang-chip">{{ 'api-usage.tbel' | translate }}</div>
|
<div toolbarPrefixButton class="tb-primary-background tbel-script-lang-chip">{{ 'api-usage.tbel' | translate }}</div>
|
||||||
<button toolbarSuffixButton
|
<button toolbarSuffixButton
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
matTooltip="{{ 'common.test-function' | translate }}"
|
matTooltip="{{ 'common.test-function' | translate }}"
|
||||||
@ -125,7 +123,7 @@
|
|||||||
{{ 'common.test-function' | translate }}
|
{{ 'common.test-function' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tb-form-panel" [formGroup]="outputFormGroup">
|
<div class="tb-form-panel" [formGroup]="outputFormGroup">
|
||||||
<div class="tb-form-panel-title">{{ 'calculated-fields.output' | translate }}</div>
|
<div class="tb-form-panel-title">{{ 'calculated-fields.output' | translate }}</div>
|
||||||
@ -154,26 +152,35 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@if (fieldFormGroup.get('type').value === CalculatedFieldType.SIMPLE) {
|
@if (fieldFormGroup.get('type').value === CalculatedFieldType.SIMPLE) {
|
||||||
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
|
<div class="flex items-center gap-3">
|
||||||
<mat-label>
|
<mat-form-field class="flex-1" appearance="outline">
|
||||||
{{ (outputFormGroup.get('type').value === OutputType.Timeseries
|
<mat-label>
|
||||||
? 'calculated-fields.timeseries-key'
|
{{ (outputFormGroup.get('type').value === OutputType.Timeseries
|
||||||
: 'calculated-fields.attribute-key')
|
? 'calculated-fields.timeseries-key'
|
||||||
| translate }}
|
: 'calculated-fields.attribute-key')
|
||||||
</mat-label>
|
| translate }}
|
||||||
<input matInput formControlName="name" required>
|
</mat-label>
|
||||||
@if (outputFormGroup.get('name').errors && outputFormGroup.get('name').touched) {
|
<input matInput formControlName="name" required>
|
||||||
<mat-error>
|
@if (outputFormGroup.get('name').errors && outputFormGroup.get('name').touched) {
|
||||||
@if (outputFormGroup.get('name').hasError('required')) {
|
<mat-error>
|
||||||
{{ 'common.hint.key-required' | translate }}
|
@if (outputFormGroup.get('name').hasError('required')) {
|
||||||
} @else if (outputFormGroup.get('name').hasError('pattern')) {
|
{{ 'common.hint.key-required' | translate }}
|
||||||
{{ 'common.hint.key-pattern' | translate }}
|
} @else if (outputFormGroup.get('name').hasError('pattern')) {
|
||||||
} @else if (outputFormGroup.get('name').hasError('maxlength')) {
|
{{ 'common.hint.key-pattern' | translate }}
|
||||||
{{ 'common.hint.key-max-length' | translate }}
|
} @else if (outputFormGroup.get('name').hasError('maxlength')) {
|
||||||
}
|
{{ 'common.hint.key-max-length' | translate }}
|
||||||
</mat-error>
|
}
|
||||||
}
|
</mat-error>
|
||||||
</mat-form-field>
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field class="flex-1" appearance="outline">
|
||||||
|
<mat-label>{{ 'calculated-fields.decimals-by-default' | translate }}</mat-label>
|
||||||
|
<input matInput type="number" formControlName="decimalsByDefault">
|
||||||
|
@if (outputFormGroup.get('decimalsByDefault').errors && outputFormGroup.get('decimalsByDefault').touched) {
|
||||||
|
<mat-error>{{ 'calculated-fields.hint.decimals-range' | translate }}</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@ -13,39 +13,37 @@
|
|||||||
* 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 {
|
|
||||||
.dialog-container {
|
|
||||||
width: 869px;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.script-lang-chip {
|
.calculated-field-dialog-container {
|
||||||
line-height: 20px;
|
width: 869px;
|
||||||
font-size: 14px;
|
max-width: 100%;
|
||||||
font-weight: 500;
|
|
||||||
color: white;
|
|
||||||
border-radius: 100px;
|
|
||||||
width: 70px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 2px;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:host ::ng-deep {
|
.tbel-script-lang-chip {
|
||||||
.expression-edit {
|
line-height: 20px;
|
||||||
.ace_tb {
|
font-size: 14px;
|
||||||
&.ace_calculated-field {
|
font-weight: 500;
|
||||||
&-key {
|
color: white;
|
||||||
color: #C52F00;
|
border-radius: 100px;
|
||||||
}
|
width: 70px;
|
||||||
&-ts, &-time-window, &-values, &-value, &-func {
|
min-width: 70px;
|
||||||
color: #7214D0;
|
display: flex;
|
||||||
}
|
justify-content: center;
|
||||||
&-start-ts, &-end-ts, &-limit {
|
margin-top: 2px;
|
||||||
color: #185F2A;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tb-js-func {
|
||||||
|
.ace_tb {
|
||||||
|
&.ace_calculated-field {
|
||||||
|
&-key {
|
||||||
|
color: #C52F00;
|
||||||
|
}
|
||||||
|
&-ts, &-time-window, &-values, &-value, &-func {
|
||||||
|
color: #7214D0;
|
||||||
|
}
|
||||||
|
&-start-ts, &-end-ts, &-limit {
|
||||||
|
color: #185F2A;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
import { Component, DestroyRef, Inject } from '@angular/core';
|
import { Component, DestroyRef, Inject, ViewEncapsulation } from '@angular/core';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { AppState } from '@core/core.state';
|
import { AppState } from '@core/core.state';
|
||||||
@ -24,6 +24,7 @@ import { DialogComponent } from '@shared/components/dialog.component';
|
|||||||
import {
|
import {
|
||||||
CalculatedField,
|
CalculatedField,
|
||||||
CalculatedFieldConfiguration,
|
CalculatedFieldConfiguration,
|
||||||
|
calculatedFieldDefaultScript,
|
||||||
CalculatedFieldDialogData,
|
CalculatedFieldDialogData,
|
||||||
CalculatedFieldType,
|
CalculatedFieldType,
|
||||||
CalculatedFieldTypeTranslations,
|
CalculatedFieldTypeTranslations,
|
||||||
@ -32,7 +33,7 @@ import {
|
|||||||
OutputType,
|
OutputType,
|
||||||
OutputTypeTranslations
|
OutputTypeTranslations
|
||||||
} from '@shared/models/calculated-field.models';
|
} from '@shared/models/calculated-field.models';
|
||||||
import { noLeadTrailSpacesRegex } from '@shared/models/regex.constants';
|
import { digitsRegex, oneSpaceInsideRegex } from '@shared/models/regex.constants';
|
||||||
import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
|
import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
|
||||||
import { EntityType } from '@shared/models/entity-type.models';
|
import { EntityType } from '@shared/models/entity-type.models';
|
||||||
import { map, startWith, switchMap } from 'rxjs/operators';
|
import { map, startWith, switchMap } from 'rxjs/operators';
|
||||||
@ -45,21 +46,23 @@ import { Observable } from 'rxjs';
|
|||||||
selector: 'tb-calculated-field-dialog',
|
selector: 'tb-calculated-field-dialog',
|
||||||
templateUrl: './calculated-field-dialog.component.html',
|
templateUrl: './calculated-field-dialog.component.html',
|
||||||
styleUrls: ['./calculated-field-dialog.component.scss'],
|
styleUrls: ['./calculated-field-dialog.component.scss'],
|
||||||
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFieldDialogComponent, CalculatedField> {
|
export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFieldDialogComponent, CalculatedField> {
|
||||||
|
|
||||||
fieldFormGroup = this.fb.group({
|
fieldFormGroup = this.fb.group({
|
||||||
name: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex), Validators.maxLength(255)]],
|
name: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]],
|
||||||
type: [CalculatedFieldType.SIMPLE],
|
type: [CalculatedFieldType.SIMPLE],
|
||||||
debugSettings: [],
|
debugSettings: [],
|
||||||
configuration: this.fb.group({
|
configuration: this.fb.group({
|
||||||
arguments: this.fb.control({}),
|
arguments: this.fb.control({}),
|
||||||
expressionSIMPLE: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex), Validators.maxLength(255)]],
|
expressionSIMPLE: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]],
|
||||||
expressionSCRIPT: [],
|
expressionSCRIPT: [calculatedFieldDefaultScript],
|
||||||
output: this.fb.group({
|
output: this.fb.group({
|
||||||
name: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex), Validators.maxLength(255)]],
|
name: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]],
|
||||||
scope: [{ value: AttributeScope.SERVER_SCOPE, disabled: true }],
|
scope: [{ value: AttributeScope.SERVER_SCOPE, disabled: true }],
|
||||||
type: [OutputType.Timeseries]
|
type: [OutputType.Timeseries],
|
||||||
|
decimalsByDefault: [null as number, [Validators.min(0), Validators.max(15), Validators.pattern(digitsRegex)]],
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@ -119,9 +122,18 @@ export class CalculatedFieldDialogComponent extends DialogComponent<CalculatedFi
|
|||||||
}
|
}
|
||||||
|
|
||||||
get fromGroupValue(): CalculatedField {
|
get fromGroupValue(): CalculatedField {
|
||||||
const { configuration, type, ...rest } = this.fieldFormGroup.value;
|
const { configuration, type, name, ...rest } = this.fieldFormGroup.value;
|
||||||
const { expressionSIMPLE, expressionSCRIPT, ...restConfig } = configuration;
|
const { expressionSIMPLE, expressionSCRIPT, output, ...restConfig } = configuration;
|
||||||
return { configuration: { ...restConfig, type, expression: configuration['expression'+type] }, ...rest, type } as CalculatedField;
|
return {
|
||||||
|
configuration: {
|
||||||
|
...restConfig,
|
||||||
|
type, expression: configuration['expression'+type].trim(),
|
||||||
|
output: { ...output, name: output.name?.trim() ?? '' }
|
||||||
|
},
|
||||||
|
name: name.trim(),
|
||||||
|
type,
|
||||||
|
...rest,
|
||||||
|
} as CalculatedField;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel(): void {
|
cancel(): void {
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
import { ChangeDetectorRef, Component, Input, OnInit, output } from '@angular/core';
|
import { ChangeDetectorRef, Component, Input, OnInit, output } from '@angular/core';
|
||||||
import { TbPopoverComponent } from '@shared/components/popover.component';
|
import { TbPopoverComponent } from '@shared/components/popover.component';
|
||||||
import { FormBuilder, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
|
import { FormBuilder, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
|
||||||
import { charsWithNumRegex, noLeadTrailSpacesRegex } from '@shared/models/regex.constants';
|
import { charsWithNumRegex, oneSpaceInsideRegex } from '@shared/models/regex.constants';
|
||||||
import {
|
import {
|
||||||
ArgumentEntityType,
|
ArgumentEntityType,
|
||||||
ArgumentEntityTypeParamsMap,
|
ArgumentEntityTypeParamsMap,
|
||||||
@ -71,10 +71,10 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit {
|
|||||||
}),
|
}),
|
||||||
refEntityKey: this.fb.group({
|
refEntityKey: this.fb.group({
|
||||||
type: [ArgumentType.LatestTelemetry, [Validators.required]],
|
type: [ArgumentType.LatestTelemetry, [Validators.required]],
|
||||||
key: [''],
|
key: ['', [Validators.pattern(oneSpaceInsideRegex)]],
|
||||||
scope: [{ value: AttributeScope.SERVER_SCOPE, disabled: true }, [Validators.required]],
|
scope: [{ value: AttributeScope.SERVER_SCOPE, disabled: true }, [Validators.required]],
|
||||||
}),
|
}),
|
||||||
defaultValue: ['', [Validators.pattern(noLeadTrailSpacesRegex)]],
|
defaultValue: ['', [Validators.pattern(oneSpaceInsideRegex)]],
|
||||||
limit: [{ value: this.defaultLimit, disabled: !this.maxDataPointsPerRollingArg }],
|
limit: [{ value: this.defaultLimit, disabled: !this.maxDataPointsPerRollingArg }],
|
||||||
timeWindow: [MINUTE * 15],
|
timeWindow: [MINUTE * 15],
|
||||||
});
|
});
|
||||||
@ -142,6 +142,10 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit {
|
|||||||
if (refEntityId.entityType === ArgumentEntityType.Tenant) {
|
if (refEntityId.entityType === ArgumentEntityType.Tenant) {
|
||||||
refEntityId.id = this.tenantId;
|
refEntityId.id = this.tenantId;
|
||||||
}
|
}
|
||||||
|
if (value.defaultValue) {
|
||||||
|
value.defaultValue = value.defaultValue.trim();
|
||||||
|
}
|
||||||
|
value.refEntityKey.key = value.refEntityKey.key.trim();
|
||||||
this.argumentsDataApplied.emit({ value, index: this.index });
|
this.argumentsDataApplied.emit({ value, index: this.index });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -71,20 +71,4 @@
|
|||||||
background-image: url("../../../../../../../assets/split.js/grips/vertical.png");
|
background-image: url("../../../../../../../assets/split.js/grips/vertical.png");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.expression-edit {
|
|
||||||
.ace_tb {
|
|
||||||
&.ace_calculated-field {
|
|
||||||
&-key {
|
|
||||||
color: #C52F00;
|
|
||||||
}
|
|
||||||
&-ts, &-time-window, &-values, &-value, &-func {
|
|
||||||
color: #7214D0;
|
|
||||||
}
|
|
||||||
&-start-ts, &-end-ts, &-limit {
|
|
||||||
color: #185F2A;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -369,7 +369,7 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
|
|||||||
(entity) => entity.body.entityId,
|
(entity) => entity.body.entityId,
|
||||||
{
|
{
|
||||||
name: this.translate.instant('event.copy-entity-id'),
|
name: this.translate.instant('event.copy-entity-id'),
|
||||||
icon: 'content_paste',
|
icon: 'content_copy',
|
||||||
style: {
|
style: {
|
||||||
padding: '4px',
|
padding: '4px',
|
||||||
'font-size': '16px',
|
'font-size': '16px',
|
||||||
@ -389,7 +389,7 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
|
|||||||
false,
|
false,
|
||||||
{
|
{
|
||||||
name: this.translate.instant('event.copy-message-id'),
|
name: this.translate.instant('event.copy-message-id'),
|
||||||
icon: 'content_paste',
|
icon: 'content_copy',
|
||||||
style: {
|
style: {
|
||||||
padding: '4px',
|
padding: '4px',
|
||||||
'font-size': '16px',
|
'font-size': '16px',
|
||||||
|
|||||||
@ -587,6 +587,10 @@ export class JsFuncComponent implements OnInit, OnChanges, OnDestroy, ControlVal
|
|||||||
newMode.$highlightRules.$rules[group] = this.highlightRules[group];
|
newMode.$highlightRules.$rules[group] = this.highlightRules[group];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const identifierRule = newMode.$highlightRules.$rules.no_regex.find(rule => rule.token?.includes('identifier'));
|
||||||
|
if (identifierRule) {
|
||||||
|
identifierRule.next = 'start';
|
||||||
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.jsEditor.session.$onChangeMode(newMode);
|
this.jsEditor.session.$onChangeMode(newMode);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -377,4 +377,3 @@ export const endGroupHighlightRule: AceHighlightRule = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -64,6 +64,7 @@ export interface CalculatedFieldOutput {
|
|||||||
type: OutputType;
|
type: OutputType;
|
||||||
name: string;
|
name: string;
|
||||||
scope?: AttributeScope;
|
scope?: AttributeScope;
|
||||||
|
decimalsByDefault?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ArgumentEntityType {
|
export enum ArgumentEntityType {
|
||||||
@ -604,3 +605,8 @@ const calculatedFieldTimeWindowArgumentValueHighlightRules: AceHighlightRules =
|
|||||||
endGroupHighlightRule
|
endGroupHighlightRule
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const calculatedFieldDefaultScript = 'return {\n' +
|
||||||
|
' // Convert Fahrenheit to Celsius\n' +
|
||||||
|
' "temperatureCelsius": (temperature.value - 32) / 1.8\n' +
|
||||||
|
'};'
|
||||||
|
|||||||
@ -14,6 +14,8 @@
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
export const noLeadTrailSpacesRegex = /^\S+(?: \S+)*$/;
|
export const oneSpaceInsideRegex = /^\s*\S+(?:\s\S+)*\s*$/;
|
||||||
|
|
||||||
export const charsWithNumRegex = /^[a-zA-Z]+[a-zA-Z0-9]*$/;
|
export const charsWithNumRegex = /^[a-zA-Z_]+[a-zA-Z0-9_]*$/;
|
||||||
|
|
||||||
|
export const digitsRegex = /^\d*$/;
|
||||||
|
|||||||
@ -1015,6 +1015,7 @@
|
|||||||
"script": "Script"
|
"script": "Script"
|
||||||
},
|
},
|
||||||
"arguments": "Arguments",
|
"arguments": "Arguments",
|
||||||
|
"decimals-by-default": "Decimals by default",
|
||||||
"debugging": "Calculated field debugging",
|
"debugging": "Calculated field debugging",
|
||||||
"argument-name": "Argument name",
|
"argument-name": "Argument name",
|
||||||
"datasource": "Datasource",
|
"datasource": "Datasource",
|
||||||
@ -1031,6 +1032,7 @@
|
|||||||
"argument-type": "Argument type",
|
"argument-type": "Argument type",
|
||||||
"see-debug-events": "See debug events",
|
"see-debug-events": "See debug events",
|
||||||
"attribute": "Attribute",
|
"attribute": "Attribute",
|
||||||
|
"copy-argument-name": "Copy argument name",
|
||||||
"timeseries-key": "Time series key",
|
"timeseries-key": "Time series key",
|
||||||
"device-name": "Device name",
|
"device-name": "Device name",
|
||||||
"latest-telemetry": "Latest telemetry",
|
"latest-telemetry": "Latest telemetry",
|
||||||
@ -1070,6 +1072,7 @@
|
|||||||
"argument-name-max-length": "Argument name should be less than 256 characters.",
|
"argument-name-max-length": "Argument name should be less than 256 characters.",
|
||||||
"argument-type-required": "Argument type is required.",
|
"argument-type-required": "Argument type is required.",
|
||||||
"max-args": "Maximum number of arguments reached.",
|
"max-args": "Maximum number of arguments reached.",
|
||||||
|
"decimals-range": "Decimals by default should be a number between 0 and 15.",
|
||||||
"expression": "Default expression demonstrates how to transform a temperature from Fahrenheit to Celsius."
|
"expression": "Default expression demonstrates how to transform a temperature from Fahrenheit to Celsius."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2521,8 +2524,8 @@
|
|||||||
"type-current-tenant": "Current Tenant",
|
"type-current-tenant": "Current Tenant",
|
||||||
"type-current-user": "Current User",
|
"type-current-user": "Current User",
|
||||||
"type-current-user-owner": "Current User Owner",
|
"type-current-user-owner": "Current User Owner",
|
||||||
"type-calculated-field": "Calculated Field",
|
"type-calculated-field": "Calculated field",
|
||||||
"type-calculated-fields": "Calculated Fields",
|
"type-calculated-fields": "Calculated fields",
|
||||||
"type-widgets-bundle": "Widgets bundle",
|
"type-widgets-bundle": "Widgets bundle",
|
||||||
"type-widgets-bundles": "Widgets bundles",
|
"type-widgets-bundles": "Widgets bundles",
|
||||||
"list-of-widgets-bundles": "{ count, plural, =1 {One widgets bundle} other {List of # widget bundles} }",
|
"list-of-widgets-bundles": "{ count, plural, =1 {One widgets bundle} other {List of # widget bundles} }",
|
||||||
@ -5534,21 +5537,21 @@
|
|||||||
"tenant-entity-import-rate-limit": "Entity version load",
|
"tenant-entity-import-rate-limit": "Entity version load",
|
||||||
"tenant-notification-request-rate-limit": "Notification requests",
|
"tenant-notification-request-rate-limit": "Notification requests",
|
||||||
"tenant-notification-requests-per-rule-rate-limit": "Notification requests per notification rule",
|
"tenant-notification-requests-per-rule-rate-limit": "Notification requests per notification rule",
|
||||||
"max-calculated-fields": "Maximum number of calculated fields per entity",
|
"max-calculated-fields": "Calculated fields per entity maximum number",
|
||||||
"max-calculated-fields-range": "Maximum number of calculated fields per entity can't be negative",
|
"max-calculated-fields-range": "Calculated fields per entity maximum number can't be negative",
|
||||||
"max-calculated-fields-required": "Maximum number of calculated fields per entity is required",
|
"max-calculated-fields-required": "Calculated fields per entity maximum number is required",
|
||||||
"max-data-points-per-rolling-arg": "Maximum number of data points in a time series rolling arguments",
|
"max-data-points-per-rolling-arg": "Maximum data points number in rolling arguments",
|
||||||
"max-data-points-per-rolling-arg-range": "Maximum number of data points in a time series rolling arguments can't be negative",
|
"max-data-points-per-rolling-arg-range": "Maximum data points number in rolling arguments can't be negative",
|
||||||
"max-data-points-per-rolling-arg-required": "Maximum number of data points in a time series rolling arguments is required",
|
"max-data-points-per-rolling-arg-required": "Maximum data points number in rolling arguments is required",
|
||||||
"max-arguments-per-cf": "Maximum number of arguments per calculated field",
|
"max-arguments-per-cf": "Arguments per calculated field maximum number",
|
||||||
"max-arguments-per-cf-range": "Maximum number of arguments per calculated field can't be negative",
|
"max-arguments-per-cf-range": "Arguments per calculated field maximum number can't be negative",
|
||||||
"max-arguments-per-cf-required": "Maximum number of arguments per calculated field is required",
|
"max-arguments-per-cf-required": "Arguments per calculated field maximum number is required",
|
||||||
"max-state-size": "Maximum size of the state in KB",
|
"max-state-size": "State maximum size in KB",
|
||||||
"max-state-size-range": "Maximum size of the state in KB can't be negative",
|
"max-state-size-range": "State maximum size in KB can't be negative",
|
||||||
"max-state-size-required": "Maximum size of the state in KB is required",
|
"max-state-size-required": "State maximum size in KB is required",
|
||||||
"max-value-argument-size": "Maximum size of the single value argument in KB",
|
"max-value-argument-size": "Single value argument maximum size in KB",
|
||||||
"max-value-argument-size-range": "Maximum size of the single value argument in KB can't be negative",
|
"max-value-argument-size-range": "Single value argument maximum size in KB can't be negative",
|
||||||
"max-value-argument-size-required": "Maximum size of the single value argument in KB is required",
|
"max-value-argument-size-required": "Single value argument maximum size in KB is required",
|
||||||
"max-transport-messages": "Transport messages maximum number",
|
"max-transport-messages": "Transport messages maximum number",
|
||||||
"max-transport-messages-required": "Transport messages maximum number is required.",
|
"max-transport-messages-required": "Transport messages maximum number is required.",
|
||||||
"max-transport-messages-range": "Transport messages maximum number can't be negative",
|
"max-transport-messages-range": "Transport messages maximum number can't be negative",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user