From 6991acd42cc2a943a8d00987683944a8250fab4c Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Fri, 18 Oct 2024 15:17:08 +0300 Subject: [PATCH 01/32] Timewindow: aggregation type select component introduced --- ui-ngx/src/app/modules/common/modules-map.ts | 8 +- .../aggregation-type-select.component.html | 25 +++ .../aggregation-type-select.component.scss | 29 ++++ .../aggregation-type-select.component.ts | 155 ++++++++++++++++++ .../timewindow-config-dialog.component.html | 10 +- .../timewindow-config-dialog.component.ts | 4 - .../components/time/timewindow-form.scss | 3 +- .../time/timewindow-panel.component.html | 12 +- .../time/timewindow-panel.component.ts | 6 +- ui-ngx/src/app/shared/shared.module.ts | 3 + 10 files changed, 228 insertions(+), 27 deletions(-) create mode 100644 ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.html create mode 100644 ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.scss create mode 100644 ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.ts diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index 76bc84bbec..b5ff3f5f33 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -333,11 +333,12 @@ import * as AssetProfileComponent from '@home/components/profile/asset-profile.c import * as AssetProfileDialogComponent from '@home/components/profile/asset-profile-dialog.component'; import * as AssetProfileAutocompleteComponent from '@home/components/profile/asset-profile-autocomplete.component'; import * as RuleChainSelectComponent from '@shared/components/rule-chain/rule-chain-select.component'; +import * as TimezoneComponent from '@shared/components/time/timezone.component'; +import * as TimezonePanelComponent from '@shared/components/time/timezone-panel.component'; +import * as DatapointsLimitComponent from '@shared/components/time/datapoints-limit.component'; +import * as AggregationTypeSelectComponent from '@shared/components/aggregation/aggregation-type-select.component'; import { IModulesMap } from '@modules/common/modules-map.models'; -import { TimezoneComponent } from '@shared/components/time/timezone.component'; -import { TimezonePanelComponent } from '@shared/components/time/timezone-panel.component'; -import { DatapointsLimitComponent } from '@shared/components/time/datapoints-limit.component'; class ModulesMap implements IModulesMap { @@ -470,6 +471,7 @@ class ModulesMap implements IModulesMap { '@shared/components/time/timezone.component': TimezoneComponent, '@shared/components/time/timezone-panel.component': TimezonePanelComponent, '@shared/components/time/datapoints-limit': DatapointsLimitComponent, + '@shared/components/aggregation/aggregation-type-select': AggregationTypeSelectComponent, '@shared/components/value-input.component': ValueInputComponent, '@shared/components/dashboard-autocomplete.component': DashboardAutocompleteComponent, '@shared/components/entity/entity-subtype-autocomplete.component': EntitySubTypeAutocompleteComponent, diff --git a/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.html b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.html new file mode 100644 index 0000000000..401ae7f16a --- /dev/null +++ b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.html @@ -0,0 +1,25 @@ + + + {{ label }} + + + {{ displayAggregationTypeFn(type) }} + + + diff --git a/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.scss b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.scss new file mode 100644 index 0000000000..ddc2385dfa --- /dev/null +++ b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.scss @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .mat-mdc-form-field { + width: 100%; + } +} + +:host ::ng-deep { + .mat-mdc-form-field-infix { + width: 100%; + } + .mat-mdc-select-value { + min-width: 100px; + } +} diff --git a/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.ts b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.ts new file mode 100644 index 0000000000..25ca4f34b4 --- /dev/null +++ b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.ts @@ -0,0 +1,155 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field'; +import { aggregationTranslations, AggregationType } from '@shared/models/time/time.models'; + +@Component({ + selector: 'tb-aggregation-type-select', + templateUrl: './aggregation-type-select.component.html', + styleUrls: ['./aggregation-type-select.component.scss'], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AggregationTypeSelectComponent), + multi: true + }] +}) +export class AggregationTypeSelectComponent implements ControlValueAccessor, OnInit, OnChanges { + + aggregationTypeFormGroup: FormGroup; + + modelValue: AggregationType | null; + + @Input() + allowedAggregationTypes: Array; + + @Input() + @coerceBoolean() + required: boolean; + + @Input() + disabled: boolean; + + @Input() + @coerceBoolean() + displayLabel = true; + + @Input() + labelText: string; + + get label(): string { + if (this.labelText && this.labelText.length) { + return this.labelText; + } + return this.defaultLabel; + } + + @Input() + subscriptSizing: SubscriptSizing = 'fixed'; + + @Input() + appearance: MatFormFieldAppearance = 'fill'; + + aggregationTypes: Array; + + private defaultLabel = this.translate.instant('aggregation.aggregation'); + + private allAggregationTypes: Array = Object.values(AggregationType); + + private propagateChange = (v: any) => { }; + + constructor(private translate: TranslateService, + private fb: FormBuilder) { + this.aggregationTypeFormGroup = this.fb.group({ + aggregationType: [null] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.aggregationTypes = this.allowedAggregationTypes?.length ? this.allowedAggregationTypes : this.allAggregationTypes; + this.aggregationTypeFormGroup.get('aggregationType').valueChanges.subscribe( + (value) => { + let modelValue; + if (!value || value === '') { + modelValue = null; + } else { + modelValue = value; + } + this.updateView(modelValue); + } + ); + } + + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (!change.firstChange && change.currentValue !== change.previousValue) { + if (propName === 'allowedAggregationTypes') { + this.aggregationTypes = this.allowedAggregationTypes?.length ? this.allowedAggregationTypes : this.allAggregationTypes; + const currentAggregationType: AggregationType = this.aggregationTypeFormGroup.get('aggregationType').value; + if (currentAggregationType && !this.aggregationTypes.includes(currentAggregationType)) { + this.aggregationTypeFormGroup.get('aggregationType').patchValue(null, {emitEvent: true}); + } + } + } + } + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.aggregationTypeFormGroup.disable(); + } else { + this.aggregationTypeFormGroup.enable(); + } + } + + writeValue(value: AggregationType | null): void { + if (value != null) { + this.modelValue = value; + this.aggregationTypeFormGroup.get('aggregationType').patchValue(value, {emitEvent: true}); + } else { + this.modelValue = null; + this.aggregationTypeFormGroup.get('aggregationType').patchValue(null, {emitEvent: true}); + } + } + + updateView(value: AggregationType | null) { + if (this.modelValue !== value) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + displayAggregationTypeFn(aggregationType?: AggregationType | null): string | undefined { + if (aggregationType) { + return this.translate.instant(aggregationTranslations.get(aggregationType)); + } else { + return ''; + } + } +} diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html index 383bf7f7b0..69ce692403 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html @@ -167,13 +167,9 @@ - - - - {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} - - - + + diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts index 41ab6a0787..6eda855cb5 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts @@ -66,10 +66,6 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On aggregationTypes = AggregationType; - aggregations = Object.keys(AggregationType); - - aggregationTypesTranslations = aggregationTranslations; - result: Timewindow; timewindowTypeOptions: ToggleHeaderOption[] = [ diff --git a/ui-ngx/src/app/shared/components/time/timewindow-form.scss b/ui-ngx/src/app/shared/components/time/timewindow-form.scss index 5f436acc09..4709bcce8b 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-form.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-form.scss @@ -30,7 +30,8 @@ tb-timeinterval, tb-quick-time-interval, tb-datetime-period, - tb-datapoints-limit { + tb-datapoints-limit, + tb-aggregation-type-select { flex: 1; } } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index a3409e0ef3..a76df1ffd1 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -125,20 +125,16 @@
{{ 'aggregation.aggregation' | translate }}
- - - - {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} - - - + +
{{ 'aggregation.limit' | translate }}
+ [required]="timewindowForm.get('aggregation.type').value === aggregationTypes.MIN">
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index 844815f51a..d29bc63f53 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -87,10 +87,6 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O aggregationTypes = AggregationType; - aggregations = Object.keys(AggregationType); - - aggregationTypesTranslations = aggregationTranslations; - result: Timewindow; timewindowTypeOptions: ToggleHeaderOption[] = [{ @@ -517,4 +513,6 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O } }); } + + protected readonly AggregationType = AggregationType; } diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index c715377e92..23dc3e5a8f 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -230,6 +230,7 @@ import { CountryAutocompleteComponent } from '@shared/components/country-autocom import { CountryData } from '@shared/models/country.models'; import { SvgXmlComponent } from '@shared/components/svg-xml.component'; import { DatapointsLimitComponent } from '@shared/components/time/datapoints-limit.component'; +import { AggregationTypeSelectComponent } from '@shared/components/aggregation/aggregation-type-select.component'; export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { return markedOptionsService; @@ -320,6 +321,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) TimezonePanelComponent, QuickTimeIntervalComponent, DatapointsLimitComponent, + AggregationTypeSelectComponent, DashboardSelectComponent, DashboardSelectPanelComponent, DatetimePeriodComponent, @@ -536,6 +538,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) TimezonePanelComponent, QuickTimeIntervalComponent, DatapointsLimitComponent, + AggregationTypeSelectComponent, DashboardSelectComponent, DatetimePeriodComponent, DatetimeComponent, From 0a8849bf4582b0a827fd18d9325b7729a2a1f703 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Fri, 18 Oct 2024 18:52:57 +0300 Subject: [PATCH 02/32] Timewindow: aggregation options list restriction (draft) --- ui-ngx/src/app/modules/common/modules-map.ts | 6 +- ...gation-options-config-panel.component.html | 24 +++++++ ...gation-options-config-panel.component.scss | 17 +++++ ...regation-options-config-panel.component.ts | 72 +++++++++++++++++++ .../aggregation-type-select.component.html | 3 + .../aggregation-type-select.component.scss | 6 ++ .../timewindow-config-dialog.component.html | 4 ++ .../timewindow-config-dialog.component.ts | 40 ++++++++++- .../time/timewindow-panel.component.ts | 1 - ui-ngx/src/app/shared/shared.module.ts | 3 + 10 files changed, 170 insertions(+), 6 deletions(-) create mode 100644 ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.html create mode 100644 ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.scss create mode 100644 ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.ts diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index b5ff3f5f33..b188abbafe 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -337,6 +337,7 @@ import * as TimezoneComponent from '@shared/components/time/timezone.component'; import * as TimezonePanelComponent from '@shared/components/time/timezone-panel.component'; import * as DatapointsLimitComponent from '@shared/components/time/datapoints-limit.component'; import * as AggregationTypeSelectComponent from '@shared/components/aggregation/aggregation-type-select.component'; +import * as AggregationOptionsConfigComponent from '@shared/components/aggregation/aggregation-options-config-panel.component'; import { IModulesMap } from '@modules/common/modules-map.models'; @@ -470,8 +471,9 @@ class ModulesMap implements IModulesMap { '@shared/components/time/timezone-select.component': TimezoneSelectComponent, '@shared/components/time/timezone.component': TimezoneComponent, '@shared/components/time/timezone-panel.component': TimezonePanelComponent, - '@shared/components/time/datapoints-limit': DatapointsLimitComponent, - '@shared/components/aggregation/aggregation-type-select': AggregationTypeSelectComponent, + '@shared/components/time/datapoints-limit.component': DatapointsLimitComponent, + '@shared/components/aggregation/aggregation-type-select.component': AggregationTypeSelectComponent, + '@shared/components/aggregation/aggregation-options-config-panel.component': AggregationOptionsConfigComponent, '@shared/components/value-input.component': ValueInputComponent, '@shared/components/dashboard-autocomplete.component': DashboardAutocompleteComponent, '@shared/components/entity/entity-subtype-autocomplete.component': EntitySubTypeAutocompleteComponent, diff --git a/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.html b/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.html new file mode 100644 index 0000000000..35016c6a28 --- /dev/null +++ b/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.html @@ -0,0 +1,24 @@ + +
+ + + {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} + + +
diff --git a/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.scss b/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.scss new file mode 100644 index 0000000000..b9dc294716 --- /dev/null +++ b/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.scss @@ -0,0 +1,17 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + diff --git a/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.ts b/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.ts new file mode 100644 index 0000000000..1b789a747c --- /dev/null +++ b/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.ts @@ -0,0 +1,72 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Input, OnInit } from '@angular/core'; +import { aggregationTranslations, AggregationType } from '@shared/models/time/time.models'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { TbPopoverComponent } from '@shared/components/popover.component'; + +export interface AggregationOptionsSelectionResult { + allowedAggregationTypes: AggregationType[] | null; +} + +@Component({ + selector: 'tb-aggregation-options-config-panel', + templateUrl: './aggregation-options-config-panel.component.html', + styleUrls: ['./aggregation-options-config-panel.component.scss'] +}) +export class AggregationOptionsConfigPanelComponent implements OnInit { + + @Input() + allowedAggregationTypes: Array; + + @Input() + onClose: (result: AggregationOptionsSelectionResult | null) => void; + + @Input() + popoverComponent: TbPopoverComponent; + + aggregationOptionsConfigForm: FormGroup; + + aggregationTypes = AggregationType; + + aggregations = Object.keys(AggregationType); + + aggregationTypesTranslations = aggregationTranslations; + + constructor(private fb: FormBuilder) {} + + ngOnInit(): void { + this.aggregationOptionsConfigForm = this.fb.group({ + allowedAggregationTypes: [this.allowedAggregationTypes] + }); + } + + update() { + if (this.onClose) { + this.onClose({ + allowedAggregationTypes: this.aggregationOptionsConfigForm.get('allowedAggregationTypes').value + }); + } + } + + cancel() { + if (this.onClose) { + this.onClose(null); + } + } + +} diff --git a/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.html b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.html index 401ae7f16a..b9a9cdd93e 100644 --- a/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.html +++ b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.html @@ -22,4 +22,7 @@ {{ displayAggregationTypeFn(type) }} + + + diff --git a/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.scss b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.scss index ddc2385dfa..4e08e7fc73 100644 --- a/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.scss +++ b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.scss @@ -26,4 +26,10 @@ .mat-mdc-select-value { min-width: 100px; } + .mat-mdc-form-field-has-icon-suffix .mat-mdc-text-field-wrapper:has(.mat-mdc-form-field-icon-suffix:empty) { + padding-right: 12px; + } + .mat-mdc-form-field-icon-suffix:empty { + padding: 0; + } } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html index 69ce692403..dbfb9e37ea 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html @@ -169,6 +169,10 @@ + diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts index 6eda855cb5..f99dd02bf2 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts @@ -14,9 +14,8 @@ /// limitations under the License. /// -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; import { - aggregationTranslations, AggregationType, DAY, HistoryWindowType, @@ -38,6 +37,11 @@ import { TranslateService } from '@ngx-translate/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { TimezoneSelectionResult } from '@shared/components/time/timezone-panel.component'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { + AggregationOptionsConfigPanelComponent +} from '@shared/components/aggregation/aggregation-options-config-panel.component'; export interface TimewindowConfigDialogData { quickIntervalOnly: boolean; @@ -112,7 +116,11 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On protected store: Store, public fb: FormBuilder, private timeService: TimeService, - private translate: TranslateService) { + private translate: TranslateService, + private popoverService: TbPopoverService, + private renderer: Renderer2, + private cd: ChangeDetectorRef, + public viewContainerRef: ViewContainerRef) { super(store); this.quickIntervalOnly = data.quickIntervalOnly; this.aggregation = data.aggregation; @@ -388,4 +396,30 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On } } + openAggregationOptionsConfig($event: Event) { + if ($event) { + $event.stopPropagation(); + } + const trigger = ($event.target || $event.srcElement || $event.currentTarget) as Element; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const aggregationConfigPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, AggregationOptionsConfigPanelComponent, ['bottomRight', 'leftBottom'], true, null, + { + allowedAggregationTypes: null, + onClose: (result: TimezoneSelectionResult | null) => { + aggregationConfigPopover.hide(); + if (result) { + console.log(result); + } + } + }, + {}, + {}, {}, false); + aggregationConfigPopover.tbComponentRef.instance.popoverComponent = aggregationConfigPopover; + } + this.cd.detectChanges(); + } + } diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index d29bc63f53..2037a3578b 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -16,7 +16,6 @@ import { Component, Inject, InjectionToken, OnDestroy, OnInit, ViewContainerRef } from '@angular/core'; import { - aggregationTranslations, AggregationType, DAY, HistoryWindowType, diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 23dc3e5a8f..2da898bd44 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -231,6 +231,7 @@ import { CountryData } from '@shared/models/country.models'; import { SvgXmlComponent } from '@shared/components/svg-xml.component'; import { DatapointsLimitComponent } from '@shared/components/time/datapoints-limit.component'; import { AggregationTypeSelectComponent } from '@shared/components/aggregation/aggregation-type-select.component'; +import { AggregationOptionsConfigPanelComponent } from '@shared/components/aggregation/aggregation-options-config-panel.component'; export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { return markedOptionsService; @@ -322,6 +323,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) QuickTimeIntervalComponent, DatapointsLimitComponent, AggregationTypeSelectComponent, + AggregationOptionsConfigPanelComponent, DashboardSelectComponent, DashboardSelectPanelComponent, DatetimePeriodComponent, @@ -539,6 +541,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) QuickTimeIntervalComponent, DatapointsLimitComponent, AggregationTypeSelectComponent, + AggregationOptionsConfigPanelComponent, DashboardSelectComponent, DatetimePeriodComponent, DatetimeComponent, From e406e9d95e45879e2ea5f29fe62d9722b0d8effe Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 21 Oct 2024 18:51:14 +0300 Subject: [PATCH 03/32] Timewindow: ability to restrict aggregation options list added --- ...gation-options-config-panel.component.html | 37 +++++++++++++++---- ...gation-options-config-panel.component.scss | 27 ++++++++++++++ ...regation-options-config-panel.component.ts | 14 +++---- .../aggregation-type-select.component.html | 4 +- .../aggregation-type-select.component.ts | 2 +- .../time/quick-time-interval.component.scss | 2 +- .../timewindow-config-dialog.component.html | 16 ++++---- .../timewindow-config-dialog.component.ts | 30 +++++++++++---- .../time/timewindow-panel.component.html | 2 +- .../time/timewindow-panel.component.ts | 8 ++++ .../src/app/shared/models/time/time.models.ts | 13 ++++++- .../assets/locale/locale.constant-en_US.json | 5 ++- 12 files changed, 123 insertions(+), 37 deletions(-) diff --git a/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.html b/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.html index 35016c6a28..ce7494b856 100644 --- a/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.html +++ b/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.html @@ -15,10 +15,33 @@ limitations under the License. --> -
- - - {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} - - -
+
+
{{ 'timewindow.edit-aggregation-functions-list' | translate }}
+
{{ 'timewindow.edit-aggregation-functions-list-hint' | translate }}
+
+
+
{{"timewindow.allowed-aggregation-functions" | translate }}
+
+
+ + + {{ aggregationTypesTranslations.get(aggregationTypes[type]) | translate }} + + +
+
+
+ + +
+
diff --git a/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.scss b/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.scss index b9dc294716..903097ba6b 100644 --- a/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.scss +++ b/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.scss @@ -13,5 +13,32 @@ * See the License for the specific language governing permissions and * 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; + } + } + } +} diff --git a/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.ts b/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.ts index 1b789a747c..bddbe3fba3 100644 --- a/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.ts +++ b/ui-ngx/src/app/shared/components/aggregation/aggregation-options-config-panel.component.ts @@ -19,10 +19,6 @@ import { aggregationTranslations, AggregationType } from '@shared/models/time/ti import { FormBuilder, FormGroup } from '@angular/forms'; import { TbPopoverComponent } from '@shared/components/popover.component'; -export interface AggregationOptionsSelectionResult { - allowedAggregationTypes: AggregationType[] | null; -} - @Component({ selector: 'tb-aggregation-options-config-panel', templateUrl: './aggregation-options-config-panel.component.html', @@ -34,7 +30,7 @@ export class AggregationOptionsConfigPanelComponent implements OnInit { allowedAggregationTypes: Array; @Input() - onClose: (result: AggregationOptionsSelectionResult | null) => void; + onClose: (result: Array | null) => void; @Input() popoverComponent: TbPopoverComponent; @@ -43,7 +39,7 @@ export class AggregationOptionsConfigPanelComponent implements OnInit { aggregationTypes = AggregationType; - aggregations = Object.keys(AggregationType); + allAggregationTypes: Array = Object.values(AggregationType); aggregationTypesTranslations = aggregationTranslations; @@ -57,9 +53,9 @@ export class AggregationOptionsConfigPanelComponent implements OnInit { update() { if (this.onClose) { - this.onClose({ - allowedAggregationTypes: this.aggregationOptionsConfigForm.get('allowedAggregationTypes').value - }); + const allowedAggregationTypes = this.aggregationOptionsConfigForm.get('allowedAggregationTypes').value; + // if full list selected returns empty for optimization + this.onClose(allowedAggregationTypes?.length < this.allAggregationTypes.length ? allowedAggregationTypes : []); } } diff --git a/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.html b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.html index b9a9cdd93e..5e80fc3237 100644 --- a/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.html +++ b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.html @@ -15,7 +15,9 @@ limitations under the License. --> - + {{ label }} diff --git a/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.ts b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.ts index 25ca4f34b4..7986a28f82 100644 --- a/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.ts +++ b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.ts @@ -112,7 +112,7 @@ export class AggregationTypeSelectComponent implements ControlValueAccessor, OnI this.aggregationTypes = this.allowedAggregationTypes?.length ? this.allowedAggregationTypes : this.allAggregationTypes; const currentAggregationType: AggregationType = this.aggregationTypeFormGroup.get('aggregationType').value; if (currentAggregationType && !this.aggregationTypes.includes(currentAggregationType)) { - this.aggregationTypeFormGroup.get('aggregationType').patchValue(null, {emitEvent: true}); + this.aggregationTypeFormGroup.get('aggregationType').patchValue(this.aggregationTypes[0], {emitEvent: true}); } } } diff --git a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.scss b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.scss index de583225fb..f3cd8fdfeb 100644 --- a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.scss +++ b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.scss @@ -16,7 +16,7 @@ @import '../../../../scss/constants'; :host { - min-width: 355px; + min-width: 300px; display: block; @media #{$mat-xs} { diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html index dbfb9e37ea..db6c207f8d 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.html @@ -51,7 +51,7 @@ {{ 'timewindow.disable-custom-interval' | translate }} -
+
@@ -70,7 +70,7 @@
+ class="tb-form-row column-xs">
@@ -107,7 +107,7 @@ {{ 'timewindow.disable-custom-interval' | translate }}
-
+
{{ 'timewindow.hide' | translate }} @@ -124,7 +124,7 @@
-
+
{{ 'timewindow.hide' | translate }} @@ -140,7 +140,7 @@
-
+
{{ 'timewindow.hide' | translate }} @@ -168,8 +168,10 @@ - diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts index f99dd02bf2..8ebd6395c9 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts @@ -31,13 +31,12 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 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 { TranslateService } from '@ngx-translate/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; -import { TimezoneSelectionResult } from '@shared/components/time/timezone-panel.component'; import { TbPopoverService } from '@shared/components/popover.service'; import { AggregationOptionsConfigPanelComponent @@ -198,6 +197,8 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On limit: [ isDefined(aggregation?.limit) ? this.timewindow.aggregation.limit : null ] }), timezone: [ isDefined(this.timewindow.timezone) ? this.timewindow.timezone : null ], + allowedAggTypes: [ isDefinedAndNotNull(this.timewindow.allowedAggTypes) + ? this.timewindow.allowedAggTypes : null ], hideAggregation: [ isDefinedAndNotNull(this.timewindow.hideAggregation) ? this.timewindow.hideAggregation : false ], 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() { @@ -344,6 +352,11 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On update() { const timewindowFormValue = this.timewindowForm.getRawValue(); this.timewindow = mergeDeep(this.timewindow, timewindowFormValue); + if (timewindowFormValue.allowedAggTypes?.length) { + this.timewindow.allowedAggTypes = timewindowFormValue.allowedAggTypes; + } else { + delete this.timewindow.allowedAggTypes; + } if (!this.aggregation) { delete this.timewindow.aggregation; } @@ -405,18 +418,19 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On this.popoverService.hidePopover(trigger); } else { 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, - onClose: (result: TimezoneSelectionResult | null) => { + allowedAggregationTypes: deepClone(this.timewindowForm.get('allowedAggTypes').value), + onClose: (result: Array | null) => { aggregationConfigPopover.hide(); if (result) { - console.log(result); + this.timewindowForm.get('allowedAggTypes').patchValue(result); + this.timewindowForm.markAsDirty(); } } }, - {}, - {}, {}, false); + {maxHeight: '90vh', height: '100%'}, + {}, {}, true, () => {}, {padding: 0}); aggregationConfigPopover.tbComponentRef.instance.popoverComponent = aggregationConfigPopover; } this.cd.detectChanges(); diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index a76df1ffd1..703e8f98c7 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -126,7 +126,7 @@
{{ 'aggregation.aggregation' | translate }}
+ formControlName="type" [allowedAggregationTypes]="allowedAggTypes">
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index 2037a3578b..0ddd3d9892 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -103,6 +103,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O historyIntervalSelectionAvailable: boolean; aggregationOptionsAvailable: boolean; + allowedAggTypes: Array; + private destroy$ = new Subject(); 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.isEdit = data.isEdit; + this.allowedAggTypes = this.timewindow.allowedAggTypes; + if (!this.historyOnly) { this.timewindowTypeOptions.unshift({ name: this.translate.instant('timewindow.realtime'), @@ -366,6 +370,9 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O fixedTimewindow: timewindowFormValue.history.fixedTimewindow, quickInterval: timewindowFormValue.history.quickInterval, }}; + if (!this.timewindow.allowedAggTypes?.length) { + delete this.timewindow.allowedAggTypes; + } if (this.aggregation) { this.timewindow.aggregation = { type: timewindowFormValue.aggregation.type, @@ -508,6 +515,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O .subscribe((res) => { if (res) { this.timewindow = res; + this.allowedAggTypes = this.timewindow.allowedAggTypes; this.updateTimewindowForm(); } }); diff --git a/ui-ngx/src/app/shared/models/time/time.models.ts b/ui-ngx/src/app/shared/models/time/time.models.ts index 96093675f8..83cb5a6403 100644 --- a/ui-ngx/src/app/shared/models/time/time.models.ts +++ b/ui-ngx/src/app/shared/models/time/time.models.ts @@ -139,6 +139,7 @@ export interface Aggregation { export interface Timewindow { displayValue?: string; displayTimezoneAbbr?: string; + allowedAggTypes?: Array; hideAggregation?: boolean; hideAggInterval?: boolean; hideTimezone?: boolean; @@ -301,6 +302,9 @@ export const initModelFromDefaultTimewindow = (value: Timewindow, quickIntervalO historyOnly: boolean, timeService: TimeService): Timewindow => { const model = defaultTimewindow(timeService); if (value) { + if (value.allowedAggTypes?.length) { + model.allowedAggTypes = value.allowedAggTypes; + } model.hideAggregation = value.hideAggregation; model.hideAggInterval = value.hideAggInterval; model.hideTimezone = value.hideTimezone; @@ -429,7 +433,7 @@ export const toHistoryTimewindow = (timewindow: Timewindow, startTimeMs: number, aggType = AggregationType.AVG; limit = timeService.getMaxDatapointsLimit(); } - return { + const historyTimewindow: Timewindow = { hideAggregation: timewindow.hideAggregation || false, hideAggInterval: timewindow.hideAggInterval || false, hideTimezone: timewindow.hideTimezone || false, @@ -451,6 +455,10 @@ export const toHistoryTimewindow = (timewindow: Timewindow, startTimeMs: number, }, timezone: timewindow.timezone }; + if (timewindow.allowedAggTypes?.length) { + historyTimewindow.allowedAggTypes = timewindow.allowedAggTypes; + } + return historyTimewindow; }; export const timewindowTypeChanged = (newTimewindow: Timewindow, oldTimewindow: Timewindow): boolean => { @@ -898,6 +906,9 @@ export const createTimewindowForComparison = (subscriptionTimewindow: Subscripti export const cloneSelectedTimewindow = (timewindow: Timewindow): Timewindow => { const cloned: Timewindow = {}; + if (timewindow.allowedAggTypes?.length) { + cloned.allowedAggTypes = timewindow.allowedAggTypes; + } cloned.hideAggregation = timewindow.hideAggregation || false; cloned.hideAggInterval = timewindow.hideAggInterval || false; cloned.hideTimezone = timewindow.hideTimezone || false; 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 d4a8cffa53..6387c4a543 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -5216,7 +5216,10 @@ "hide-group-interval": "Hide grouping interval from end-users", "hide-max-values": "Hide max values 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": { "trigger": "Trigger", From 9624fd2bc990cadaf7e3ce00902ffa1c28719ca8 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 21 Oct 2024 19:23:15 +0300 Subject: [PATCH 04/32] Timewindow: update disable advanced option state on hiding interval --- .../timewindow-config-dialog.component.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts index 8ebd6395c9..618d6daf8e 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts @@ -228,6 +228,7 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On this.timewindowForm.get('realtime.hideLastInterval').enable({emitEvent: false}); this.timewindowForm.get('realtime.hideQuickInterval').enable({emitEvent: false}); } + this.updateDisableAdvancedOptionState('realtime.disableCustomInterval', value); }); this.timewindowForm.get('realtime.hideLastInterval').valueChanges.pipe( takeUntil(this.destroy$) @@ -235,6 +236,7 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On if (hideLastInterval && !this.timewindowForm.get('realtime.hideQuickInterval').value) { this.timewindowForm.get('realtime.realtimeType').setValue(RealtimeWindowType.INTERVAL); } + this.updateDisableAdvancedOptionState('realtime.disableCustomInterval', hideLastInterval); }); this.timewindowForm.get('realtime.hideQuickInterval').valueChanges.pipe( takeUntil(this.destroy$) @@ -256,6 +258,7 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On this.timewindowForm.get('history.hideQuickInterval').enable({emitEvent: false}); this.timewindowForm.get('history.hideFixedInterval').enable({emitEvent: false}); } + this.updateDisableAdvancedOptionState('history.disableCustomInterval', value); }); this.timewindowForm.get('history.hideLastInterval').valueChanges.pipe( takeUntil(this.destroy$) @@ -267,6 +270,7 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On this.timewindowForm.get('history.historyType').setValue(HistoryWindowType.INTERVAL); } } + this.updateDisableAdvancedOptionState('history.disableCustomInterval', hideLastInterval); }); this.timewindowForm.get('history.hideFixedInterval').valueChanges.pipe( takeUntil(this.destroy$) @@ -290,6 +294,7 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On } } }); + this.timewindowForm.get('hideAggregation').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe((value: boolean) => { @@ -297,6 +302,12 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On this.timewindowForm.get('allowedAggTypes').patchValue([]); } }); + this.timewindowForm.get('hideAggInterval').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value: boolean) => { + this.updateDisableAdvancedOptionState('realtime.disableCustomGroupInterval', value); + this.updateDisableAdvancedOptionState('history.disableCustomGroupInterval', value); + }); } ngOnDestroy() { @@ -304,6 +315,15 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On this.destroy$.complete(); } + private updateDisableAdvancedOptionState(controlName: string, intervalHidden: boolean) { + if (intervalHidden) { + this.timewindowForm.get(controlName).disable({emitEvent: false}); + this.timewindowForm.get(controlName).patchValue(false, {emitEvent: false}); + } else { + this.timewindowForm.get(controlName).enable({emitEvent: false}); + } + } + private updateValidators(aggType: AggregationType) { if (aggType !== AggregationType.NONE) { this.timewindowForm.get('aggregation.limit').clearValidators(); From aff0adbfa7c1de25010cd03cdf7ba4ea73cc0924 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Fri, 25 Oct 2024 18:51:05 +0300 Subject: [PATCH 05/32] Timewindow: start adding ability to customize interval options --- ui-ngx/src/app/modules/common/modules-map.ts | 2 + .../aggregation-type-select.component.scss | 6 -- ...terval-options-config-panel.component.html | 47 +++++++++++++ ...terval-options-config-panel.component.scss | 44 ++++++++++++ ...interval-options-config-panel.component.ts | 68 +++++++++++++++++++ .../time/quick-time-interval.component.html | 3 + .../time/timeinterval.component.html | 3 + .../timewindow-config-dialog.component.html | 24 +++++++ .../timewindow-config-dialog.component.ts | 47 +++++++++++++ ui-ngx/src/app/shared/shared.module.ts | 3 + .../assets/locale/locale.constant-en_US.json | 4 +- ui-ngx/src/form.scss | 10 +++ 12 files changed, 254 insertions(+), 7 deletions(-) create mode 100644 ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.html create mode 100644 ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.scss create mode 100644 ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.ts diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index e49e9de335..f93a7172af 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -337,6 +337,7 @@ import * as TimezonePanelComponent from '@shared/components/time/timezone-panel. import * as DatapointsLimitComponent from '@shared/components/time/datapoints-limit.component'; import * as AggregationTypeSelectComponent from '@shared/components/aggregation/aggregation-type-select.component'; import * as AggregationOptionsConfigComponent from '@shared/components/aggregation/aggregation-options-config-panel.component'; +import * as IntervalOptionsConfigPanelComponent from '@shared/components/time/interval-options-config-panel.component'; import { IModulesMap } from '@modules/common/modules-map.models'; import { Observable, map, of } from 'rxjs'; @@ -476,6 +477,7 @@ class ModulesMap implements IModulesMap { '@shared/components/time/datapoints-limit.component': DatapointsLimitComponent, '@shared/components/aggregation/aggregation-type-select.component': AggregationTypeSelectComponent, '@shared/components/aggregation/aggregation-options-config-panel.component': AggregationOptionsConfigComponent, + '@shared/components/time/interval-options-config-panel.component': IntervalOptionsConfigPanelComponent, '@shared/components/value-input.component': ValueInputComponent, '@shared/components/dashboard-autocomplete.component': DashboardAutocompleteComponent, '@shared/components/entity/entity-subtype-autocomplete.component': EntitySubTypeAutocompleteComponent, diff --git a/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.scss b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.scss index 4e08e7fc73..ddc2385dfa 100644 --- a/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.scss +++ b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.scss @@ -26,10 +26,4 @@ .mat-mdc-select-value { min-width: 100px; } - .mat-mdc-form-field-has-icon-suffix .mat-mdc-text-field-wrapper:has(.mat-mdc-form-field-icon-suffix:empty) { - padding-right: 12px; - } - .mat-mdc-form-field-icon-suffix:empty { - padding: 0; - } } diff --git a/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.html b/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.html new file mode 100644 index 0000000000..ce8abdd7e5 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.html @@ -0,0 +1,47 @@ + +
+
{{ 'timewindow.edit-intervals-list' | translate }}
+
{{ 'timewindow.edit-intervals-list-hint' | translate }}
+
+
+
{{"timewindow.interval" | translate }}
+
+
+ + + {{ interval.name | translate:interval.translateParams }} + + +
+
+
+ + +
+
diff --git a/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.scss b/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.scss new file mode 100644 index 0000000000..e6ffca11ee --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.scss @@ -0,0 +1,44 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .tb-interval-options-form { + height: 100%; + max-width: 600px; + + .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; + } + } + } +} + diff --git a/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.ts b/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.ts new file mode 100644 index 0000000000..53de09e28f --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.ts @@ -0,0 +1,68 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Input, OnInit } from '@angular/core'; +import { HistoryWindowType, RealtimeWindowType, TimewindowType } from '@shared/models/time/time.models'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { TbPopoverComponent } from '@shared/components/popover.component'; + +@Component({ + selector: 'tb-interval-options-config-panel', + templateUrl: './interval-options-config-panel.component.html', + styleUrls: ['./interval-options-config-panel.component.scss'] +}) +export class IntervalOptionsConfigPanelComponent implements OnInit { + + @Input() + allowedIntervals: Array; + + @Input() + intervalType: RealtimeWindowType | HistoryWindowType; + + @Input() + timewindowType: TimewindowType; + + @Input() + onClose: (result: Array | null) => void; + + @Input() + popoverComponent: TbPopoverComponent; + + intervalOptionsConfigForm: FormGroup; + + intervals = []; + + constructor(private fb: FormBuilder) {} + + ngOnInit(): void { + this.intervalOptionsConfigForm = this.fb.group({ + allowedIntervals: [this.allowedIntervals] + }); + } + + update() { + if (this.onClose) { + this.onClose([]); + } + } + + cancel() { + if (this.onClose) { + this.onClose(null); + } + } + +} diff --git a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html index 40f39118c8..8eb18ea34b 100644 --- a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html +++ b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html @@ -23,5 +23,8 @@ {{ timeIntervalTranslationMap.get(interval) | translate}} + + + diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.html b/ui-ngx/src/app/shared/components/time/timeinterval.component.html index 57e6ca0808..e9914c6068 100644 --- a/ui-ngx/src/app/shared/components/time/timeinterval.component.html +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.html @@ -25,6 +25,9 @@ {{ interval.name | translate:interval.translateParams }} + + +
+
@@ -85,6 +91,12 @@ appearance="outline" [required]="timewindowForm.get('selectedTab').value === timewindowTypes.REALTIME && timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL"> +
@@ -120,6 +132,12 @@ [disabledAdvanced]="timewindowForm.get('history.disableCustomInterval').value" [required]="timewindowForm.get('selectedTab').value === timewindowTypes.HISTORY && timewindowForm.get('history.historyType').value === historyTypes.LAST_INTERVAL"> +
@@ -153,6 +171,12 @@ appearance="outline" [required]="timewindowForm.get('selectedTab').value === timewindowTypes.HISTORY && timewindowForm.get('history.historyType').value === historyTypes.INTERVAL"> +
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts index 618d6daf8e..e85e5dbaa0 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-config-dialog.component.ts @@ -41,6 +41,7 @@ import { TbPopoverService } from '@shared/components/popover.service'; import { AggregationOptionsConfigPanelComponent } from '@shared/components/aggregation/aggregation-options-config-panel.component'; +import { IntervalOptionsConfigPanelComponent } from '@shared/components/time/interval-options-config-panel.component'; export interface TimewindowConfigDialogData { quickIntervalOnly: boolean; @@ -456,4 +457,50 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On this.cd.detectChanges(); } + configureRealtimeLastIntervalOptions($event: Event) { + const resFn = (res) => {}; + this.openIntervalOptionsConfig($event, [], resFn, RealtimeWindowType.LAST_INTERVAL); + } + + configureRealtimeQuickIntervalOptions($event: Event) { + const resFn = (res) => {}; + this.openIntervalOptionsConfig($event, [], resFn, RealtimeWindowType.INTERVAL, TimewindowType.REALTIME); + } + + configureHistoryLastIntervalOptions($event: Event) { + const resFn = (res) => {}; + this.openIntervalOptionsConfig($event, [], resFn, HistoryWindowType.LAST_INTERVAL); + } + + configureHistoryQuickIntervalOptions($event: Event) { + const resFn = (res) => {}; + this.openIntervalOptionsConfig($event, [], resFn, HistoryWindowType.INTERVAL, TimewindowType.HISTORY); + } + + private openIntervalOptionsConfig($event: Event, allowedIntervals: Array, resFn: (res) => void, + intervalType: RealtimeWindowType | HistoryWindowType, timewindowType?: TimewindowType) { + if ($event) { + $event.stopPropagation(); + } + const trigger = ($event.target || $event.srcElement || $event.currentTarget) as Element; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const intervalsConfigPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, IntervalOptionsConfigPanelComponent, ['left', 'leftTop', 'leftBottom'], true, null, + { + allowedIntervals: deepClone(allowedIntervals), + intervalType: intervalType, + timewindowType: timewindowType, + onClose: (result: Array | null) => { + intervalsConfigPopover.hide(); + resFn(result); + } + }, + {maxHeight: '90vh', height: '100%'}, + {}, {}, true, () => {}, {padding: 0}); + intervalsConfigPopover.tbComponentRef.instance.popoverComponent = intervalsConfigPopover; + } + this.cd.detectChanges(); + } } diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 2968193d43..3200fad4cd 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -220,6 +220,7 @@ import { SvgXmlComponent } from '@shared/components/svg-xml.component'; import { DatapointsLimitComponent } from '@shared/components/time/datapoints-limit.component'; import { AggregationTypeSelectComponent } from '@shared/components/aggregation/aggregation-type-select.component'; import { AggregationOptionsConfigPanelComponent } from '@shared/components/aggregation/aggregation-options-config-panel.component'; +import { IntervalOptionsConfigPanelComponent } from '@shared/components/time/interval-options-config-panel.component'; export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { return markedOptionsService; @@ -311,6 +312,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) DatapointsLimitComponent, AggregationTypeSelectComponent, AggregationOptionsConfigPanelComponent, + IntervalOptionsConfigPanelComponent, DashboardSelectComponent, DashboardSelectPanelComponent, DatetimePeriodComponent, @@ -520,6 +522,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) DatapointsLimitComponent, AggregationTypeSelectComponent, AggregationOptionsConfigPanelComponent, + IntervalOptionsConfigPanelComponent, DashboardSelectComponent, DatetimePeriodComponent, DatetimeComponent, 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 b36ef5c293..e699f75c11 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4545,7 +4545,9 @@ "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" + "allowed-aggregation-functions": "Allowed aggregation functions", + "edit-intervals-list": "Edit intervals list", + "edit-intervals-list-hint": "List of available interval options can be specified. It is possible to configure the grouping intervals list and default grouping interval." }, "tooltip": { "trigger": "Trigger", diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 2a7ee6e7eb..bd3ba3c275 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -381,6 +381,16 @@ } } } + &.mat-mdc-form-field-has-icon-suffix { + .mat-mdc-form-field-icon-suffix:empty { + padding: 0; + } + .mat-mdc-text-field-wrapper:has(.mat-mdc-form-field-icon-suffix:empty) { + &.mdc-text-field--outlined, &:not(.mdc-text-field--outlined) { + padding-right: 12px; + } + } + } .mat-mdc-text-field-wrapper { &.mdc-text-field--outlined, &:not(.mdc-text-field--outlined) { &:not(.mdc-text-field--focused):not(.mdc-text-field--disabled):not(.mdc-text-field--invalid):not(:hover) { From 7e8ea0775ea8d520ab1db3db527cdf6563e9f9ef Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 28 Oct 2024 17:43:35 +0200 Subject: [PATCH 06/32] Timewindow: display full interval options list for customization --- .../aggregation-type-select.component.html | 2 +- ...terval-options-config-panel.component.html | 18 +++++-- ...terval-options-config-panel.component.scss | 1 - ...interval-options-config-panel.component.ts | 50 +++++++++++++++++-- .../timewindow-config-dialog.component.ts | 45 +++++++++++++---- .../src/app/shared/models/time/time.models.ts | 8 +++ .../assets/locale/locale.constant-en_US.json | 3 +- 7 files changed, 105 insertions(+), 22 deletions(-) diff --git a/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.html b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.html index 5e80fc3237..205e25cdf5 100644 --- a/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.html +++ b/ui-ngx/src/app/shared/components/aggregation/aggregation-type-select.component.html @@ -17,7 +17,7 @@ --> + class="flex flex-1"> {{ label }} diff --git a/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.html b/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.html index ce8abdd7e5..afeaf2b2a2 100644 --- a/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/interval-options-config-panel.component.html @@ -15,22 +15,32 @@ limitations under the License. --> -
+
{{ 'timewindow.edit-intervals-list' | translate }}
-
{{ 'timewindow.edit-intervals-list-hint' | translate }}
+
+ {{ 'timewindow.edit-intervals-list-hint' | translate }} {{ aggregation ? ('timewindow.edit-grouping-intervals-list-hint' | translate) : '' }} +
{{"timewindow.interval" | translate }}
- + {{ interval.name | translate:interval.translateParams }}
-
+
+ +