diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index 67e11430bd..0e559efd46 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -127,6 +127,7 @@ import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.IntervalType; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.mobile.app.MobileApp; import org.thingsboard.server.common.data.mobile.bundle.MobileAppBundle; @@ -2476,7 +2477,12 @@ public class RestClient implements Closeable { return getTimeseries(entityId, keys, interval, agg, sortOrder != null ? sortOrder.getDirection() : null, pageLink.getStartTime(), pageLink.getEndTime(), 100, useStrictDataTypes); } + @Deprecated public List getTimeseries(EntityId entityId, List keys, Long interval, Aggregation agg, SortOrder.Direction sortOrder, Long startTime, Long endTime, Integer limit, boolean useStrictDataTypes) { + return getTimeseries(entityId, keys, interval, null, null, agg, sortOrder, startTime, endTime, limit, useStrictDataTypes); + } + + public List getTimeseries(EntityId entityId, List keys, Long interval, IntervalType intervalType, String timeZone, Aggregation agg, SortOrder.Direction sortOrder, Long startTime, Long endTime, Integer limit, boolean useStrictDataTypes) { Map params = new HashMap<>(); params.put("entityType", entityId.getEntityType().name()); params.put("entityId", entityId.getId().toString()); @@ -2490,6 +2496,16 @@ public class RestClient implements Closeable { StringBuilder urlBuilder = new StringBuilder(baseURL); urlBuilder.append("/api/plugins/telemetry/{entityType}/{entityId}/values/timeseries?keys={keys}&interval={interval}&limit={limit}&agg={agg}&useStrictDataTypes={useStrictDataTypes}&orderBy={orderBy}"); + if (intervalType != null) { + urlBuilder.append("&intervalType={intervalType}"); + params.put("intervalType", intervalType.name()); + } + + if (timeZone != null) { + urlBuilder.append("&timeZone={timeZone}"); + params.put("timeZone", timeZone); + } + if (startTime != null) { urlBuilder.append("&startTs={startTs}"); params.put("startTs", String.valueOf(startTime)); diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 353727e006..0fc948ac96 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -958,3 +958,28 @@ export const unwrapModule = (module: any) : any => { return module; } }; + +export const trimDefaultValues = (input: Record, defaults: Record): Record => { + const result: Record = {}; + + for (const key in input) { + if (!(key in defaults)) { + result[key] = input[key]; + } else if (typeof defaults[key] === 'object' && defaults[key] !== null && typeof input[key] === 'object' && input[key] !== null) { + const subPatch = trimDefaultValues(input[key], defaults[key]); + if (Object.keys(subPatch).length > 0) { + result[key] = subPatch; + } + } else if (defaults[key] !== input[key]) { + result[key] = input[key]; + } + } + + for (const key in defaults) { + if (!(key in input)) { + delete result[key]; + } + } + + return result; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/dynamic-form/dynamic-form.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/dynamic-form/dynamic-form.component.ts index 5f6682ea90..26c89ca4cb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/dynamic-form/dynamic-form.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/dynamic-form/dynamic-form.component.ts @@ -37,7 +37,7 @@ import { } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { isDefinedAndNotNull, mergeDeep } from '@core/utils'; +import { isDefinedAndNotNull, mergeDeep, trimDefaultValues } from '@core/utils'; import { defaultFormProperties, FormProperty, @@ -106,10 +106,16 @@ export class DynamicFormComponent implements OnInit, OnChanges, ControlValueAcce @coerceBoolean() noBorder = false; + @Input() + @coerceBoolean() + trimDefaults = false; + private modelValue: {[id: string]: any}; private propagateChange = null; + private defaults: {[id: string]: any}; + private validatorTriggers: string[]; public propertiesFormGroup: UntypedFormGroup; @@ -180,11 +186,13 @@ export class DynamicFormComponent implements OnInit, OnChanges, ControlValueAcce private loadMetadata() { this.validatorTriggers = []; this.propertyGroups = []; + this.defaults = {}; for (const control of Object.keys(this.propertiesFormGroup.controls)) { this.propertiesFormGroup.removeControl(control, {emitEvent: false}); } if (this.properties) { + this.defaults = defaultFormProperties(this.properties); for (let property of this.properties) { property.disabled = false; property.visible = true; @@ -282,8 +290,7 @@ export class DynamicFormComponent implements OnInit, OnChanges, ControlValueAcce private setupValue() { if (this.properties) { - const defaults = defaultFormProperties(this.properties); - this.modelValue = mergeDeep<{[id: string]: any}>(defaults, this.modelValue); + this.modelValue = mergeDeep<{[id: string]: any}>({}, this.defaults, this.modelValue); this.propertiesFormGroup.patchValue( this.modelValue, {emitEvent: false} ); @@ -295,7 +302,11 @@ export class DynamicFormComponent implements OnInit, OnChanges, ControlValueAcce private updateModel() { this.modelValue = this.propertiesFormGroup.getRawValue(); this.calculateControlsState(true); - this.propagateChange(this.modelValue); + let result = this.modelValue; + if (this.trimDefaults) { + result = trimDefaultValues(this.modelValue, this.defaults); + } + this.propagateChange(result); } }