Timewindow: remove timewindow config from widget if unused

This commit is contained in:
Ekaterina Chantsova 2025-07-22 20:44:43 +03:00
parent 35b349bd6e
commit f1c80e4379
5 changed files with 71 additions and 17 deletions

View File

@ -38,6 +38,7 @@ import {
import { deepClone, isDefined, isDefinedAndNotNull, isNotEmptyStr, isString, isUndefined } from '@core/utils'; import { deepClone, isDefined, isDefinedAndNotNull, isNotEmptyStr, isString, isUndefined } from '@core/utils';
import { import {
Datasource, Datasource,
datasourcesHasAggregation,
datasourcesHasOnlyComparisonAggregation, datasourcesHasOnlyComparisonAggregation,
DatasourceType, DatasourceType,
defaultLegendConfig, defaultLegendConfig,
@ -49,7 +50,8 @@ import {
WidgetConfigMode, WidgetConfigMode,
WidgetSize, WidgetSize,
widgetType, widgetType,
WidgetTypeDescriptor WidgetTypeDescriptor,
widgetTypeHasTimewindow
} from '@app/shared/models/widget.models'; } from '@app/shared/models/widget.models';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { AliasFilterType, EntityAlias, EntityAliasFilter } from '@app/shared/models/alias.models'; import { AliasFilterType, EntityAlias, EntityAliasFilter } from '@app/shared/models/alias.models';
@ -295,8 +297,11 @@ export class DashboardUtilsService {
widgetConfig.datasources = this.validateAndUpdateDatasources(widgetConfig.datasources); widgetConfig.datasources = this.validateAndUpdateDatasources(widgetConfig.datasources);
if (type === widgetType.latest) { if (type === widgetType.latest) {
const onlyHistoryTimewindow = datasourcesHasOnlyComparisonAggregation(widgetConfig.datasources); const onlyHistoryTimewindow = datasourcesHasOnlyComparisonAggregation(widgetConfig.datasources);
widgetConfig.timewindow = initModelFromDefaultTimewindow(widgetConfig.timewindow, true, const aggregationEnabledForKeys = datasourcesHasAggregation(widgetConfig.datasources);
onlyHistoryTimewindow, this.timeService, false); if (aggregationEnabledForKeys) {
widgetConfig.timewindow = initModelFromDefaultTimewindow(widgetConfig.timewindow, true,
onlyHistoryTimewindow, this.timeService, false);
}
} else if (type === widgetType.rpc) { } else if (type === widgetType.rpc) {
if (widgetConfig.targetDeviceAliasIds && widgetConfig.targetDeviceAliasIds.length) { if (widgetConfig.targetDeviceAliasIds && widgetConfig.targetDeviceAliasIds.length) {
widgetConfig.targetDevice = { widgetConfig.targetDevice = {
@ -346,9 +351,29 @@ export class DashboardUtilsService {
} }
} }
} }
this.removeTimewindowConfigIfUnused(widgetConfig, type);
return widgetConfig; return widgetConfig;
} }
public removeTimewindowConfigIfUnused(widgetConfig: WidgetConfig, type: widgetType) {
const widgetHasTimewindow = widgetTypeHasTimewindow(type) || (type === widgetType.latest && datasourcesHasAggregation(widgetConfig.datasources));
if (!widgetHasTimewindow || widgetConfig.useDashboardTimewindow) {
delete widgetConfig.displayTimewindow;
delete widgetConfig.timewindow;
delete widgetConfig.timewindowStyle;
if (!widgetHasTimewindow) {
delete widgetConfig.useDashboardTimewindow;
}
}
}
public prepareWidgetForSaving(widget: Widget): Widget {
this.removeTimewindowConfigIfUnused(widget.config, widget.type);
return widget;
}
public prepareWidgetForScadaLayout(widget: Widget, isScada: boolean): Widget { public prepareWidgetForScadaLayout(widget: Widget, isScada: boolean): Widget {
const config = widget.config; const config = widget.config;
config.showTitle = false; config.showTitle = false;

View File

@ -1322,6 +1322,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
} }
private addWidgetToDashboard(widget: Widget) { private addWidgetToDashboard(widget: Widget) {
this.dashboardUtils.prepareWidgetForSaving(widget);
if (this.addingLayoutCtx) { if (this.addingLayoutCtx) {
this.addWidgetToLayout(widget, this.addingLayoutCtx.id); this.addWidgetToLayout(widget, this.addingLayoutCtx.id);
this.addingLayoutCtx = null; this.addingLayoutCtx = null;
@ -1409,7 +1410,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
saveWidget() { saveWidget() {
this.editWidgetComponent.widgetFormGroup.markAsPristine(); this.editWidgetComponent.widgetFormGroup.markAsPristine();
const widget = deepClone(this.editingWidget); const widget = this.dashboardUtils.prepareWidgetForSaving(deepClone(this.editingWidget));
const widgetLayout = deepClone(this.editingWidgetLayout); const widgetLayout = deepClone(this.editingWidgetLayout);
const id = this.editingWidgetOriginal.id; const id = this.editingWidgetOriginal.id;
this.dashboardConfiguration.widgets[id] = widget; this.dashboardConfiguration.widgets[id] = widget;

View File

@ -23,11 +23,19 @@ import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { AbstractControl, UntypedFormGroup } from '@angular/forms'; import { AbstractControl, UntypedFormGroup } from '@angular/forms';
import { DataKey, DatasourceType, Widget, WidgetConfigMode, widgetType } from '@shared/models/widget.models'; import {
DataKey,
DatasourceType,
Widget,
widgetTypeCanHaveTimewindow,
WidgetConfigMode,
widgetType
} from '@shared/models/widget.models';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { isDefinedAndNotNull } from '@core/utils'; import { isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils';
import { IAliasController } from '@core/api/widget-api.models'; import { IAliasController } from '@core/api/widget-api.models';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { initModelFromDefaultTimewindow } from '@shared/models/time/time.models';
export type WidgetConfigCallbacks = DatasourceCallbacks & WidgetActionCallbacks; export type WidgetConfigCallbacks = DatasourceCallbacks & WidgetActionCallbacks;
@ -107,6 +115,11 @@ export abstract class BasicWidgetConfigComponent extends PageComponent implement
if (this.isAdd) { if (this.isAdd) {
this.setupDefaults(widgetConfig); this.setupDefaults(widgetConfig);
} }
if (widgetTypeCanHaveTimewindow(widgetConfig.widgetType) && isUndefinedOrNull(widgetConfig.config.timewindow)) {
widgetConfig.config.timewindow = initModelFromDefaultTimewindow(null,
widgetConfig.widgetType === widgetType.latest, false, this.widgetConfigComponent.timeService,
widgetConfig.widgetType === widgetType.timeseries);
}
this.onConfigSet(widgetConfig); this.onConfigSet(widgetConfig);
this.updateValidators(false); this.updateValidators(false);
for (const trigger of this.validatorTriggers()) { for (const trigger of this.validatorTriggers()) {

View File

@ -38,6 +38,7 @@ import {
TargetDevice, TargetDevice,
targetDeviceValid, targetDeviceValid,
Widget, Widget,
widgetTypeCanHaveTimewindow,
WidgetConfigMode, WidgetConfigMode,
widgetType widgetType
} from '@shared/models/widget.models'; } from '@shared/models/widget.models';
@ -53,7 +54,7 @@ import {
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { WidgetConfigComponentData } from '@home/models/widget-component.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { deepClone, genNextLabel, isDefined, isObject } from '@app/core/utils'; import { deepClone, genNextLabel, isDefined, isDefinedAndNotNull, isObject } from '@app/core/utils';
import { alarmFields, AlarmSearchStatus } from '@shared/models/alarm.models'; import { alarmFields, AlarmSearchStatus } from '@shared/models/alarm.models';
import { IAliasController } from '@core/api/widget-api.models'; import { IAliasController } from '@core/api/widget-api.models';
import { EntityAlias } from '@shared/models/alias.models'; import { EntityAlias } from '@shared/models/alias.models';
@ -84,6 +85,8 @@ import { DataKeySettingsFunction } from '@home/components/widget/lib/settings/co
import { defaultFormProperties, FormProperty } from '@shared/models/dynamic-form.models'; import { defaultFormProperties, FormProperty } from '@shared/models/dynamic-form.models';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { WidgetService } from '@core/http/widget.service'; import { WidgetService } from '@core/http/widget.service';
import { TimeService } from '@core/services/time.service';
import { initModelFromDefaultTimewindow } from '@shared/models/time/time.models';
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
@Component({ @Component({
@ -201,6 +204,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private utils: UtilsService, private utils: UtilsService,
private entityService: EntityService, private entityService: EntityService,
public timeService: TimeService,
private dialog: MatDialog, private dialog: MatDialog,
public translate: TranslateService, public translate: TranslateService,
private fb: UntypedFormBuilder, private fb: UntypedFormBuilder,
@ -366,16 +370,16 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
this.dataSettings = this.fb.group({}); this.dataSettings = this.fb.group({});
this.targetDeviceSettings = this.fb.group({}); this.targetDeviceSettings = this.fb.group({});
this.advancedSettings = this.fb.group({}); this.advancedSettings = this.fb.group({});
if (this.widgetType === widgetType.timeseries || this.widgetType === widgetType.alarm || this.widgetType === widgetType.latest) { if (widgetTypeCanHaveTimewindow(this.widgetType)) {
this.dataSettings.addControl('timewindowConfig', this.fb.control({ this.dataSettings.addControl('timewindowConfig', this.fb.control({
useDashboardTimewindow: true, useDashboardTimewindow: this.widgetType !== widgetType.latest,
displayTimewindow: true, displayTimewindow: this.widgetType !== widgetType.latest,
timewindow: null, timewindow: null,
timewindowStyle: null timewindowStyle: null
})); }));
if (this.widgetType === widgetType.alarm) { }
this.dataSettings.addControl('alarmFilterConfig', this.fb.control(null)); if (this.widgetType === widgetType.alarm) {
} this.dataSettings.addControl('alarmFilterConfig', this.fb.control(null));
} }
if (this.modelValue.isDataEnabled) { if (this.modelValue.isDataEnabled) {
if (this.widgetType !== widgetType.rpc && if (this.widgetType !== widgetType.rpc &&
@ -529,14 +533,17 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
}, },
{emitEvent: false} {emitEvent: false}
); );
if (this.widgetType === widgetType.timeseries || this.widgetType === widgetType.alarm || this.widgetType === widgetType.latest) { if (widgetTypeCanHaveTimewindow(this.widgetType)) {
const useDashboardTimewindow = isDefined(config.useDashboardTimewindow) ? const useDashboardTimewindow = isDefined(config.useDashboardTimewindow) ?
config.useDashboardTimewindow : true; config.useDashboardTimewindow : this.widgetType !== widgetType.latest;
this.dataSettings.get('timewindowConfig').patchValue({ this.dataSettings.get('timewindowConfig').patchValue({
useDashboardTimewindow, useDashboardTimewindow,
displayTimewindow: isDefined(config.displayTimewindow) ? displayTimewindow: isDefined(config.displayTimewindow) ?
config.displayTimewindow : true, config.displayTimewindow : this.widgetType !== widgetType.latest,
timewindow: config.timewindow, timewindow: isDefinedAndNotNull(config.timewindow)
? config.timewindow
: initModelFromDefaultTimewindow(null, this.widgetType === widgetType.latest, this.onlyHistoryTimewindow(),
this.timeService, this.widgetType === widgetType.timeseries),
timewindowStyle: config.timewindowStyle timewindowStyle: config.timewindowStyle
}, {emitEvent: false}); }, {emitEvent: false});
} }

View File

@ -489,6 +489,14 @@ export const targetDeviceValid = (targetDevice?: TargetDevice): boolean =>
((targetDevice.type === TargetDeviceType.device && !!targetDevice.deviceId) || ((targetDevice.type === TargetDeviceType.device && !!targetDevice.deviceId) ||
(targetDevice.type === TargetDeviceType.entity && !!targetDevice.entityAliasId)); (targetDevice.type === TargetDeviceType.entity && !!targetDevice.entityAliasId));
export const widgetTypeHasTimewindow = (type: widgetType): boolean => {
return type === widgetType.timeseries || type === widgetType.alarm;
}
export const widgetTypeCanHaveTimewindow = (type: widgetType): boolean => {
return widgetTypeHasTimewindow(type) || type === widgetType.latest;
}
export const datasourcesHasAggregation = (datasources?: Array<Datasource>): boolean => { export const datasourcesHasAggregation = (datasources?: Array<Datasource>): boolean => {
if (datasources) { if (datasources) {
const foundDatasource = datasources.find(datasource => { const foundDatasource = datasources.find(datasource => {