diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts index 6932860be4..cd9f0373db 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts @@ -14,7 +14,11 @@ /// 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 { TranslateService } from '@ngx-translate/core'; import { Direction } from '@shared/models/page/sort-order'; @@ -45,6 +49,7 @@ import { getCalculatedFieldArgumentsEditorCompleter, getCalculatedFieldArgumentsHighlights, CalculatedFieldTypeTranslations, + CalculatedFieldType, } from '@shared/models/calculated-field.models'; import { CalculatedFieldDebugDialogComponent, @@ -53,6 +58,7 @@ import { } from './components/public-api'; import { ImportExportService } from '@shared/import-export/import-export.service'; import { isObject } from '@core/utils'; +import { DatePipe } from '@angular/common'; export class CalculatedFieldsTableConfig extends EntityTableConfig { @@ -69,6 +75,7 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig, private durationLeft: DurationLeftPipe, @@ -107,11 +114,20 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig('expression', 'calculated-fields.expression', '33%', entity => entity.configuration?.expression); + const expressionColumn = new EntityTableColumn('expression', 'calculated-fields.expression', '300px'); expressionColumn.sortable = false; + expressionColumn.cellContentFunction = entity => { + const expressionLabel = this.getExpressionLabel(entity); + return expressionLabel.length < 45 ? expressionLabel : `${expressionLabel.substring(0, 44)}…`; + } + expressionColumn.cellTooltipFunction = entity => { + const expressionLabel = this.getExpressionLabel(entity); + return expressionLabel.length < 45 ? null : expressionLabel + }; + this.columns.push(new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '150px')); this.columns.push(new EntityTableColumn('name', 'common.name', '33%')); this.columns.push(new EntityTableColumn('type', 'common.type', '50px', entity => this.translate.instant(CalculatedFieldTypeTranslations.get(entity.type)))); this.columns.push(expressionColumn); @@ -146,6 +162,14 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig> { return this.calculatedFieldsService.getCalculatedFields(this.entityId, pageLink); } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts index be6fb11480..1d0b9dcb15 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts @@ -35,6 +35,7 @@ import { DurationLeftPipe } from '@shared/pipe/duration-left.pipe'; import { TbPopoverService } from '@shared/components/popover.service'; import { CalculatedFieldsService } from '@core/http/calculated-fields.service'; import { ImportExportService } from '@shared/import-export/import-export.service'; +import { DatePipe } from '@angular/common'; @Component({ selector: 'tb-calculated-fields-table', @@ -56,6 +57,7 @@ export class CalculatedFieldsTableComponent { private translate: TranslateService, private dialog: MatDialog, private store: Store, + private datePipe: DatePipe, private durationLeft: DurationLeftPipe, private popoverService: TbPopoverService, private cd: ChangeDetectorRef, @@ -69,6 +71,7 @@ export class CalculatedFieldsTableComponent { this.calculatedFieldsService, this.translate, this.dialog, + this.datePipe, this.entityId(), this.store, this.durationLeft, diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html b/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html index 6bab01f976..162bd3aa1e 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html @@ -20,18 +20,27 @@ - +
{{ 'common.name' | translate }}
- -
{{ argument.argumentName }}
+ +
+
{{ argument.argumentName }}
+ +
- + {{ 'entity.entity-type' | translate }} - +
@if (argument.refEntityId?.entityType === ArgumentEntityType.Tenant) { {{ 'calculated-fields.argument-current-tenant' | translate }} @@ -98,7 +107,7 @@
- } +
{{ 'calculated-fields.output' | translate }}
@@ -154,26 +152,35 @@ }
@if (fieldFormGroup.get('type').value === CalculatedFieldType.SIMPLE) { - - - {{ (outputFormGroup.get('type').value === OutputType.Timeseries - ? 'calculated-fields.timeseries-key' - : 'calculated-fields.attribute-key') - | translate }} - - - @if (outputFormGroup.get('name').errors && outputFormGroup.get('name').touched) { - - @if (outputFormGroup.get('name').hasError('required')) { - {{ 'common.hint.key-required' | translate }} - } @else if (outputFormGroup.get('name').hasError('pattern')) { - {{ 'common.hint.key-pattern' | translate }} - } @else if (outputFormGroup.get('name').hasError('maxlength')) { - {{ 'common.hint.key-max-length' | translate }} - } - - } - +
+ + + {{ (outputFormGroup.get('type').value === OutputType.Timeseries + ? 'calculated-fields.timeseries-key' + : 'calculated-fields.attribute-key') + | translate }} + + + @if (outputFormGroup.get('name').errors && outputFormGroup.get('name').touched) { + + @if (outputFormGroup.get('name').hasError('required')) { + {{ 'common.hint.key-required' | translate }} + } @else if (outputFormGroup.get('name').hasError('pattern')) { + {{ 'common.hint.key-pattern' | translate }} + } @else if (outputFormGroup.get('name').hasError('maxlength')) { + {{ 'common.hint.key-max-length' | translate }} + } + + } + + + {{ 'calculated-fields.decimals-by-default' | translate }} + + @if (outputFormGroup.get('decimalsByDefault').errors && outputFormGroup.get('decimalsByDefault').touched) { + {{ 'calculated-fields.hint.decimals-range' | translate }} + } + +
}
diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.scss b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.scss index 0e994ed825..3bb2759274 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.scss @@ -13,39 +13,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host { - .dialog-container { - width: 869px; - max-width: 100%; - } - .script-lang-chip { - line-height: 20px; - font-size: 14px; - font-weight: 500; - color: white; - border-radius: 100px; - width: 70px; - display: flex; - justify-content: center; - margin-top: 2px; - margin-right: 4px; - } +.calculated-field-dialog-container { + width: 869px; + max-width: 100%; } -:host ::ng-deep { - .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; - } +.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 { + &-key { + color: #C52F00; + } + &-ts, &-time-window, &-values, &-value, &-func { + color: #7214D0; + } + &-start-ts, &-end-ts, &-limit { + color: #185F2A; } } } diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts index 68abd646e3..0c3e7464cd 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.ts @@ -14,7 +14,7 @@ /// 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 { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -24,6 +24,7 @@ import { DialogComponent } from '@shared/components/dialog.component'; import { CalculatedField, CalculatedFieldConfiguration, + calculatedFieldDefaultScript, CalculatedFieldDialogData, CalculatedFieldType, CalculatedFieldTypeTranslations, @@ -32,7 +33,7 @@ import { OutputType, OutputTypeTranslations } 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 { EntityType } from '@shared/models/entity-type.models'; import { map, startWith, switchMap } from 'rxjs/operators'; @@ -45,21 +46,23 @@ import { Observable } from 'rxjs'; selector: 'tb-calculated-field-dialog', templateUrl: './calculated-field-dialog.component.html', styleUrls: ['./calculated-field-dialog.component.scss'], + encapsulation: ViewEncapsulation.None }) export class CalculatedFieldDialogComponent extends DialogComponent { 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], debugSettings: [], configuration: this.fb.group({ arguments: this.fb.control({}), - expressionSIMPLE: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex), Validators.maxLength(255)]], - expressionSCRIPT: [], + expressionSIMPLE: ['', [Validators.required, Validators.pattern(oneSpaceInsideRegex), Validators.maxLength(255)]], + expressionSCRIPT: [calculatedFieldDefaultScript], 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 }], - 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 { (entity) => entity.body.entityId, { name: this.translate.instant('event.copy-entity-id'), - icon: 'content_paste', + icon: 'content_copy', style: { padding: '4px', 'font-size': '16px', @@ -389,7 +389,7 @@ export class EventTableConfig extends EntityTableConfig { false, { name: this.translate.instant('event.copy-message-id'), - icon: 'content_paste', + icon: 'content_copy', style: { padding: '4px', 'font-size': '16px', diff --git a/ui-ngx/src/app/shared/components/js-func.component.ts b/ui-ngx/src/app/shared/components/js-func.component.ts index 7c7d14eff5..eccb5cb908 100644 --- a/ui-ngx/src/app/shared/components/js-func.component.ts +++ b/ui-ngx/src/app/shared/components/js-func.component.ts @@ -587,6 +587,10 @@ export class JsFuncComponent implements OnInit, OnChanges, OnDestroy, ControlVal 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 this.jsEditor.session.$onChangeMode(newMode); } diff --git a/ui-ngx/src/app/shared/models/ace/ace.models.ts b/ui-ngx/src/app/shared/models/ace/ace.models.ts index 5287886b84..49cf670989 100644 --- a/ui-ngx/src/app/shared/models/ace/ace.models.ts +++ b/ui-ngx/src/app/shared/models/ace/ace.models.ts @@ -377,4 +377,3 @@ export const endGroupHighlightRule: AceHighlightRule = { }; - diff --git a/ui-ngx/src/app/shared/models/calculated-field.models.ts b/ui-ngx/src/app/shared/models/calculated-field.models.ts index 4467fecc79..7083ce7ae0 100644 --- a/ui-ngx/src/app/shared/models/calculated-field.models.ts +++ b/ui-ngx/src/app/shared/models/calculated-field.models.ts @@ -64,6 +64,7 @@ export interface CalculatedFieldOutput { type: OutputType; name: string; scope?: AttributeScope; + decimalsByDefault?: number; } export enum ArgumentEntityType { @@ -604,3 +605,8 @@ const calculatedFieldTimeWindowArgumentValueHighlightRules: AceHighlightRules = endGroupHighlightRule ] } + +export const calculatedFieldDefaultScript = 'return {\n' + + ' // Convert Fahrenheit to Celsius\n' + + ' "temperatureCelsius": (temperature.value - 32) / 1.8\n' + + '};' diff --git a/ui-ngx/src/app/shared/models/regex.constants.ts b/ui-ngx/src/app/shared/models/regex.constants.ts index f0489060ef..b8b1be4e83 100644 --- a/ui-ngx/src/app/shared/models/regex.constants.ts +++ b/ui-ngx/src/app/shared/models/regex.constants.ts @@ -14,6 +14,8 @@ /// 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*$/; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 6db2fa9ff1..a26f267c64 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1015,6 +1015,7 @@ "script": "Script" }, "arguments": "Arguments", + "decimals-by-default": "Decimals by default", "debugging": "Calculated field debugging", "argument-name": "Argument name", "datasource": "Datasource", @@ -1031,6 +1032,7 @@ "argument-type": "Argument type", "see-debug-events": "See debug events", "attribute": "Attribute", + "copy-argument-name": "Copy argument name", "timeseries-key": "Time series key", "device-name": "Device name", "latest-telemetry": "Latest telemetry", @@ -1070,6 +1072,7 @@ "argument-name-max-length": "Argument name should be less than 256 characters.", "argument-type-required": "Argument type is required.", "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." } }, @@ -2521,8 +2524,8 @@ "type-current-tenant": "Current Tenant", "type-current-user": "Current User", "type-current-user-owner": "Current User Owner", - "type-calculated-field": "Calculated Field", - "type-calculated-fields": "Calculated Fields", + "type-calculated-field": "Calculated field", + "type-calculated-fields": "Calculated fields", "type-widgets-bundle": "Widgets bundle", "type-widgets-bundles": "Widgets 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-notification-request-rate-limit": "Notification requests", "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-range": "Maximum number of calculated fields per entity can't be negative", - "max-calculated-fields-required": "Maximum number of calculated fields per entity 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-range": "Maximum number of data points in a time series 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-arguments-per-cf": "Maximum number of arguments per calculated field", - "max-arguments-per-cf-range": "Maximum number of arguments per calculated field can't be negative", - "max-arguments-per-cf-required": "Maximum number of arguments per calculated field is required", - "max-state-size": "Maximum size of the state in KB", - "max-state-size-range": "Maximum size of the state in KB can't be negative", - "max-state-size-required": "Maximum size of the state in KB is required", - "max-value-argument-size": "Maximum size of the single value argument in KB", - "max-value-argument-size-range": "Maximum size of the single value argument in KB can't be negative", - "max-value-argument-size-required": "Maximum size of the single value argument in KB is required", + "max-calculated-fields": "Calculated fields per entity maximum number", + "max-calculated-fields-range": "Calculated fields per entity maximum number can't be negative", + "max-calculated-fields-required": "Calculated fields per entity maximum number is required", + "max-data-points-per-rolling-arg": "Maximum data points number in rolling arguments", + "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 data points number in rolling arguments is required", + "max-arguments-per-cf": "Arguments per calculated field maximum number", + "max-arguments-per-cf-range": "Arguments per calculated field maximum number can't be negative", + "max-arguments-per-cf-required": "Arguments per calculated field maximum number is required", + "max-state-size": "State maximum size in KB", + "max-state-size-range": "State maximum size in KB can't be negative", + "max-state-size-required": "State maximum size in KB is required", + "max-value-argument-size": "Single value argument maximum size in KB", + "max-value-argument-size-range": "Single value argument maximum size in KB can't be negative", + "max-value-argument-size-required": "Single value argument maximum size in KB is required", "max-transport-messages": "Transport messages maximum number", "max-transport-messages-required": "Transport messages maximum number is required.", "max-transport-messages-range": "Transport messages maximum number can't be negative",