Update RestClient getTimeseries method. UI: Improve dynamic form - allow to trim default values from value.

This commit is contained in:
Igor Kulikov 2025-08-13 19:38:24 +03:00
parent 052b5cddb8
commit 178b2849ed
3 changed files with 56 additions and 4 deletions

View File

@ -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<TsKvEntry> getTimeseries(EntityId entityId, List<String> 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<TsKvEntry> getTimeseries(EntityId entityId, List<String> keys, Long interval, IntervalType intervalType, String timeZone, Aggregation agg, SortOrder.Direction sortOrder, Long startTime, Long endTime, Integer limit, boolean useStrictDataTypes) {
Map<String, String> 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));

View File

@ -958,3 +958,28 @@ export const unwrapModule = (module: any) : any => {
return module;
}
};
export const trimDefaultValues = (input: Record<string, any>, defaults: Record<string, any>): Record<string, any> => {
const result: Record<string, any> = {};
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;
}

View File

@ -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);
}
}