Browse Source

Merge pull request #12885 from maxunbearable/feature/calculated-fields-adjustments-11-05

Calculated adjustments
pull/12936/head
Igor Kulikov 2 years ago
committed by GitHub
parent
commit
fa15f5146f
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 11
      ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts
  2. 2
      ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.html
  3. 15
      ui-ngx/src/app/modules/home/components/calculated-fields/components/arguments-table/calculated-field-arguments-table.component.ts
  4. 4
      ui-ngx/src/app/modules/home/components/calculated-fields/components/debug-dialog/calculated-field-debug-dialog.component.ts
  5. 1
      ui-ngx/src/app/modules/home/components/calculated-fields/components/dialog/calculated-field-dialog.component.html
  6. 5
      ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.html
  7. 18
      ui-ngx/src/app/modules/home/components/calculated-fields/components/panel/calculated-field-argument-panel.component.ts
  8. 1
      ui-ngx/src/app/modules/home/components/calculated-fields/components/test-dialog/calculated-field-script-test-dialog.component.html
  9. 7
      ui-ngx/src/app/modules/home/components/event/event-table-config.ts
  10. 3
      ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.ts
  11. 2
      ui-ngx/src/app/shared/components/js-func.component.html
  12. 2
      ui-ngx/src/app/shared/components/js-func.component.ts
  13. 22
      ui-ngx/src/app/shared/import-export/import-export.service.ts
  14. 15
      ui-ngx/src/app/shared/models/calculated-field.models.ts
  15. 15
      ui-ngx/src/assets/locale/locale.constant-en_US.json

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

@ -249,8 +249,15 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig<CalculatedFie
}
private importCalculatedField(): void {
this.importExportService.importCalculatedField(this.entityId)
.pipe(filter(Boolean), takeUntilDestroyed(this.destroyRef))
this.importExportService.openCalculatedFieldImportDialog()
.pipe(
filter(Boolean),
switchMap(calculatedField => this.getCalculatedFieldDialog(calculatedField, 'action.add')),
filter(Boolean),
switchMap(calculatedField => this.calculatedFieldsService.saveCalculatedField(calculatedField)),
filter(Boolean),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(() => this.updateData());
}

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

@ -97,7 +97,7 @@
matTooltipPosition="above">
<mat-icon
[matBadgeHidden]="!(argument.refEntityKey.type === ArgumentType.Rolling
&& calculatedFieldType === CalculatedFieldType.SIMPLE)"
&& calculatedFieldType === CalculatedFieldType.SIMPLE) && !entityNameErrorSet.has(argument.refEntityId?.id)"
matBadgeColor="warn"
matBadgeSize="small"
matBadge="*"

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

@ -58,6 +58,8 @@ import { MatSort } from '@angular/material/sort';
import { getCurrentAuthState } from '@core/auth/auth.selectors';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { catchError } from 'rxjs/operators';
import { NEVER } from 'rxjs';
@Component({
selector: 'tb-calculated-field-arguments-table',
@ -88,6 +90,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
errorText = '';
argumentsFormArray = this.fb.array<AbstractControl>([]);
entityNameMap = new Map<string, string>();
entityNameErrorSet = new Set<string>();
sortOrder = { direction: 'asc', property: '' };
dataSource = new CalculatedFieldArgumentDatasource();
@ -168,6 +171,7 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
buttonTitle: this.argumentsFormArray.at(index)?.value ? 'action.apply' : 'action.add',
tenantId: this.tenantId,
entityName: this.entityName,
entityHasError: this.entityNameErrorSet.has(argument.refEntityId?.id),
usedArgumentNames: this.argumentsFormArray.value.map(({ argumentName }) => argumentName).filter(name => name !== argument.argumentName),
};
this.popoverComponent = this.popoverService.displayPopover(trigger, this.renderer,
@ -198,6 +202,8 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
if (this.calculatedFieldType === CalculatedFieldType.SIMPLE
&& this.argumentsFormArray.controls.some(control => control.value.refEntityKey.type === ArgumentType.Rolling)) {
this.errorText = 'calculated-fields.hint.arguments-simple-with-rolling';
} else if (this.entityNameErrorSet.size) {
this.errorText = 'calculated-fields.hint.arguments-entity-not-found';
} else if (!this.argumentsFormArray.controls.length) {
this.errorText = 'calculated-fields.hint.arguments-empty';
} else {
@ -234,11 +240,18 @@ export class CalculatedFieldArgumentsTableComponent implements ControlValueAcces
}
private updateEntityNameMap(value: CalculatedFieldArgumentValue[]): void {
this.entityNameErrorSet.clear();
value.forEach(({ refEntityId = {}}) => {
if (refEntityId.id && !this.entityNameMap.has(refEntityId.id)) {
const { id, entityType } = refEntityId as EntityId;
this.entityService.getEntity(entityType as EntityType, id, { ignoreLoading: true, ignoreErrors: true })
.pipe(takeUntilDestroyed(this.destroyRef))
.pipe(
catchError(() => {
this.entityNameErrorSet.add(id);
return NEVER;
}),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(entity => this.entityNameMap.set(id, entity.name));
}
});

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

@ -20,7 +20,7 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import { DialogComponent } from '@shared/components/dialog.component';
import { CalculatedFieldEventBody, DebugEventType, EventType } from '@shared/models/event.models';
import { CalculatedFieldEventBody, DebugEventType, Event, EventType } from '@shared/models/event.models';
import { EventTableComponent } from '@home/components/event/event-table.component';
import {
CalculatedField,
@ -56,7 +56,7 @@ export class CalculatedFieldDebugDialogComponent extends DialogComponent<Calcula
ngAfterViewInit(): void {
this.eventsTable.entitiesTable.updateData();
this.eventsTable.entitiesTable.cellActionDescriptors[0].isEnabled = () => this.data.value.type === CalculatedFieldType.SCRIPT;
this.eventsTable.entitiesTable.cellActionDescriptors[0].isEnabled = (event => this.data.value.type === CalculatedFieldType.SCRIPT && !!(event as Event).body.arguments)
}
cancel(): void {

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

@ -102,6 +102,7 @@
[scriptLanguage]="ScriptLanguage.TBEL"
[highlightRules]="argumentsHighlightRules$ | async"
[editorCompleter]="argumentsEditorCompleter$ | async"
[helpPopupStyle]="{ width: '1200px' }"
helpId="calculated-field/expression_fn"
>
<div toolbarPrefixButton class="tb-primary-background tbel-script-lang-chip">{{ 'api-usage.tbel' | translate }}</div>

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

@ -82,6 +82,7 @@
<div class="fixed-title-width tb-required">{{ ArgumentEntityTypeParamsMap.get(entityType).title | translate }}</div>
<tb-entity-autocomplete
class="flex flex-1"
#entityAutocomplete
formControlName="id"
inlineField
[placeholder]="'action.set' | translate"
@ -165,7 +166,7 @@
</div>
} @else {
<div class="tb-form-row">
<div class="fixed-title-width">{{ 'calculated-fields.time-window' | translate }}</div>
<div class="fixed-title-width tb-required">{{ 'calculated-fields.time-window' | translate }}</div>
<tb-timeinterval
subscriptSizing="dynamic"
appearance="outline"
@ -175,7 +176,7 @@
</div>
@if (maxDataPointsPerRollingArg) {
<div class="tb-form-row limit-field-row">
<div class="fixed-title-width">{{ 'calculated-fields.limit' | translate }}</div>
<div class="fixed-title-width tb-required">{{ 'calculated-fields.limit' | translate }}</div>
<div class="limit-slider-container flex w-full flex-1 flex-row items-center justify-start">
<mat-slider class="flex-1" min="1" max="{{maxDataPointsPerRollingArg}}">
<input matSliderThumb formControlName="limit" [value]="argumentFormGroup.get('limit').value"/>

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

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { ChangeDetectorRef, Component, Input, OnInit, output } from '@angular/core';
import { AfterViewInit, ChangeDetectorRef, Component, Input, OnInit, output, ViewChild } from '@angular/core';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { FormBuilder, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { charsWithNumRegex, oneSpaceInsideRegex } from '@shared/models/regex.constants';
@ -41,13 +41,14 @@ import { MINUTE } from '@shared/models/time/time.models';
import { getCurrentAuthState } from '@core/auth/auth.selectors';
import { AppState } from '@core/core.state';
import { Store } from '@ngrx/store';
import { EntityAutocompleteComponent } from '@shared/components/entity/entity-autocomplete.component';
@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 {
export class CalculatedFieldArgumentPanelComponent implements OnInit, AfterViewInit {
@Input() buttonTitle: string;
@Input() index: number;
@ -55,9 +56,12 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit {
@Input() entityId: EntityId;
@Input() tenantId: string;
@Input() entityName: string;
@Input() entityHasError: boolean;
@Input() calculatedFieldType: CalculatedFieldType;
@Input() usedArgumentNames: string[];
@ViewChild('entityAutocomplete') entityAutocomplete: EntityAutocompleteComponent;
argumentsDataApplied = output<{ value: CalculatedFieldArgumentValue, index: number }>();
readonly maxDataPointsPerRollingArg = getCurrentAuthState(this.store).maxDataPointsPerRollingArg;
@ -75,8 +79,8 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit {
scope: [{ value: AttributeScope.SERVER_SCOPE, disabled: true }, [Validators.required]],
}),
defaultValue: ['', [Validators.pattern(oneSpaceInsideRegex)]],
limit: [{ value: this.defaultLimit, disabled: !this.maxDataPointsPerRollingArg }],
timeWindow: [MINUTE * 15],
limit: [{ value: this.defaultLimit, disabled: !this.maxDataPointsPerRollingArg }, [Validators.required, Validators.min(1), Validators.max(this.maxDataPointsPerRollingArg)]],
timeWindow: [MINUTE * 15, [Validators.required]],
});
argumentTypes: ArgumentType[];
@ -136,6 +140,12 @@ export class CalculatedFieldArgumentPanelComponent implements OnInit {
.filter(type => type !== ArgumentType.Rolling || this.calculatedFieldType === CalculatedFieldType.SCRIPT);
}
ngAfterViewInit(): void {
if (this.entityHasError) {
this.entityAutocomplete.selectEntityFormGroup.get('entity').markAsTouched();
}
}
saveArgument(): void {
const { refEntityId, ...restConfig } = this.argumentFormGroup.value;
const value = (refEntityId.entityType === ArgumentEntityType.Current ? restConfig : { refEntityId, ...restConfig }) as CalculatedFieldArgumentValue;

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

@ -44,6 +44,7 @@
[highlightRules]="data.argumentsHighlightRules"
[scriptLanguage]="ScriptLanguage.TBEL"
[editorCompleter]="data.argumentsEditorCompleter"
[helpPopupStyle]="{ width: '1200px' }"
resultType="object"
helpId="calculated-field/expression_fn"
/>

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

@ -478,7 +478,12 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
}
if (contentType === ContentType.JSON && sortKeys) {
try {
content = JSON.stringify(sortObjectKeys(JSON.parse(content)));
const parsedContent = JSON.parse(content);
if (Array.isArray(parsedContent)) {
content = JSON.stringify(parsedContent.map(item => item && typeof item === 'object' ? sortObjectKeys(item) : item));
} else {
content = JSON.stringify(sortObjectKeys(parsedContent));
}
} catch (e) {}
}
this.dialog.open<EventContentDialogComponent, EventContentDialogData>(EventContentDialogComponent, {

3
ui-ngx/src/app/modules/home/components/vc/entity-version-create.component.ts

@ -89,7 +89,8 @@ export class EntityVersionCreateComponent extends PageComponent implements OnIni
{entityName: this.entityName}), [Validators.required, Validators.pattern(/(?:.|\s)*\S(&:.|\s)*/)]],
saveRelations: [false, []],
saveAttributes: [true, []],
saveCredentials: [true, []]
saveCredentials: [true, []],
saveCalculatedFields: [true, []]
});
}

2
ui-ngx/src/app/shared/components/js-func.component.html

@ -41,7 +41,7 @@
{{'js-func.tidy' | translate }}
</button>
<ng-content select="[toolbarSuffixButton]"></ng-content>
<div *ngIf="helpId" [tb-help-popup]="helpId"></div>
<div *ngIf="helpId" [tb-help-popup]="helpId" [tb-help-popup-style]="helpPopupStyle"></div>
<fieldset style="width: initial">
<div matTooltip="{{(fullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}"
matTooltipPosition="above"

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

@ -107,6 +107,8 @@ export class JsFuncComponent implements OnInit, OnChanges, OnDestroy, ControlVal
@Input() globalVariables: Array<string>;
@Input() helpPopupStyle: Record<string, any> = {};
@Input()
@coerceBoolean()
disableUndefinedCheck = false;

22
ui-ngx/src/app/shared/import-export/import-export.service.ts

@ -187,18 +187,8 @@ export class ImportExportService {
});
}
public importCalculatedField(entityId: EntityId): Observable<CalculatedField> {
public openCalculatedFieldImportDialog(): Observable<CalculatedField> {
return this.openImportDialog('calculated-fields.import', 'calculated-fields.file').pipe(
mergeMap((calculatedField: CalculatedField) => {
if (!this.validateImportedCalculatedField({ entityId, ...calculatedField })) {
this.store.dispatch(new ActionNotificationShow(
{message: this.translate.instant('calculated-fields.invalid-file-error'),
type: 'error'}));
throw new Error('Invalid calculated field file');
} else {
return this.calculatedFieldsService.saveCalculatedField(this.prepareImport({ entityId, ...calculatedField }));
}
}),
catchError(() => of(null)),
);
}
@ -989,16 +979,6 @@ export class ImportExportService {
}
}
private validateImportedCalculatedField(calculatedField: CalculatedField): boolean {
const { name, configuration, entityId } = calculatedField;
return isNotEmptyStr(name)
&& isDefined(configuration)
&& isDefined(entityId?.id)
&& !!Object.keys(configuration.arguments).length
&& isDefined(configuration.expression)
&& isDefined(configuration.output)
}
private validateImportedImage(image: ImageExportData): boolean {
return !(!isNotEmptyStr(image.data)
|| !isNotEmptyStr(image.title)

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

@ -126,7 +126,7 @@ export const ArgumentTypeTranslations = new Map<ArgumentType, string>(
export interface CalculatedFieldArgument {
refEntityKey: RefEntityKey;
defaultValue?: string;
refEntityId?: RefEntityKey;
refEntityId?: RefEntityId;
limit?: number;
timeWindow?: number;
}
@ -137,7 +137,7 @@ export interface RefEntityKey {
scope?: AttributeScope;
}
export interface RefEntityKey {
export interface RefEntityId {
entityType: ArgumentEntityType;
id: string;
}
@ -538,12 +538,12 @@ export const getCalculatedFieldArgumentsHighlights = (
regex: `\\b${key}\\b`,
next: argumentsObj[key].refEntityKey.type === ArgumentType.Rolling
? 'calculatedFieldRollingArgumentValue'
: 'start'
: 'no_regex'
}));
const calculatedFieldCtxArgumentsHighlightRules = {
calculatedFieldCtxArgs: [
dotOperatorHighlightRule,
...calculatedFieldArgumentsKeys.map(argumentRule => argumentRule.next === 'start' ? {...argumentRule, next: 'calculatedFieldSingleArgumentValue' } : argumentRule),
...calculatedFieldArgumentsKeys.map(argumentRule => argumentRule.next === 'no_regex' ? {...argumentRule, next: 'calculatedFieldSingleArgumentValue' } : argumentRule),
endGroupHighlightRule
]
};
@ -638,7 +638,8 @@ const calculatedFieldTimeWindowArgumentValueHighlightRules: AceHighlightRules =
]
}
export const calculatedFieldDefaultScript = 'return {\n' +
' // Convert Fahrenheit to Celsius\n' +
' "temperatureCelsius": (temperature - 32) / 1.8\n' +
export const calculatedFieldDefaultScript =
'// Sample script to convert temperature readings from Fahrenheit to Celsius\n' +
'return {\n' +
' "temperatureC": (temperatureF - 32) / 1.8\n' +
'};'

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

@ -1074,7 +1074,8 @@
"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."
"expression": "Default expression demonstrates how to transform a temperature from Fahrenheit to Celsius.",
"arguments-entity-not-found": "Argument target entity not found."
}
},
"confirm-on-exit": {
@ -5571,12 +5572,12 @@
"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-data-points-per-rolling-arg": "Max data points number in rolling arguments",
"max-data-points-per-rolling-arg-range": "Max data points number in rolling arguments can't be negative",
"max-data-points-per-rolling-arg-required": "Max data points number in rolling arguments is required",
"max-arguments-per-cf": "Arguments per calculated field max number",
"max-arguments-per-cf-range": "Arguments per calculated field max number can't be negative",
"max-arguments-per-cf-required": "Arguments per calculated field max 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",

Loading…
Cancel
Save