Commit 11cfdd6840badf4c0a82062ed1e0b05f02e69b46
Merge branch 'master' of github.com:thingsboard/thingsboard
Showing
7 changed files
with
505 additions
and
29 deletions
... | ... | @@ -63,6 +63,7 @@ import java.util.ArrayList; |
63 | 63 | import java.util.Collections; |
64 | 64 | import java.util.List; |
65 | 65 | import java.util.concurrent.ExecutionException; |
66 | +import java.util.concurrent.atomic.AtomicLong; | |
66 | 67 | import java.util.stream.Collectors; |
67 | 68 | |
68 | 69 | import static org.apache.commons.lang3.StringUtils.isBlank; |
... | ... | @@ -144,35 +145,38 @@ public class DefaultDataUpdateService implements DataUpdateService { |
144 | 145 | |
145 | 146 | @Override |
146 | 147 | protected void updateEntity(DeviceProfileEntity deviceProfile) { |
147 | - if (deviceProfile.getProfileData().has("alarms") && | |
148 | - !deviceProfile.getProfileData().get("alarms").isNull()) { | |
149 | - boolean isUpdated = false; | |
150 | - JsonNode alarms = deviceProfile.getProfileData().get("alarms"); | |
151 | - for (JsonNode alarm : alarms) { | |
152 | - if (alarm.has("createRules")) { | |
153 | - JsonNode createRules = alarm.get("createRules"); | |
154 | - for (AlarmSeverity severity : AlarmSeverity.values()) { | |
155 | - if (createRules.has(severity.name())) { | |
156 | - JsonNode spec = createRules.get(severity.name()).get("condition").get("spec"); | |
157 | - if (convertDeviceProfileAlarmRulesForVersion330(spec)) { | |
158 | - isUpdated = true; | |
159 | - } | |
160 | - } | |
161 | - } | |
162 | - } | |
163 | - if (alarm.has("clearRule") && !alarm.get("clearRule").isNull()) { | |
164 | - JsonNode spec = alarm.get("clearRule").get("condition").get("spec"); | |
165 | - if (convertDeviceProfileAlarmRulesForVersion330(spec)) { | |
166 | - isUpdated = true; | |
167 | - } | |
148 | + if (convertDeviceProfileForVersion330(deviceProfile.getProfileData())) { | |
149 | + deviceProfileRepository.save(deviceProfile); | |
150 | + } | |
151 | + } | |
152 | + }; | |
153 | + | |
154 | + boolean convertDeviceProfileForVersion330(JsonNode profileData) { | |
155 | + boolean isUpdated = false; | |
156 | + if (profileData.has("alarms") && !profileData.get("alarms").isNull()) { | |
157 | + JsonNode alarms = profileData.get("alarms"); | |
158 | + for (JsonNode alarm : alarms) { | |
159 | + if (alarm.has("createRules")) { | |
160 | + JsonNode createRules = alarm.get("createRules"); | |
161 | + for (AlarmSeverity severity : AlarmSeverity.values()) { | |
162 | + if (createRules.has(severity.name())) { | |
163 | + JsonNode spec = createRules.get(severity.name()).get("condition").get("spec"); | |
164 | + if (convertDeviceProfileAlarmRulesForVersion330(spec)) { | |
165 | + isUpdated = true; | |
168 | 166 | } |
169 | 167 | } |
170 | - if (isUpdated) { | |
171 | - deviceProfileRepository.save(deviceProfile); | |
172 | - } | |
173 | 168 | } |
174 | 169 | } |
175 | - }; | |
170 | + if (alarm.has("clearRule") && !alarm.get("clearRule").isNull()) { | |
171 | + JsonNode spec = alarm.get("clearRule").get("condition").get("spec"); | |
172 | + if (convertDeviceProfileAlarmRulesForVersion330(spec)) { | |
173 | + isUpdated = true; | |
174 | + } | |
175 | + } | |
176 | + } | |
177 | + } | |
178 | + return isUpdated; | |
179 | + } | |
176 | 180 | |
177 | 181 | private final PaginatedUpdater<String, Tenant> tenantsDefaultRuleChainUpdater = |
178 | 182 | new PaginatedUpdater<>() { |
... | ... | @@ -382,6 +386,8 @@ public class DefaultDataUpdateService implements DataUpdateService { |
382 | 386 | private final PaginatedUpdater<String, Tenant> tenantsAlarmsCustomerUpdater = |
383 | 387 | new PaginatedUpdater<>() { |
384 | 388 | |
389 | + final AtomicLong processed = new AtomicLong(); | |
390 | + | |
385 | 391 | @Override |
386 | 392 | protected String getName() { |
387 | 393 | return "Tenants alarms customer updater"; |
... | ... | @@ -399,12 +405,12 @@ public class DefaultDataUpdateService implements DataUpdateService { |
399 | 405 | |
400 | 406 | @Override |
401 | 407 | protected void updateEntity(Tenant tenant) { |
402 | - updateTenantAlarmsCustomer(tenant.getId()); | |
408 | + updateTenantAlarmsCustomer(tenant.getId(), getName(), processed); | |
403 | 409 | } |
404 | 410 | }; |
405 | 411 | |
406 | - private void updateTenantAlarmsCustomer(TenantId tenantId) { | |
407 | - AlarmQuery alarmQuery = new AlarmQuery(null, new TimePageLink(100), null, null, false); | |
412 | + private void updateTenantAlarmsCustomer(TenantId tenantId, String name, AtomicLong processed) { | |
413 | + AlarmQuery alarmQuery = new AlarmQuery(null, new TimePageLink(1000), null, null, false); | |
408 | 414 | PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, alarmQuery); |
409 | 415 | boolean hasNext = true; |
410 | 416 | while (hasNext) { |
... | ... | @@ -413,6 +419,9 @@ public class DefaultDataUpdateService implements DataUpdateService { |
413 | 419 | alarm.setCustomerId(entityService.fetchEntityCustomerId(tenantId, alarm.getOriginator())); |
414 | 420 | alarmDao.save(tenantId, alarm); |
415 | 421 | } |
422 | + if (processed.incrementAndGet() % 1000 == 0) { | |
423 | + log.info("{}: {} alarms processed so far...", name, processed); | |
424 | + } | |
416 | 425 | } |
417 | 426 | if (alarms.hasNext()) { |
418 | 427 | alarmQuery.setPageLink(alarmQuery.getPageLink().nextPageLink()); |
... | ... | @@ -423,7 +432,7 @@ public class DefaultDataUpdateService implements DataUpdateService { |
423 | 432 | } |
424 | 433 | } |
425 | 434 | |
426 | - private boolean convertDeviceProfileAlarmRulesForVersion330(JsonNode spec) { | |
435 | + boolean convertDeviceProfileAlarmRulesForVersion330(JsonNode spec) { | |
427 | 436 | if (spec != null) { |
428 | 437 | if (spec.has("type") && spec.get("type").asText().equals("DURATION")) { |
429 | 438 | if (spec.has("value")) { | ... | ... |
... | ... | @@ -28,6 +28,7 @@ public abstract class PaginatedUpdater<I, D> { |
28 | 28 | private int updated = 0; |
29 | 29 | |
30 | 30 | public void updateEntities(I id) { |
31 | + log.info("{}: started...", getName()); | |
31 | 32 | updated = 0; |
32 | 33 | PageLink pageLink = new PageLink(DEFAULT_LIMIT); |
33 | 34 | boolean hasNext = true; | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.install.update; | |
17 | + | |
18 | +import com.fasterxml.jackson.core.JsonProcessingException; | |
19 | +import com.fasterxml.jackson.databind.JsonNode; | |
20 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
21 | +import org.junit.jupiter.api.BeforeEach; | |
22 | +import org.junit.jupiter.api.Test; | |
23 | +import org.springframework.boot.test.context.SpringBootTest; | |
24 | +import org.springframework.boot.test.mock.mockito.MockBean; | |
25 | +import org.springframework.test.context.ActiveProfiles; | |
26 | + | |
27 | +import java.io.IOException; | |
28 | + | |
29 | +import static org.assertj.core.api.Assertions.assertThat; | |
30 | +import static org.mockito.ArgumentMatchers.any; | |
31 | +import static org.mockito.BDDMockito.willCallRealMethod; | |
32 | + | |
33 | +@ActiveProfiles("install") | |
34 | +@SpringBootTest(classes = DefaultDataUpdateService.class) | |
35 | +class DefaultDataUpdateServiceTest { | |
36 | + | |
37 | + ObjectMapper mapper = new ObjectMapper(); | |
38 | + | |
39 | + @MockBean | |
40 | + DefaultDataUpdateService service; | |
41 | + | |
42 | + @BeforeEach | |
43 | + void setUp() { | |
44 | + willCallRealMethod().given(service).convertDeviceProfileAlarmRulesForVersion330(any()); | |
45 | + willCallRealMethod().given(service).convertDeviceProfileForVersion330(any()); | |
46 | + } | |
47 | + | |
48 | + JsonNode readFromResource(String resourceName) throws IOException { | |
49 | + return mapper.readTree(this.getClass().getClassLoader().getResourceAsStream(resourceName)); | |
50 | + } | |
51 | + | |
52 | + @Test | |
53 | + void convertDeviceProfileAlarmRulesForVersion330FirstRun() throws IOException { | |
54 | + JsonNode spec = readFromResource("update/330/device_profile_001_in.json"); | |
55 | + JsonNode expected = readFromResource("update/330/device_profile_001_out.json"); | |
56 | + | |
57 | + assertThat(service.convertDeviceProfileForVersion330(spec.get("profileData"))).isTrue(); | |
58 | + assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString()); // use IDE feature <Click to see difference> | |
59 | + } | |
60 | + | |
61 | + @Test | |
62 | + void convertDeviceProfileAlarmRulesForVersion330SecondRun() throws IOException { | |
63 | + JsonNode spec = readFromResource("update/330/device_profile_001_out.json"); | |
64 | + JsonNode expected = readFromResource("update/330/device_profile_001_out.json"); | |
65 | + | |
66 | + assertThat(service.convertDeviceProfileForVersion330(spec.get("profileData"))).isFalse(); | |
67 | + assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString()); // use IDE feature <Click to see difference> | |
68 | + } | |
69 | + | |
70 | + @Test | |
71 | + void convertDeviceProfileAlarmRulesForVersion330EmptyJson() throws JsonProcessingException { | |
72 | + JsonNode spec = mapper.readTree("{ }"); | |
73 | + JsonNode expected = mapper.readTree("{ }"); | |
74 | + | |
75 | + assertThat(service.convertDeviceProfileForVersion330(spec)).isFalse(); | |
76 | + assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString()); | |
77 | + } | |
78 | + | |
79 | + @Test | |
80 | + void convertDeviceProfileAlarmRulesForVersion330AlarmNodeNull() throws JsonProcessingException { | |
81 | + JsonNode spec = mapper.readTree("{ \"alarms\" : null }"); | |
82 | + JsonNode expected = mapper.readTree("{ \"alarms\" : null }"); | |
83 | + | |
84 | + assertThat(service.convertDeviceProfileForVersion330(spec)).isFalse(); | |
85 | + assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString()); | |
86 | + } | |
87 | + | |
88 | + @Test | |
89 | + void convertDeviceProfileAlarmRulesForVersion330NoAlarmNode() throws JsonProcessingException { | |
90 | + JsonNode spec = mapper.readTree("{ \"configuration\": { \"type\": \"DEFAULT\" } }"); | |
91 | + JsonNode expected = mapper.readTree("{ \"configuration\": { \"type\": \"DEFAULT\" } }"); | |
92 | + | |
93 | + assertThat(service.convertDeviceProfileForVersion330(spec)).isFalse(); | |
94 | + assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString()); | |
95 | + } | |
96 | + | |
97 | +} | ... | ... |
1 | +{ | |
2 | + "id": { | |
3 | + "entityType": "DEVICE_PROFILE", | |
4 | + "id": "b99fde7a-33dd-4d5d-a325-d0637f6acbe5" | |
5 | + }, | |
6 | + "createdTime": 1627268171906, | |
7 | + "tenantId": { | |
8 | + "entityType": "TENANT", | |
9 | + "id": "3db30ac6-db03-4788-98fe-6e024b422a15" | |
10 | + }, | |
11 | + "name": "LORAWAN 001", | |
12 | + "description": "Tektelic - 001", | |
13 | + "type": "DEFAULT", | |
14 | + "transportType": "DEFAULT", | |
15 | + "provisionType": "DISABLED", | |
16 | + "defaultRuleChainId": { | |
17 | + "entityType": "RULE_CHAIN", | |
18 | + "id": "9c50f4df-f41e-443f-bb7d-37b5ac97f3c3" | |
19 | + }, | |
20 | + "defaultQueueName": "LORAWAN", | |
21 | + "profileData": { | |
22 | + "configuration": { | |
23 | + "type": "DEFAULT" | |
24 | + }, | |
25 | + "transportConfiguration": { | |
26 | + "type": "DEFAULT" | |
27 | + }, | |
28 | + "provisionConfiguration": { | |
29 | + "type": "DISABLED", | |
30 | + "provisionDeviceSecret": null | |
31 | + }, | |
32 | + "alarms": [ | |
33 | + { | |
34 | + "id": "b86271fd-5fee-4bd5-975c-d9c18f610cd5", | |
35 | + "alarmType": "LORAWAN - Battery Alarm", | |
36 | + "createRules": { | |
37 | + "CRITICAL": { | |
38 | + "condition": { | |
39 | + "condition": [ | |
40 | + { | |
41 | + "key": { | |
42 | + "type": "TIME_SERIES", | |
43 | + "key": "batteryLevel" | |
44 | + }, | |
45 | + "valueType": "NUMERIC", | |
46 | + "value": null, | |
47 | + "predicate": { | |
48 | + "type": "NUMERIC", | |
49 | + "operation": "LESS", | |
50 | + "value": { | |
51 | + "defaultValue": 25.0, | |
52 | + "userValue": null, | |
53 | + "dynamicValue": null | |
54 | + } | |
55 | + } | |
56 | + } | |
57 | + ], | |
58 | + "spec": { | |
59 | + "type": "DURATION", | |
60 | + "unit": "DAYS", | |
61 | + "value": 1 | |
62 | + } | |
63 | + }, | |
64 | + "schedule": null, | |
65 | + "alarmDetails": null | |
66 | + } | |
67 | + }, | |
68 | + "clearRule": { | |
69 | + "condition": { | |
70 | + "condition": [ | |
71 | + { | |
72 | + "key": { | |
73 | + "type": "TIME_SERIES", | |
74 | + "key": "batteryLevel" | |
75 | + }, | |
76 | + "valueType": "NUMERIC", | |
77 | + "value": null, | |
78 | + "predicate": { | |
79 | + "type": "NUMERIC", | |
80 | + "operation": "GREATER_OR_EQUAL", | |
81 | + "value": { | |
82 | + "defaultValue": 25.0, | |
83 | + "userValue": null, | |
84 | + "dynamicValue": null | |
85 | + } | |
86 | + } | |
87 | + } | |
88 | + ], | |
89 | + "spec": { | |
90 | + "type": "DURATION", | |
91 | + "unit": "DAYS", | |
92 | + "value": 1 | |
93 | + } | |
94 | + }, | |
95 | + "schedule": null, | |
96 | + "alarmDetails": null | |
97 | + }, | |
98 | + "propagate": true, | |
99 | + "propagateRelationTypes": [ | |
100 | + "UC-0007 LORAWAN" | |
101 | + ] | |
102 | + }, | |
103 | + { | |
104 | + "id": "c70aef4e-65cf-4578-acd9-e1927c08b469", | |
105 | + "alarmType": "LORAWAN - No Data", | |
106 | + "createRules": { | |
107 | + "CRITICAL": { | |
108 | + "condition": { | |
109 | + "condition": [ | |
110 | + { | |
111 | + "key": { | |
112 | + "type": "TIME_SERIES", | |
113 | + "key": "active" | |
114 | + }, | |
115 | + "valueType": "BOOLEAN", | |
116 | + "value": null, | |
117 | + "predicate": { | |
118 | + "type": "BOOLEAN", | |
119 | + "operation": "EQUAL", | |
120 | + "value": { | |
121 | + "defaultValue": false, | |
122 | + "userValue": null, | |
123 | + "dynamicValue": null | |
124 | + } | |
125 | + } | |
126 | + } | |
127 | + ], | |
128 | + "spec": { | |
129 | + "type": "SIMPLE" | |
130 | + } | |
131 | + }, | |
132 | + "schedule": null, | |
133 | + "alarmDetails": null | |
134 | + } | |
135 | + }, | |
136 | + "clearRule": { | |
137 | + "condition": { | |
138 | + "condition": [ | |
139 | + { | |
140 | + "key": { | |
141 | + "type": "TIME_SERIES", | |
142 | + "key": "active" | |
143 | + }, | |
144 | + "valueType": "BOOLEAN", | |
145 | + "value": null, | |
146 | + "predicate": { | |
147 | + "type": "BOOLEAN", | |
148 | + "operation": "EQUAL", | |
149 | + "value": { | |
150 | + "defaultValue": true, | |
151 | + "userValue": null, | |
152 | + "dynamicValue": null | |
153 | + } | |
154 | + } | |
155 | + } | |
156 | + ], | |
157 | + "spec": { | |
158 | + "type": "SIMPLE" | |
159 | + } | |
160 | + }, | |
161 | + "schedule": null, | |
162 | + "alarmDetails": null | |
163 | + }, | |
164 | + "propagate": true, | |
165 | + "propagateRelationTypes": [ | |
166 | + "LORAWAN 001 related" | |
167 | + ] | |
168 | + } | |
169 | + ] | |
170 | + }, | |
171 | + "provisionDeviceKey": null, | |
172 | + "default": false | |
173 | +} | ... | ... |
1 | +{ | |
2 | + "id": { | |
3 | + "entityType": "DEVICE_PROFILE", | |
4 | + "id": "b99fde7a-33dd-4d5d-a325-d0637f6acbe5" | |
5 | + }, | |
6 | + "createdTime": 1627268171906, | |
7 | + "tenantId": { | |
8 | + "entityType": "TENANT", | |
9 | + "id": "3db30ac6-db03-4788-98fe-6e024b422a15" | |
10 | + }, | |
11 | + "name": "LORAWAN 001", | |
12 | + "description": "Tektelic - 001", | |
13 | + "type": "DEFAULT", | |
14 | + "transportType": "DEFAULT", | |
15 | + "provisionType": "DISABLED", | |
16 | + "defaultRuleChainId": { | |
17 | + "entityType": "RULE_CHAIN", | |
18 | + "id": "9c50f4df-f41e-443f-bb7d-37b5ac97f3c3" | |
19 | + }, | |
20 | + "defaultQueueName": "LORAWAN", | |
21 | + "profileData": { | |
22 | + "configuration": { | |
23 | + "type": "DEFAULT" | |
24 | + }, | |
25 | + "transportConfiguration": { | |
26 | + "type": "DEFAULT" | |
27 | + }, | |
28 | + "provisionConfiguration": { | |
29 | + "type": "DISABLED", | |
30 | + "provisionDeviceSecret": null | |
31 | + }, | |
32 | + "alarms": [ | |
33 | + { | |
34 | + "id": "b86271fd-5fee-4bd5-975c-d9c18f610cd5", | |
35 | + "alarmType": "LORAWAN - Battery Alarm", | |
36 | + "createRules": { | |
37 | + "CRITICAL": { | |
38 | + "condition": { | |
39 | + "condition": [ | |
40 | + { | |
41 | + "key": { | |
42 | + "type": "TIME_SERIES", | |
43 | + "key": "batteryLevel" | |
44 | + }, | |
45 | + "valueType": "NUMERIC", | |
46 | + "value": null, | |
47 | + "predicate": { | |
48 | + "type": "NUMERIC", | |
49 | + "operation": "LESS", | |
50 | + "value": { | |
51 | + "defaultValue": 25.0, | |
52 | + "userValue": null, | |
53 | + "dynamicValue": null | |
54 | + } | |
55 | + } | |
56 | + } | |
57 | + ], | |
58 | + "spec": { | |
59 | + "type": "DURATION", | |
60 | + "unit": "DAYS", | |
61 | + "predicate": { | |
62 | + "defaultValue": 1, | |
63 | + "userValue": null, | |
64 | + "dynamicValue": { | |
65 | + "sourceType": null, | |
66 | + "sourceAttribute": null, | |
67 | + "inherit": false | |
68 | + } | |
69 | + } | |
70 | + } | |
71 | + }, | |
72 | + "schedule": null, | |
73 | + "alarmDetails": null | |
74 | + } | |
75 | + }, | |
76 | + "clearRule": { | |
77 | + "condition": { | |
78 | + "condition": [ | |
79 | + { | |
80 | + "key": { | |
81 | + "type": "TIME_SERIES", | |
82 | + "key": "batteryLevel" | |
83 | + }, | |
84 | + "valueType": "NUMERIC", | |
85 | + "value": null, | |
86 | + "predicate": { | |
87 | + "type": "NUMERIC", | |
88 | + "operation": "GREATER_OR_EQUAL", | |
89 | + "value": { | |
90 | + "defaultValue": 25.0, | |
91 | + "userValue": null, | |
92 | + "dynamicValue": null | |
93 | + } | |
94 | + } | |
95 | + } | |
96 | + ], | |
97 | + "spec": { | |
98 | + "type": "DURATION", | |
99 | + "unit": "DAYS", | |
100 | + "predicate": { | |
101 | + "defaultValue": 1, | |
102 | + "userValue": null, | |
103 | + "dynamicValue": { | |
104 | + "sourceType": null, | |
105 | + "sourceAttribute": null, | |
106 | + "inherit": false | |
107 | + } | |
108 | + } | |
109 | + } | |
110 | + }, | |
111 | + "schedule": null, | |
112 | + "alarmDetails": null | |
113 | + }, | |
114 | + "propagate": true, | |
115 | + "propagateRelationTypes": [ | |
116 | + "UC-0007 LORAWAN" | |
117 | + ] | |
118 | + }, | |
119 | + { | |
120 | + "id": "c70aef4e-65cf-4578-acd9-e1927c08b469", | |
121 | + "alarmType": "LORAWAN - No Data", | |
122 | + "createRules": { | |
123 | + "CRITICAL": { | |
124 | + "condition": { | |
125 | + "condition": [ | |
126 | + { | |
127 | + "key": { | |
128 | + "type": "TIME_SERIES", | |
129 | + "key": "active" | |
130 | + }, | |
131 | + "valueType": "BOOLEAN", | |
132 | + "value": null, | |
133 | + "predicate": { | |
134 | + "type": "BOOLEAN", | |
135 | + "operation": "EQUAL", | |
136 | + "value": { | |
137 | + "defaultValue": false, | |
138 | + "userValue": null, | |
139 | + "dynamicValue": null | |
140 | + } | |
141 | + } | |
142 | + } | |
143 | + ], | |
144 | + "spec": { | |
145 | + "type": "SIMPLE" | |
146 | + } | |
147 | + }, | |
148 | + "schedule": null, | |
149 | + "alarmDetails": null | |
150 | + } | |
151 | + }, | |
152 | + "clearRule": { | |
153 | + "condition": { | |
154 | + "condition": [ | |
155 | + { | |
156 | + "key": { | |
157 | + "type": "TIME_SERIES", | |
158 | + "key": "active" | |
159 | + }, | |
160 | + "valueType": "BOOLEAN", | |
161 | + "value": null, | |
162 | + "predicate": { | |
163 | + "type": "BOOLEAN", | |
164 | + "operation": "EQUAL", | |
165 | + "value": { | |
166 | + "defaultValue": true, | |
167 | + "userValue": null, | |
168 | + "dynamicValue": null | |
169 | + } | |
170 | + } | |
171 | + } | |
172 | + ], | |
173 | + "spec": { | |
174 | + "type": "SIMPLE" | |
175 | + } | |
176 | + }, | |
177 | + "schedule": null, | |
178 | + "alarmDetails": null | |
179 | + }, | |
180 | + "propagate": true, | |
181 | + "propagateRelationTypes": [ | |
182 | + "LORAWAN 001 related" | |
183 | + ] | |
184 | + } | |
185 | + ] | |
186 | + }, | |
187 | + "provisionDeviceKey": null, | |
188 | + "default": false | |
189 | +} | ... | ... |
... | ... | @@ -17,8 +17,12 @@ package org.thingsboard.server.common.data.page; |
17 | 17 | |
18 | 18 | import com.fasterxml.jackson.annotation.JsonIgnore; |
19 | 19 | import lombok.Data; |
20 | +import lombok.EqualsAndHashCode; | |
21 | +import lombok.ToString; | |
20 | 22 | |
21 | 23 | @Data |
24 | +@ToString(callSuper = true) | |
25 | +@EqualsAndHashCode(callSuper = true) | |
22 | 26 | public class TimePageLink extends PageLink { |
23 | 27 | |
24 | 28 | private final Long startTime; | ... | ... |