diff --git a/ui-ngx/src/app/core/services/dashboard-utils.service.ts b/ui-ngx/src/app/core/services/dashboard-utils.service.ts index 328f96d905..64500b8b43 100644 --- a/ui-ngx/src/app/core/services/dashboard-utils.service.ts +++ b/ui-ngx/src/app/core/services/dashboard-utils.service.ts @@ -35,7 +35,15 @@ import { LayoutType, WidgetLayout } from '@shared/models/dashboard.models'; -import { deepClone, isDefined, isDefinedAndNotNull, isNotEmptyStr, isString, isUndefined } from '@core/utils'; +import { + deepClean, + deepClone, + isDefined, + isDefinedAndNotNull, + isNotEmptyStr, + isString, + isUndefined +} from '@core/utils'; import { Datasource, datasourcesHasAggregation, @@ -353,7 +361,7 @@ export class DashboardUtilsService { } } } - return widgetConfig; + return deepClean(widgetConfig, {cleanKeys: ['_hash'], cleanOnlyKey: true}); } private removeTimewindowConfigIfUnused(widget: Widget) { @@ -380,6 +388,7 @@ export class DashboardUtilsService { public prepareWidgetForSaving(widget: Widget): Widget { this.removeTimewindowConfigIfUnused(widget); + widget = deepClean(widget, {cleanKeys: ['_hash'], cleanOnlyKey: true}); return widget; } diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index c68f26d56f..2640845264 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -34,7 +34,7 @@ import { } from '@shared/models/js-function.models'; import { DomSanitizer } from '@angular/platform-browser'; import { SecurityContext } from '@angular/core'; -import { AbstractControl, ValidationErrors, Validators } from '@angular/forms'; +import { AbstractControl, ValidationErrors } from '@angular/forms'; const varsRegex = /\${([^}]*)}/g; const emailRegex = /^[A-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i; @@ -795,31 +795,72 @@ export function deepTrim(obj: T): T { }, (Array.isArray(obj) ? [] : {}) as T); } +const isValidValue = (value: any): boolean => { + return ( + value !== undefined && + value !== null && + value !== '' && + !Number.isNaN(value) + ); +}; + export function deepClean | any[]>(obj: T, { - cleanKeys = [] + cleanKeys = [], + cleanOnlyKey = false } = {}): T { - return _.transform(obj, (result, value, key) => { - if (cleanKeys.includes(key)) { - return; - } - if (Array.isArray(value) || isLiteralObject(value)) { - value = deepClean(value, {cleanKeys}); - } - if(isLiteralObject(value) && isEmpty(value)) { - return; - } - if (Array.isArray(value) && !value.length) { - return; - } - if (value === undefined || value === null || value === '' || Number.isNaN(value)) { - return; + const keysToRemove = new Set(cleanKeys); + + const clean = (input: any): any => { + if (Array.isArray(input)) { + const result: any[] = []; + for (const item of input) { + const value = clean(item); + + if (cleanOnlyKey) { + result.push(value); + continue; + } + + if (isValidValue(value)) { + const isEmptyArray = Array.isArray(value) && value.length === 0; + const isEmptyObj = isLiteralObject(value) && Object.keys(value).length === 0; + + if (!isEmptyArray && !isEmptyObj) { + result.push(value); + } + } + } + return result; } - if (Array.isArray(result)) { - return result.push(value); + if (isLiteralObject(input)) { + const result: Record = {}; + + for (const key in input) { + if (keysToRemove.has(key)) continue; + + const value = clean(input[key]); + + if (cleanOnlyKey) { + result[key] = value; + continue; + } + + if (isValidValue(value)) { + const isEmptyArray = Array.isArray(value) && value.length === 0; + const isEmptyObj = isLiteralObject(value) && Object.keys(value).length === 0; + + if (!isEmptyArray && !isEmptyObj) { + result[key] = value; + } + } + } + return result; } - result[key] = value; - }); + return input; + }; + + return clean(obj); } export function generateSecret(length?: number): string { diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts index fa9f0a718a..f7bb87455a 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts @@ -37,7 +37,7 @@ import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; import { TranslateService } from '@ngx-translate/core'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { DialogService } from '@core/services/dialog.service'; -import { deepClean, deepClone, isUndefined } from '@core/utils'; +import { deepClone, isUndefined } from '@core/utils'; import { EntityAliasDialogComponent, EntityAliasDialogData } from './entity-alias-dialog.component'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -263,7 +263,7 @@ export class EntityAliasesDialogComponent extends DialogComponent