Timewindow: apply default grouping interval option for interval

This commit is contained in:
Ekaterina Chantsova 2024-11-29 12:10:55 +02:00
parent 7277a0939c
commit 7aae331ac4
12 changed files with 215 additions and 105 deletions

View File

@ -337,7 +337,6 @@ import * as DatapointsLimitComponent from '@shared/components/time/datapoints-li
import * as AggregationTypeSelectComponent from '@shared/components/time/aggregation/aggregation-type-select.component';
import * as AggregationOptionsConfigComponent from '@shared/components/time/aggregation/aggregation-options-config-panel.component';
import * as IntervalOptionsConfigPanelComponent from '@shared/components/time/interval-options-config-panel.component';
import * as TimeIntervalsListComponent from '@shared/components/time/time-intervals-list.component';
import { IModulesMap } from '@modules/common/modules-map.models';
import { Observable, map, of } from 'rxjs';
@ -478,7 +477,6 @@ class ModulesMap implements IModulesMap {
'@shared/components/time/aggregation/aggregation-type-select.component': AggregationTypeSelectComponent,
'@shared/components/time/aggregation/aggregation-options-config-panel.component': AggregationOptionsConfigComponent,
'@shared/components/time/interval-options-config-panel.component': IntervalOptionsConfigPanelComponent,
'@shared/components/time/time-intervals-list.component': TimeIntervalsListComponent,
'@shared/components/value-input.component': ValueInputComponent,
'@shared/components/dashboard-autocomplete.component': DashboardAutocompleteComponent,
'@shared/components/entity/entity-subtype-autocomplete.component': EntitySubTypeAutocompleteComponent,

View File

@ -0,0 +1,37 @@
<!--
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.
-->
<section class="flex flex-1 flex-row gap-2" [formGroup]="timeintervalFormGroup">
<mat-form-field class="flex flex-1" [class]="{'tb-inline-field': appearance === 'outline'}"
[subscriptSizing]="subscriptSizing" [appearance]="appearance">
<mat-select formControlName="aggIntervals" multiple
placeholder="{{ 'timewindow.all' | translate }}">
<mat-option *ngFor="let interval of allIntervals" [value]="interval.value">
{{ interval.name | translate:interval.translateParams }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="flex flex-1" [class]="{'tb-inline-field': appearance === 'outline'}"
[subscriptSizing]="subscriptSizing" [appearance]="appearance">
<mat-select formControlName="defaultAggInterval"
placeholder="{{ 'action.set' | translate }}">
<mat-option *ngFor="let interval of selectedIntervals" [value]="interval.value">
{{ interval.name | translate:interval.translateParams }}
</mat-option>
</mat-select>
</mat-form-field>
</section>

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../../scss/constants';
@import '../../../../../scss/constants';
:host {

View File

@ -19,23 +19,28 @@ import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from
import { TimeService } from '@core/services/time.service';
import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field';
import { coerceBoolean, coerceNumber } from '@shared/decorators/coercion';
import { Interval, TimeInterval } from '@shared/models/time/time.models';
import {
Interval,
intervalValuesToTimeIntervals,
TimeInterval,
TimewindowAggIntervalOptions
} from '@shared/models/time/time.models';
import { isDefined } from '@core/utils';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'tb-time-intervals-list',
templateUrl: './time-intervals-list.component.html',
styleUrls: ['./time-intervals-list.component.scss'],
selector: 'tb-grouping-interval-options',
templateUrl: './grouping-interval-options.component.html',
styleUrls: ['./grouping-interval-options.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TimeIntervalsListComponent),
useExisting: forwardRef(() => GroupingIntervalOptionsComponent),
multi: true
}
]
})
export class TimeIntervalsListComponent implements OnInit, ControlValueAccessor {
export class GroupingIntervalOptionsComponent implements OnInit, ControlValueAccessor {
@Input()
@coerceNumber()
@ -45,16 +50,6 @@ export class TimeIntervalsListComponent implements OnInit, ControlValueAccessor
@coerceNumber()
max: number;
@Input() predefinedName: string;
@Input()
@coerceBoolean()
setAllIfEmpty = false;
@Input()
@coerceBoolean()
returnEmptyIfAllSet = false;
@Input()
@coerceBoolean()
useCalendarIntervals = false;
@ -69,10 +64,11 @@ export class TimeIntervalsListComponent implements OnInit, ControlValueAccessor
allIntervals: Array<TimeInterval>;
allIntervalValues: Array<Interval>;
selectedIntervals: Array<TimeInterval>;
timeintervalFormGroup: FormGroup;
private modelValue: Array<Interval>;
private modelValue: TimewindowAggIntervalOptions;
private rendered = false;
private propagateChangeValue: any;
@ -83,9 +79,13 @@ export class TimeIntervalsListComponent implements OnInit, ControlValueAccessor
constructor(private timeService: TimeService,
private fb: FormBuilder) {
this.timeintervalFormGroup = this.fb.group({
intervals: [ [] ]
aggIntervals: [ [] ],
defaultAggInterval: [ null ],
});
this.timeintervalFormGroup.get('intervals').valueChanges.pipe(
this.timeintervalFormGroup.get('aggIntervals').valueChanges.pipe(
takeUntilDestroyed()
).subscribe(selectedIntervalValues => this.setSelectedIntervals(selectedIntervalValues));
this.timeintervalFormGroup.valueChanges.pipe(
takeUntilDestroyed()
).subscribe(() => this.updateView());
}
@ -113,10 +113,11 @@ export class TimeIntervalsListComponent implements OnInit, ControlValueAccessor
}
}
writeValue(intervals: Array<Interval>): void {
this.modelValue = intervals;
writeValue(intervalOptions: TimewindowAggIntervalOptions): void {
this.modelValue = intervalOptions;
this.rendered = true;
this.setIntervals(this.modelValue);
this.timeintervalFormGroup.get('defaultAggInterval').patchValue(intervalOptions.defaultAggInterval, { emitEvent: false });
this.setIntervals(intervalOptions.aggIntervals);
}
private updateIntervalsList() {
@ -125,24 +126,41 @@ export class TimeIntervalsListComponent implements OnInit, ControlValueAccessor
}
private setIntervals(intervals: Array<Interval>) {
this.timeintervalFormGroup.get('intervals').patchValue(
(this.setAllIfEmpty && !intervals?.length) ? this.allIntervalValues : intervals,
const selectedIntervals = !intervals?.length ? this.allIntervalValues : intervals;
this.timeintervalFormGroup.get('aggIntervals').patchValue(
selectedIntervals,
{emitEvent: false});
this.setSelectedIntervals(selectedIntervals);
}
private setSelectedIntervals(selectedIntervalValues: Array<Interval>) {
if (!selectedIntervalValues.length || selectedIntervalValues.length === this.allIntervalValues.length) {
this.selectedIntervals = this.allIntervals;
} else {
this.selectedIntervals = intervalValuesToTimeIntervals(selectedIntervalValues);
}
const defaultInterval: Interval = this.timeintervalFormGroup.get('defaultAggInterval').value;
if (defaultInterval && !selectedIntervalValues.includes(defaultInterval)) {
this.timeintervalFormGroup.get('defaultAggInterval').patchValue(null);
}
}
private updateView() {
if (!this.rendered) {
return;
}
let value: Array<Interval>;
const intervals: Array<Interval> = this.timeintervalFormGroup.get('intervals').value;
let selectedIntervals: Array<Interval>;
const intervals: Array<Interval> = this.timeintervalFormGroup.get('aggIntervals').value;
if (!this.returnEmptyIfAllSet || intervals.length < this.allIntervals.length) {
value = intervals;
if (intervals.length < this.allIntervals.length) {
selectedIntervals = intervals;
} else {
value = [];
selectedIntervals = [];
}
this.modelValue = value;
this.modelValue = {
aggIntervals: selectedIntervals,
defaultAggInterval: this.timeintervalFormGroup.get('defaultAggInterval').value
};
this.propagateChange(this.modelValue);
}

View File

@ -25,8 +25,8 @@
<div class="tb-form-table-header">
<div class="tb-form-table-header-cell tb-interval">{{"timewindow.interval" | translate }}</div>
<ng-container *ngIf="aggregation">
<div class="tb-form-table-header-cell tb-agg-interval">{{"timewindow.allowed-agg-intervals" | translate }}</div>
<div class="tb-form-table-header-cell tb-agg-interval">{{"timewindow.default-agg-interval" | translate }}</div>
<div class="tb-form-table-header-cell tb-agg-interval-header">{{"timewindow.allowed-agg-intervals" | translate }}</div>
<div class="tb-form-table-header-cell tb-agg-interval-header">{{"timewindow.default-agg-interval" | translate }}</div>
</ng-container>
</div>
<div class="tb-form-table-body" formArrayName="intervals">
@ -39,29 +39,15 @@
</div>
<ng-container *ngIf="aggregation">
<div class="tb-form-table-row-cell tb-agg-interval">
<tb-time-intervals-list
<tb-grouping-interval-options
class="tb-inline-field"
formControlName="aggIntervals"
formControlName="aggIntervalsConfig"
[min]="minAggInterval(interval.get('value').value)"
[max]="maxAggInterval(interval.get('value').value)"
useCalendarIntervals
subscriptSizing="dynamic"
appearance="outline"
setAllIfEmpty
returnEmptyIfAllSet>
</tb-time-intervals-list>
</div>
<div class="tb-form-table-row-cell tb-agg-interval">
<tb-timeinterval
class="tb-inline-field"
formControlName="defaultAggInterval"
[min]="minAggInterval(interval.get('value').value)"
[max]="maxAggInterval(interval.get('value').value)"
useCalendarIntervals
subscriptSizing="dynamic"
appearance="outline"
disabledAdvanced>
</tb-timeinterval>
appearance="outline">
</tb-grouping-interval-options>
</div>
</ng-container>
</div>

View File

@ -30,12 +30,16 @@
flex: 1 1 30%;
}
&.tb-agg-interval {
flex: 1 1 35%;
width: 35%;
max-width: 35%;
flex: 1 1 70%;
width: 70%;
max-width: 70%;
}
}
.tb-form-table-header-cell.tb-agg-interval-header {
flex: 1 1 35%;
}
.tb-form-hint {
flex-shrink: 0;
}

View File

@ -100,26 +100,26 @@ export class IntervalOptionsConfigPanelComponent implements OnInit {
const intervalControls: Array<AbstractControl> = [];
for (const interval of this.allIntervals) {
const intervalConfig: TimewindowAggIntervalOptions = this.aggIntervalsConfig?.hasOwnProperty(interval.value)
? this.allIntervalValues[interval.value]
? this.aggIntervalsConfig[interval.value]
: null;
const intervalEnabled = this.allowedIntervals?.length ? this.allowedIntervals.includes(interval.value) : true;
const intervalEnabled = this.allowedIntervals?.length ? this.allowedIntervals.includes(interval.value) : false;
const intervalControl = this.fb.group({
name: [this.translate.instant(interval.name, interval.translateParams)],
value: [interval.value],
enabled: [intervalEnabled],
aggIntervals: [{value: intervalConfig ? intervalConfig.aggIntervals : [], disabled: !(intervalEnabled && this.aggregation)}],
defaultAggInterval: [{value: intervalConfig ? intervalConfig.defaultAggInterval : null, disabled: !(intervalEnabled && this.aggregation)}],
aggIntervalsConfig: [{value: {
aggIntervals: intervalConfig?.aggIntervals ? intervalConfig.aggIntervals : [],
defaultAggInterval: intervalConfig?.defaultAggInterval ? intervalConfig.defaultAggInterval : null
}, disabled: !(intervalEnabled && this.aggregation)}]
});
if (this.aggregation) {
intervalControl.get('enabled').valueChanges.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe((intervalEnabled) => {
if (intervalEnabled) {
intervalControl.get('aggIntervals').enable({emitEvent: false});
intervalControl.get('defaultAggInterval').enable({emitEvent: false});
intervalControl.get('aggIntervalsConfig').enable({emitEvent: false});
} else {
intervalControl.get('aggIntervals').disable({emitEvent: false});
intervalControl.get('defaultAggInterval').disable({emitEvent: false});
intervalControl.get('aggIntervalsConfig').disable({emitEvent: false});
}
});
}
@ -160,19 +160,18 @@ export class IntervalOptionsConfigPanelComponent implements OnInit {
for (const interval of intervalOptionsConfig) {
if (interval.enabled) {
allowedIntervals.push(interval.value);
if (this.aggregation && (interval.aggIntervals.length || interval.defaultAggInterval)) {
if (this.aggregation && (interval.aggIntervalsConfig.aggIntervals.length || interval.aggIntervalsConfig.defaultAggInterval)) {
const intervalParams: TimewindowAggIntervalOptions = {};
if (interval.aggIntervals.length) {
intervalParams.aggIntervals = interval.aggIntervals;
if (interval.aggIntervalsConfig.aggIntervals.length) {
intervalParams.aggIntervals = interval.aggIntervalsConfig.aggIntervals;
}
if (interval.defaultAggInterval) {
intervalParams.defaultAggInterval = interval.defaultAggInterval;
if (interval.aggIntervalsConfig.defaultAggInterval) {
intervalParams.defaultAggInterval = interval.aggIntervalsConfig.defaultAggInterval;
}
aggIntervalsConfig[interval.value] = intervalParams;
}
}
}
console.log(aggIntervalsConfig);
this.onClose({
// if full list selected returns empty for optimization
allowedIntervals: allowedIntervals?.length < this.allIntervals.length ? allowedIntervals : [],
@ -192,8 +191,10 @@ export class IntervalOptionsConfigPanelComponent implements OnInit {
for (const interval of intervalControls) {
interval.patchValue({
enabled: true,
aggIntervalsConfig: {
aggIntervals: [],
defaultAggInterval: null,
defaultAggInterval: null
}
});
}
this.intervalOptionsConfigForm.markAsDirty();

View File

@ -1,27 +0,0 @@
<!--
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.
-->
<section class="flex flex-1 flex-row" [formGroup]="timeintervalFormGroup">
<mat-form-field class="flex flex-1" [subscriptSizing]="subscriptSizing" [appearance]="appearance">
<mat-label *ngIf="predefinedName" translate>{{ predefinedName }}</mat-label>
<mat-select formControlName="intervals" multiple>
<mat-option *ngFor="let interval of allIntervals" [value]="interval.value">
{{ interval.name | translate:interval.translateParams }}
</mat-option>
</mat-select>
</mat-form-field>
</section>

View File

@ -26,7 +26,7 @@ import {
realtimeAllowedAggIntervals,
RealtimeWindowType,
realtimeWindowTypeTranslations,
Timewindow,
Timewindow, TimewindowAggIntervalsConfig,
TimewindowType,
updateFormValuesOnTimewindowTypeChange
} from '@shared/models/time/time.models';
@ -235,6 +235,55 @@ export class TimewindowConfigDialogComponent extends PageComponent implements On
? this.timewindow.hideTimezone : false ]
});
this.timewindowForm.get('realtime.timewindowMs').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((timewindowMs: number) => {
const lastAggIntervalsConfig:TimewindowAggIntervalsConfig =
this.timewindowForm.get('realtime.advancedParams.lastAggIntervalsConfig').value;
if (lastAggIntervalsConfig?.hasOwnProperty(timewindowMs) &&
lastAggIntervalsConfig[timewindowMs].defaultAggInterval) {
this.timewindowForm.get('realtime.interval').patchValue(
lastAggIntervalsConfig[timewindowMs].defaultAggInterval, {emitEvent: false}
);
}
});
this.timewindowForm.get('realtime.quickInterval').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((quickInterval: number) => {
const quickAggIntervalsConfig:TimewindowAggIntervalsConfig =
this.timewindowForm.get('realtime.advancedParams.quickAggIntervalsConfig').value;
if (quickAggIntervalsConfig?.hasOwnProperty(quickInterval) &&
quickAggIntervalsConfig[quickInterval].defaultAggInterval) {
this.timewindowForm.get('realtime.interval').patchValue(
quickAggIntervalsConfig[quickInterval].defaultAggInterval, {emitEvent: false}
);
}
});
this.timewindowForm.get('history.timewindowMs').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((timewindowMs: number) => {
const lastAggIntervalsConfig:TimewindowAggIntervalsConfig =
this.timewindowForm.get('history.advancedParams.lastAggIntervalsConfig').value;
if (lastAggIntervalsConfig?.hasOwnProperty(timewindowMs) &&
lastAggIntervalsConfig[timewindowMs].defaultAggInterval) {
this.timewindowForm.get('history.interval').patchValue(
lastAggIntervalsConfig[timewindowMs].defaultAggInterval, {emitEvent: false}
);
}
});
this.timewindowForm.get('history.quickInterval').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((quickInterval: number) => {
const quickAggIntervalsConfig:TimewindowAggIntervalsConfig =
this.timewindowForm.get('history.advancedParams.quickAggIntervalsConfig').value;
if (quickAggIntervalsConfig?.hasOwnProperty(quickInterval) &&
quickAggIntervalsConfig[quickInterval].defaultAggInterval) {
this.timewindowForm.get('history.interval').patchValue(
quickAggIntervalsConfig[quickInterval].defaultAggInterval, {emitEvent: false}
);
}
});
this.updateValidators(this.timewindowForm.get('aggregation.type').value);
this.timewindowForm.get('aggregation.type').valueChanges.pipe(
takeUntil(this.destroy$)

View File

@ -298,6 +298,48 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit, O
disabled: hideTimezone
}]
});
this.timewindowForm.get('realtime.timewindowMs').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((timewindowMs: number) => {
if (this.realtimeAdvancedParams?.lastAggIntervalsConfig?.hasOwnProperty(timewindowMs) &&
this.realtimeAdvancedParams.lastAggIntervalsConfig[timewindowMs].defaultAggInterval) {
this.timewindowForm.get('realtime.interval').patchValue(
this.realtimeAdvancedParams.lastAggIntervalsConfig[timewindowMs].defaultAggInterval, {emitEvent: false}
);
}
});
this.timewindowForm.get('realtime.quickInterval').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((quickInterval: number) => {
if (this.realtimeAdvancedParams?.quickAggIntervalsConfig?.hasOwnProperty(quickInterval) &&
this.realtimeAdvancedParams.quickAggIntervalsConfig[quickInterval].defaultAggInterval) {
this.timewindowForm.get('realtime.interval').patchValue(
this.realtimeAdvancedParams.quickAggIntervalsConfig[quickInterval].defaultAggInterval, {emitEvent: false}
);
}
});
this.timewindowForm.get('history.timewindowMs').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((timewindowMs: number) => {
if (this.historyAdvancedParams?.lastAggIntervalsConfig?.hasOwnProperty(timewindowMs) &&
this.historyAdvancedParams.lastAggIntervalsConfig[timewindowMs].defaultAggInterval) {
this.timewindowForm.get('history.interval').patchValue(
this.historyAdvancedParams.lastAggIntervalsConfig[timewindowMs].defaultAggInterval, {emitEvent: false}
);
}
});
this.timewindowForm.get('history.quickInterval').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((quickInterval: number) => {
if (this.historyAdvancedParams?.quickAggIntervalsConfig?.hasOwnProperty(quickInterval) &&
this.historyAdvancedParams.quickAggIntervalsConfig[quickInterval].defaultAggInterval) {
this.timewindowForm.get('history.interval').patchValue(
this.historyAdvancedParams.quickAggIntervalsConfig[quickInterval].defaultAggInterval, {emitEvent: false}
);
}
});
this.updateValidators(this.timewindowForm.get('aggregation.type').value);
this.timewindowForm.get('aggregation.type').valueChanges.pipe(
takeUntil(this.destroy$)

View File

@ -221,7 +221,7 @@ import { DatapointsLimitComponent } from '@shared/components/time/datapoints-lim
import { AggregationTypeSelectComponent } from '@shared/components/time/aggregation/aggregation-type-select.component';
import { AggregationOptionsConfigPanelComponent } from '@shared/components/time/aggregation/aggregation-options-config-panel.component';
import { IntervalOptionsConfigPanelComponent } from '@shared/components/time/interval-options-config-panel.component';
import { TimeIntervalsListComponent } from '@shared/components/time/time-intervals-list.component';
import { GroupingIntervalOptionsComponent } from '@shared/components/time/aggregation/grouping-interval-options.component';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService;
@ -307,7 +307,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
TimewindowPanelComponent,
TimewindowConfigDialogComponent,
TimeintervalComponent,
TimeIntervalsListComponent,
GroupingIntervalOptionsComponent,
TimezoneComponent,
TimezonePanelComponent,
QuickTimeIntervalComponent,
@ -518,7 +518,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
TimewindowPanelComponent,
TimewindowConfigDialogComponent,
TimeintervalComponent,
TimeIntervalsListComponent,
GroupingIntervalOptionsComponent,
TimezoneComponent,
TimezonePanelComponent,
QuickTimeIntervalComponent,

View File

@ -80,7 +80,8 @@
"clear": "Clear",
"upload": "Upload",
"delete-anyway": "Delete anyway",
"delete-selected": "Delete selected"
"delete-selected": "Delete selected",
"set": "Set"
},
"aggregation": {
"aggregation": "Aggregation",
@ -4712,7 +4713,8 @@
"allowed-agg-intervals": "Allowed grouping intervals",
"default-agg-interval": "Default grouping interval",
"edit-intervals-list-hint": "List of available interval options can be specified.",
"edit-grouping-intervals-list-hint": "It is possible to configure the grouping intervals list and default grouping interval."
"edit-grouping-intervals-list-hint": "It is possible to configure the grouping intervals list and default grouping interval.",
"all": "All"
},
"tooltip": {
"trigger": "Trigger",