Timewindow: ability to restrict aggregation options list added

This commit is contained in:
Chantsova Ekaterina 2024-10-21 18:51:14 +03:00
parent 0a8849bf45
commit e406e9d95e
12 changed files with 123 additions and 37 deletions

View File

@ -15,10 +15,33 @@
limitations under the License. limitations under the License.
--> -->
<div [formGroup]="aggregationOptionsConfigForm"> <form [formGroup]="aggregationOptionsConfigForm" class="tb-aggregation-options-form tb-form-panel no-border">
<mat-selection-list formControlName="allowedAggregationTypes"> <div class="tb-form-panel-title">{{ 'timewindow.edit-aggregation-functions-list' | translate }}</div>
<mat-list-option *ngFor="let aggregation of aggregations" [value]="aggregation"> <div class="tb-form-hint tb-primary-fill">{{ 'timewindow.edit-aggregation-functions-list-hint' | translate }}</div>
{{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} <div class="tb-form-table no-gap no-padding">
</mat-list-option> <div class="tb-form-table-header">
</mat-selection-list> <div class="tb-form-table-header-cell">{{"timewindow.allowed-aggregation-functions" | translate }}</div>
</div> </div>
<div class="tb-form-table-body">
<mat-selection-list formControlName="allowedAggregationTypes">
<mat-list-option *ngFor="let type of allAggregationTypes" [value]="type" togglePosition="before">
{{ aggregationTypesTranslations.get(aggregationTypes[type]) | translate }}
</mat-list-option>
</mat-selection-list>
</div>
</div>
<div class="tb-flex flex-end no-gap">
<button type="button"
mat-button
(click)="cancel()">
{{ 'action.cancel' | translate }}
</button>
<button type="button"
mat-raised-button
color="primary"
(click)="update()"
[disabled]="aggregationOptionsConfigForm.invalid || !aggregationOptionsConfigForm.dirty">
{{ 'action.apply' | translate }}
</button>
</div>
</form>

View File

@ -13,5 +13,32 @@
* 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 {
.tb-aggregation-options-form {
height: 100%;
max-width: 350px;
.tb-form-table {
overflow: hidden;
}
.tb-form-table-body {
overflow-y: auto;
}
.tb-form-hint {
flex-shrink: 0;
}
.mdc-list {
display: flex;
flex-direction: column;
gap: 8px;
.mat-mdc-list-item.mdc-list-item--with-leading-checkbox.mdc-list-item--with-one-line {
height: 40px;
}
}
}
}

View File

@ -19,10 +19,6 @@ import { aggregationTranslations, AggregationType } from '@shared/models/time/ti
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { TbPopoverComponent } from '@shared/components/popover.component'; import { TbPopoverComponent } from '@shared/components/popover.component';
export interface AggregationOptionsSelectionResult {
allowedAggregationTypes: AggregationType[] | null;
}
@Component({ @Component({
selector: 'tb-aggregation-options-config-panel', selector: 'tb-aggregation-options-config-panel',
templateUrl: './aggregation-options-config-panel.component.html', templateUrl: './aggregation-options-config-panel.component.html',
@ -34,7 +30,7 @@ export class AggregationOptionsConfigPanelComponent implements OnInit {
allowedAggregationTypes: Array<AggregationType>; allowedAggregationTypes: Array<AggregationType>;
@Input() @Input()
onClose: (result: AggregationOptionsSelectionResult | null) => void; onClose: (result: Array<AggregationType> | null) => void;
@Input() @Input()
popoverComponent: TbPopoverComponent; popoverComponent: TbPopoverComponent;
@ -43,7 +39,7 @@ export class AggregationOptionsConfigPanelComponent implements OnInit {
aggregationTypes = AggregationType; aggregationTypes = AggregationType;
aggregations = Object.keys(AggregationType); allAggregationTypes: Array<AggregationType> = Object.values(AggregationType);
aggregationTypesTranslations = aggregationTranslations; aggregationTypesTranslations = aggregationTranslations;
@ -57,9 +53,9 @@ export class AggregationOptionsConfigPanelComponent implements OnInit {
update() { update() {
if (this.onClose) { if (this.onClose) {
this.onClose({ const allowedAggregationTypes = this.aggregationOptionsConfigForm.get('allowedAggregationTypes').value;
allowedAggregationTypes: this.aggregationOptionsConfigForm.get('allowedAggregationTypes').value // if full list selected returns empty for optimization
}); this.onClose(allowedAggregationTypes?.length < this.allAggregationTypes.length ? allowedAggregationTypes : []);
} }
} }

View File

@ -15,7 +15,9 @@
limitations under the License. limitations under the License.
--> -->
<mat-form-field [formGroup]="aggregationTypeFormGroup" [subscriptSizing]="subscriptSizing" [appearance]="appearance"> <mat-form-field [formGroup]="aggregationTypeFormGroup"
[subscriptSizing]="subscriptSizing" [appearance]="appearance"
class="mat-block">
<mat-label *ngIf="displayLabel">{{ label }}</mat-label> <mat-label *ngIf="displayLabel">{{ label }}</mat-label>
<mat-select [required]="required" formControlName="aggregationType"> <mat-select [required]="required" formControlName="aggregationType">
<mat-option *ngFor="let type of aggregationTypes" [value]="type"> <mat-option *ngFor="let type of aggregationTypes" [value]="type">

View File

@ -112,7 +112,7 @@ export class AggregationTypeSelectComponent implements ControlValueAccessor, OnI
this.aggregationTypes = this.allowedAggregationTypes?.length ? this.allowedAggregationTypes : this.allAggregationTypes; this.aggregationTypes = this.allowedAggregationTypes?.length ? this.allowedAggregationTypes : this.allAggregationTypes;
const currentAggregationType: AggregationType = this.aggregationTypeFormGroup.get('aggregationType').value; const currentAggregationType: AggregationType = this.aggregationTypeFormGroup.get('aggregationType').value;
if (currentAggregationType && !this.aggregationTypes.includes(currentAggregationType)) { if (currentAggregationType && !this.aggregationTypes.includes(currentAggregationType)) {
this.aggregationTypeFormGroup.get('aggregationType').patchValue(null, {emitEvent: true}); this.aggregationTypeFormGroup.get('aggregationType').patchValue(this.aggregationTypes[0], {emitEvent: true});
} }
} }
} }

View File

@ -16,7 +16,7 @@
@import '../../../../scss/constants'; @import '../../../../scss/constants';
:host { :host {
min-width: 355px; min-width: 300px;
display: block; display: block;
@media #{$mat-xs} { @media #{$mat-xs} {

View File

@ -51,7 +51,7 @@
{{ 'timewindow.disable-custom-interval' | translate }} {{ 'timewindow.disable-custom-interval' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="tb-form-row"> <div class="tb-form-row column-xs">
<mat-slide-toggle *ngIf="!quickIntervalOnly" <mat-slide-toggle *ngIf="!quickIntervalOnly"
class="mat-slide" formControlName="hideLastInterval"> class="mat-slide" formControlName="hideLastInterval">
<div tb-hint-tooltip-icon="{{'timewindow.hide-last-interval' | translate}}"> <div tb-hint-tooltip-icon="{{'timewindow.hide-last-interval' | translate}}">
@ -70,7 +70,7 @@
</ng-container> </ng-container>
<div *ngIf="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL" <div *ngIf="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL"
class="tb-form-row"> class="tb-form-row column-xs">
<mat-slide-toggle *ngIf="!quickIntervalOnly" <mat-slide-toggle *ngIf="!quickIntervalOnly"
class="mat-slide" formControlName="hideQuickInterval"> class="mat-slide" formControlName="hideQuickInterval">
<div tb-hint-tooltip-icon="{{'timewindow.hide-relative-interval' | translate}}"> <div tb-hint-tooltip-icon="{{'timewindow.hide-relative-interval' | translate}}">
@ -107,7 +107,7 @@
{{ 'timewindow.disable-custom-interval' | translate }} {{ 'timewindow.disable-custom-interval' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="tb-form-row"> <div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide" formControlName="hideLastInterval"> <mat-slide-toggle class="mat-slide" formControlName="hideLastInterval">
<div tb-hint-tooltip-icon="{{'timewindow.hide-last-interval' | translate}}"> <div tb-hint-tooltip-icon="{{'timewindow.hide-last-interval' | translate}}">
{{ 'timewindow.hide' | translate }} {{ 'timewindow.hide' | translate }}
@ -124,7 +124,7 @@
</div> </div>
</ng-container> </ng-container>
<div *ngIf="timewindowForm.get('history.historyType').value === historyTypes.FIXED" class="tb-form-row"> <div *ngIf="timewindowForm.get('history.historyType').value === historyTypes.FIXED" class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide" formControlName="hideFixedInterval"> <mat-slide-toggle class="mat-slide" formControlName="hideFixedInterval">
<div tb-hint-tooltip-icon="{{'timewindow.hide-fixed-interval' | translate}}"> <div tb-hint-tooltip-icon="{{'timewindow.hide-fixed-interval' | translate}}">
{{ 'timewindow.hide' | translate }} {{ 'timewindow.hide' | translate }}
@ -140,7 +140,7 @@
</tb-datetime-period> </tb-datetime-period>
</div> </div>
<div *ngIf="timewindowForm.get('history.historyType').value === historyTypes.INTERVAL" class="tb-form-row"> <div *ngIf="timewindowForm.get('history.historyType').value === historyTypes.INTERVAL" class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide" formControlName="hideQuickInterval"> <mat-slide-toggle class="mat-slide" formControlName="hideQuickInterval">
<div tb-hint-tooltip-icon="{{'timewindow.hide-relative-interval' | translate}}"> <div tb-hint-tooltip-icon="{{'timewindow.hide-relative-interval' | translate}}">
{{ 'timewindow.hide' | translate }} {{ 'timewindow.hide' | translate }}
@ -168,8 +168,10 @@
</mat-slide-toggle> </mat-slide-toggle>
<ng-container formGroupName="aggregation"> <ng-container formGroupName="aggregation">
<tb-aggregation-type-select class="flex" subscriptSizing="dynamic" appearance="outline" displayLabel="false" <tb-aggregation-type-select class="flex" subscriptSizing="dynamic" appearance="outline" displayLabel="false"
formControlName="type"> formControlName="type"
<button matSuffix mat-icon-button type="button" class="tb-mat-24" [allowedAggregationTypes]="timewindowForm.get('allowedAggTypes').value">
<button *ngIf="!timewindowForm.get('hideAggregation').value"
matSuffix mat-icon-button type="button" class="tb-mat-24"
(click)="openAggregationOptionsConfig($event)"> (click)="openAggregationOptionsConfig($event)">
<mat-icon>edit</mat-icon> <mat-icon>edit</mat-icon>
</button> </button>

View File

@ -31,13 +31,12 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { TimeService } from '@core/services/time.service'; import { TimeService } from '@core/services/time.service';
import { isDefined, isDefinedAndNotNull, mergeDeep } from '@core/utils'; import { deepClone, isDefined, isDefinedAndNotNull, mergeDeep } from '@core/utils';
import { ToggleHeaderOption } from '@shared/components/toggle-header.component'; import { ToggleHeaderOption } from '@shared/components/toggle-header.component';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { TimezoneSelectionResult } from '@shared/components/time/timezone-panel.component';
import { TbPopoverService } from '@shared/components/popover.service'; import { TbPopoverService } from '@shared/components/popover.service';
import { import {
AggregationOptionsConfigPanelComponent AggregationOptionsConfigPanelComponent
@ -198,6 +197,8 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On
limit: [ isDefined(aggregation?.limit) ? this.timewindow.aggregation.limit : null ] limit: [ isDefined(aggregation?.limit) ? this.timewindow.aggregation.limit : null ]
}), }),
timezone: [ isDefined(this.timewindow.timezone) ? this.timewindow.timezone : null ], timezone: [ isDefined(this.timewindow.timezone) ? this.timewindow.timezone : null ],
allowedAggTypes: [ isDefinedAndNotNull(this.timewindow.allowedAggTypes)
? this.timewindow.allowedAggTypes : null ],
hideAggregation: [ isDefinedAndNotNull(this.timewindow.hideAggregation) hideAggregation: [ isDefinedAndNotNull(this.timewindow.hideAggregation)
? this.timewindow.hideAggregation : false ], ? this.timewindow.hideAggregation : false ],
hideAggInterval: [ isDefinedAndNotNull(this.timewindow.hideAggInterval) hideAggInterval: [ isDefinedAndNotNull(this.timewindow.hideAggInterval)
@ -289,6 +290,13 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On
} }
} }
}); });
this.timewindowForm.get('hideAggregation').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value: boolean) => {
if (value) {
this.timewindowForm.get('allowedAggTypes').patchValue([]);
}
});
} }
ngOnDestroy() { ngOnDestroy() {
@ -344,6 +352,11 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On
update() { update() {
const timewindowFormValue = this.timewindowForm.getRawValue(); const timewindowFormValue = this.timewindowForm.getRawValue();
this.timewindow = mergeDeep(this.timewindow, timewindowFormValue); this.timewindow = mergeDeep(this.timewindow, timewindowFormValue);
if (timewindowFormValue.allowedAggTypes?.length) {
this.timewindow.allowedAggTypes = timewindowFormValue.allowedAggTypes;
} else {
delete this.timewindow.allowedAggTypes;
}
if (!this.aggregation) { if (!this.aggregation) {
delete this.timewindow.aggregation; delete this.timewindow.aggregation;
} }
@ -405,18 +418,19 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On
this.popoverService.hidePopover(trigger); this.popoverService.hidePopover(trigger);
} else { } else {
const aggregationConfigPopover = this.popoverService.displayPopover(trigger, this.renderer, const aggregationConfigPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, AggregationOptionsConfigPanelComponent, ['bottomRight', 'leftBottom'], true, null, this.viewContainerRef, AggregationOptionsConfigPanelComponent, ['left', 'leftTop', 'leftBottom'], true, null,
{ {
allowedAggregationTypes: null, allowedAggregationTypes: deepClone(this.timewindowForm.get('allowedAggTypes').value),
onClose: (result: TimezoneSelectionResult | null) => { onClose: (result: Array<AggregationType> | null) => {
aggregationConfigPopover.hide(); aggregationConfigPopover.hide();
if (result) { if (result) {
console.log(result); this.timewindowForm.get('allowedAggTypes').patchValue(result);
this.timewindowForm.markAsDirty();
} }
} }
}, },
{}, {maxHeight: '90vh', height: '100%'},
{}, {}, false); {}, {}, true, () => {}, {padding: 0});
aggregationConfigPopover.tbComponentRef.instance.popoverComponent = aggregationConfigPopover; aggregationConfigPopover.tbComponentRef.instance.popoverComponent = aggregationConfigPopover;
} }
this.cd.detectChanges(); this.cd.detectChanges();

View File

@ -126,7 +126,7 @@
<section class="tb-form-row column-xs space-between same-padding" *ngIf="isEdit || !timewindow.hideAggregation"> <section class="tb-form-row column-xs space-between same-padding" *ngIf="isEdit || !timewindow.hideAggregation">
<div class="fixed-title-width-180">{{ 'aggregation.aggregation' | translate }}</div> <div class="fixed-title-width-180">{{ 'aggregation.aggregation' | translate }}</div>
<tb-aggregation-type-select class="flex" subscriptSizing="dynamic" appearance="outline" displayLabel="false" <tb-aggregation-type-select class="flex" subscriptSizing="dynamic" appearance="outline" displayLabel="false"
formControlName="type"> formControlName="type" [allowedAggregationTypes]="allowedAggTypes">
</tb-aggregation-type-select> </tb-aggregation-type-select>
</section> </section>

View File

@ -103,6 +103,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
historyIntervalSelectionAvailable: boolean; historyIntervalSelectionAvailable: boolean;
aggregationOptionsAvailable: boolean; aggregationOptionsAvailable: boolean;
allowedAggTypes: Array<AggregationType>;
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
constructor(@Inject(TIMEWINDOW_PANEL_DATA) public data: TimewindowPanelData, constructor(@Inject(TIMEWINDOW_PANEL_DATA) public data: TimewindowPanelData,
@ -122,6 +124,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
this.timezone = data.timezone; this.timezone = data.timezone;
this.isEdit = data.isEdit; this.isEdit = data.isEdit;
this.allowedAggTypes = this.timewindow.allowedAggTypes;
if (!this.historyOnly) { if (!this.historyOnly) {
this.timewindowTypeOptions.unshift({ this.timewindowTypeOptions.unshift({
name: this.translate.instant('timewindow.realtime'), name: this.translate.instant('timewindow.realtime'),
@ -366,6 +370,9 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
fixedTimewindow: timewindowFormValue.history.fixedTimewindow, fixedTimewindow: timewindowFormValue.history.fixedTimewindow,
quickInterval: timewindowFormValue.history.quickInterval, quickInterval: timewindowFormValue.history.quickInterval,
}}; }};
if (!this.timewindow.allowedAggTypes?.length) {
delete this.timewindow.allowedAggTypes;
}
if (this.aggregation) { if (this.aggregation) {
this.timewindow.aggregation = { this.timewindow.aggregation = {
type: timewindowFormValue.aggregation.type, type: timewindowFormValue.aggregation.type,
@ -508,6 +515,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
.subscribe((res) => { .subscribe((res) => {
if (res) { if (res) {
this.timewindow = res; this.timewindow = res;
this.allowedAggTypes = this.timewindow.allowedAggTypes;
this.updateTimewindowForm(); this.updateTimewindowForm();
} }
}); });

View File

@ -139,6 +139,7 @@ export interface Aggregation {
export interface Timewindow { export interface Timewindow {
displayValue?: string; displayValue?: string;
displayTimezoneAbbr?: string; displayTimezoneAbbr?: string;
allowedAggTypes?: Array<AggregationType>;
hideAggregation?: boolean; hideAggregation?: boolean;
hideAggInterval?: boolean; hideAggInterval?: boolean;
hideTimezone?: boolean; hideTimezone?: boolean;
@ -301,6 +302,9 @@ export const initModelFromDefaultTimewindow = (value: Timewindow, quickIntervalO
historyOnly: boolean, timeService: TimeService): Timewindow => { historyOnly: boolean, timeService: TimeService): Timewindow => {
const model = defaultTimewindow(timeService); const model = defaultTimewindow(timeService);
if (value) { if (value) {
if (value.allowedAggTypes?.length) {
model.allowedAggTypes = value.allowedAggTypes;
}
model.hideAggregation = value.hideAggregation; model.hideAggregation = value.hideAggregation;
model.hideAggInterval = value.hideAggInterval; model.hideAggInterval = value.hideAggInterval;
model.hideTimezone = value.hideTimezone; model.hideTimezone = value.hideTimezone;
@ -429,7 +433,7 @@ export const toHistoryTimewindow = (timewindow: Timewindow, startTimeMs: number,
aggType = AggregationType.AVG; aggType = AggregationType.AVG;
limit = timeService.getMaxDatapointsLimit(); limit = timeService.getMaxDatapointsLimit();
} }
return { const historyTimewindow: Timewindow = {
hideAggregation: timewindow.hideAggregation || false, hideAggregation: timewindow.hideAggregation || false,
hideAggInterval: timewindow.hideAggInterval || false, hideAggInterval: timewindow.hideAggInterval || false,
hideTimezone: timewindow.hideTimezone || false, hideTimezone: timewindow.hideTimezone || false,
@ -451,6 +455,10 @@ export const toHistoryTimewindow = (timewindow: Timewindow, startTimeMs: number,
}, },
timezone: timewindow.timezone timezone: timewindow.timezone
}; };
if (timewindow.allowedAggTypes?.length) {
historyTimewindow.allowedAggTypes = timewindow.allowedAggTypes;
}
return historyTimewindow;
}; };
export const timewindowTypeChanged = (newTimewindow: Timewindow, oldTimewindow: Timewindow): boolean => { export const timewindowTypeChanged = (newTimewindow: Timewindow, oldTimewindow: Timewindow): boolean => {
@ -898,6 +906,9 @@ export const createTimewindowForComparison = (subscriptionTimewindow: Subscripti
export const cloneSelectedTimewindow = (timewindow: Timewindow): Timewindow => { export const cloneSelectedTimewindow = (timewindow: Timewindow): Timewindow => {
const cloned: Timewindow = {}; const cloned: Timewindow = {};
if (timewindow.allowedAggTypes?.length) {
cloned.allowedAggTypes = timewindow.allowedAggTypes;
}
cloned.hideAggregation = timewindow.hideAggregation || false; cloned.hideAggregation = timewindow.hideAggregation || false;
cloned.hideAggInterval = timewindow.hideAggInterval || false; cloned.hideAggInterval = timewindow.hideAggInterval || false;
cloned.hideTimezone = timewindow.hideTimezone || false; cloned.hideTimezone = timewindow.hideTimezone || false;

View File

@ -5216,7 +5216,10 @@
"hide-group-interval": "Hide grouping interval from end-users", "hide-group-interval": "Hide grouping interval from end-users",
"hide-max-values": "Hide max values from end-users", "hide-max-values": "Hide max values from end-users",
"hide-timezone": "Hide time zone from end-users", "hide-timezone": "Hide time zone from end-users",
"disable-custom-interval": "Disable custom interval selection" "disable-custom-interval": "Disable custom interval selection",
"edit-aggregation-functions-list": "Edit aggregation functions list",
"edit-aggregation-functions-list-hint": "List of available options can be specified.",
"allowed-aggregation-functions": "Allowed aggregation functions"
}, },
"tooltip": { "tooltip": {
"trigger": "Trigger", "trigger": "Trigger",