Commit b5659fd32d06cd090f25c9eefce8898aac1c2e48
Merge remote-tracking branch 'upstream/feature/edge' into feature/widgets_bundle_fetch
Showing
25 changed files
with
295 additions
and
58 deletions
... | ... | @@ -66,6 +66,7 @@ import org.thingsboard.server.dao.user.UserService; |
66 | 66 | import org.thingsboard.server.queue.discovery.PartitionService; |
67 | 67 | import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
68 | 68 | import org.thingsboard.server.service.component.ComponentDiscoveryService; |
69 | +import org.thingsboard.server.service.edge.rpc.EdgeRpcService; | |
69 | 70 | import org.thingsboard.server.service.encoding.DataDecodingEncodingService; |
70 | 71 | import org.thingsboard.server.service.executors.DbCallbackExecutorService; |
71 | 72 | import org.thingsboard.server.service.executors.ExternalCallExecutorService; |
... | ... | @@ -254,15 +255,14 @@ public class ActorSystemContext { |
254 | 255 | @Getter |
255 | 256 | private TbCoreDeviceRpcService tbCoreDeviceRpcService; |
256 | 257 | |
257 | - @Lazy | |
258 | - @Autowired | |
259 | - @Getter | |
260 | - private EdgeService edgeService; | |
258 | + @Autowired(required = false) | |
259 | + @Getter private EdgeService edgeService; | |
261 | 260 | |
262 | - @Lazy | |
263 | - @Autowired | |
264 | - @Getter | |
265 | - private EdgeEventService edgeEventService; | |
261 | + @Autowired(required = false) | |
262 | + @Getter private EdgeEventService edgeEventService; | |
263 | + | |
264 | + @Autowired(required = false) | |
265 | + @Getter private EdgeRpcService edgeRpcService; | |
266 | 266 | |
267 | 267 | @Value("${actors.session.max_concurrent_sessions_per_device:1}") |
268 | 268 | @Getter | ... | ... |
... | ... | @@ -31,10 +31,13 @@ import org.thingsboard.server.actors.service.ContextBasedCreator; |
31 | 31 | import org.thingsboard.server.actors.service.DefaultActorService; |
32 | 32 | import org.thingsboard.server.common.data.EntityType; |
33 | 33 | import org.thingsboard.server.common.data.Tenant; |
34 | +import org.thingsboard.server.common.data.edge.Edge; | |
34 | 35 | import org.thingsboard.server.common.data.id.DeviceId; |
36 | +import org.thingsboard.server.common.data.id.EdgeId; | |
35 | 37 | import org.thingsboard.server.common.data.id.EntityId; |
36 | 38 | import org.thingsboard.server.common.data.id.RuleChainId; |
37 | 39 | import org.thingsboard.server.common.data.id.TenantId; |
40 | +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; | |
38 | 41 | import org.thingsboard.server.common.data.rule.RuleChain; |
39 | 42 | import org.thingsboard.server.common.data.rule.RuleChainType; |
40 | 43 | import org.thingsboard.server.common.msg.MsgType; |
... | ... | @@ -47,6 +50,7 @@ import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; |
47 | 50 | import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; |
48 | 51 | import org.thingsboard.server.common.msg.queue.RuleEngineException; |
49 | 52 | import org.thingsboard.server.common.msg.queue.ServiceType; |
53 | +import org.thingsboard.server.service.edge.rpc.EdgeRpcService; | |
50 | 54 | |
51 | 55 | import java.util.List; |
52 | 56 | import java.util.Optional; |
... | ... | @@ -202,7 +206,18 @@ public class TenantActor extends RuleChainManagerActor { |
202 | 206 | } |
203 | 207 | |
204 | 208 | private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { |
205 | - if (isRuleEngineForCurrentTenant) { | |
209 | + if (msg.getEntityId().getEntityType() == EntityType.EDGE) { | |
210 | + EdgeId edgeId = new EdgeId(msg.getEntityId().getId()); | |
211 | + EdgeRpcService edgeRpcService = systemContext.getEdgeRpcService(); | |
212 | + if (msg.getEvent() == ComponentLifecycleEvent.DELETED) { | |
213 | + edgeRpcService.deleteEdge(edgeId); | |
214 | + } else { | |
215 | + Edge edge = systemContext.getEdgeService().findEdgeById(tenantId, edgeId); | |
216 | + if (msg.getEvent() == ComponentLifecycleEvent.UPDATED) { | |
217 | + edgeRpcService.updateEdge(edge); | |
218 | + } | |
219 | + } | |
220 | + } else if (isRuleEngineForCurrentTenant) { | |
206 | 221 | TbActorRef target = getEntityActorRef(msg.getEntityId()); |
207 | 222 | if (target != null) { |
208 | 223 | if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) { | ... | ... |
... | ... | @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.id.RuleChainId; |
40 | 40 | import org.thingsboard.server.common.data.id.TenantId; |
41 | 41 | import org.thingsboard.server.common.data.page.TextPageData; |
42 | 42 | import org.thingsboard.server.common.data.page.TextPageLink; |
43 | +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; | |
43 | 44 | import org.thingsboard.server.common.data.rule.RuleChain; |
44 | 45 | import org.thingsboard.server.dao.exception.DataValidationException; |
45 | 46 | import org.thingsboard.server.dao.exception.IncorrectParameterException; |
... | ... | @@ -101,6 +102,9 @@ public class EdgeController extends BaseController { |
101 | 102 | edgeService.assignDefaultRuleChainsToEdge(tenantId, savedEdge.getId()); |
102 | 103 | } |
103 | 104 | |
105 | + tbClusterService.onEntityStateChange(savedEdge.getTenantId(), savedEdge.getId(), | |
106 | + created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); | |
107 | + | |
104 | 108 | logEntityAction(savedEdge.getId(), savedEdge, null, created ? ActionType.ADDED : ActionType.UPDATED, null); |
105 | 109 | return savedEdge; |
106 | 110 | } catch (Exception e) { |
... | ... | @@ -120,6 +124,9 @@ public class EdgeController extends BaseController { |
120 | 124 | Edge edge = checkEdgeId(edgeId, Operation.DELETE); |
121 | 125 | edgeService.deleteEdge(getTenantId(), edgeId); |
122 | 126 | |
127 | + tbClusterService.onEntityStateChange(getTenantId(), edgeId, | |
128 | + ComponentLifecycleEvent.DELETED); | |
129 | + | |
123 | 130 | logEntityAction(edgeId, edge, |
124 | 131 | null, |
125 | 132 | ActionType.DELETED, null, strEdgeId); |
... | ... | @@ -284,6 +291,8 @@ public class EdgeController extends BaseController { |
284 | 291 | |
285 | 292 | Edge updatedEdge = edgeNotificationService.setEdgeRootRuleChain(getTenantId(), edge, ruleChainId); |
286 | 293 | |
294 | + tbClusterService.onEntityStateChange(updatedEdge.getTenantId(), updatedEdge.getId(), ComponentLifecycleEvent.UPDATED); | |
295 | + | |
287 | 296 | logEntityAction(updatedEdge.getId(), updatedEdge, null, ActionType.UPDATED, null); |
288 | 297 | |
289 | 298 | return updatedEdge; | ... | ... |
... | ... | @@ -61,7 +61,7 @@ public class EdgeEventController extends BaseController { |
61 | 61 | EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); |
62 | 62 | checkEdgeId(edgeId, Operation.READ); |
63 | 63 | TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); |
64 | - return checkNotNull(edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink)); | |
64 | + return checkNotNull(edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, false)); | |
65 | 65 | } catch (Exception e) { |
66 | 66 | throw handleException(e); |
67 | 67 | } | ... | ... |
... | ... | @@ -111,7 +111,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService { |
111 | 111 | |
112 | 112 | @Override |
113 | 113 | public TimePageData<EdgeEvent> findEdgeEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink) { |
114 | - return edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink); | |
114 | + return edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, true); | |
115 | 115 | } |
116 | 116 | |
117 | 117 | @Override | ... | ... |
... | ... | @@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Value; |
27 | 27 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
28 | 28 | import org.springframework.stereotype.Service; |
29 | 29 | import org.thingsboard.server.common.data.DataConstants; |
30 | +import org.thingsboard.server.common.data.edge.Edge; | |
30 | 31 | import org.thingsboard.server.common.data.id.EdgeId; |
31 | 32 | import org.thingsboard.server.common.data.id.TenantId; |
32 | 33 | import org.thingsboard.server.common.data.kv.BasicTsKvEntry; |
... | ... | @@ -52,7 +53,7 @@ import java.util.concurrent.Executors; |
52 | 53 | @Service |
53 | 54 | @Slf4j |
54 | 55 | @ConditionalOnProperty(prefix = "edges.rpc", value = "enabled", havingValue = "true") |
55 | -public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase { | |
56 | +public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase implements EdgeRpcService { | |
56 | 57 | |
57 | 58 | private final Map<EdgeId, EdgeGrpcSession> sessions = new ConcurrentHashMap<>(); |
58 | 59 | private static final ObjectMapper mapper = new ObjectMapper(); |
... | ... | @@ -117,6 +118,23 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase { |
117 | 118 | return new EdgeGrpcSession(ctx, outputStream, this::onEdgeConnect, this::onEdgeDisconnect, mapper).getInputStream(); |
118 | 119 | } |
119 | 120 | |
121 | + @Override | |
122 | + public void updateEdge(Edge edge) { | |
123 | + EdgeGrpcSession session = sessions.get(edge.getId()); | |
124 | + if (session != null && session.isConnected()) { | |
125 | + session.onConfigurationUpdate(edge); | |
126 | + } | |
127 | + } | |
128 | + | |
129 | + @Override | |
130 | + public void deleteEdge(EdgeId edgeId) { | |
131 | + EdgeGrpcSession session = sessions.get(edgeId); | |
132 | + if (session != null && session.isConnected()) { | |
133 | + session.close(); | |
134 | + sessions.remove(edgeId); | |
135 | + } | |
136 | + } | |
137 | + | |
120 | 138 | private void onEdgeConnect(EdgeId edgeId, EdgeGrpcSession edgeGrpcSession) { |
121 | 139 | sessions.put(edgeId, edgeGrpcSession); |
122 | 140 | save(edgeId, DefaultDeviceStateService.ACTIVITY_STATE, true); | ... | ... |
... | ... | @@ -198,6 +198,20 @@ public final class EdgeGrpcSession implements Closeable { |
198 | 198 | }; |
199 | 199 | } |
200 | 200 | |
201 | + void onConfigurationUpdate(Edge edge) { | |
202 | + try { | |
203 | + this.edge = edge; | |
204 | + // TODO: voba - push edge configuration update to edge | |
205 | +// outputStream.onNext(org.thingsboard.server.gen.integration.ResponseMsg.newBuilder() | |
206 | +// .setIntegrationUpdateMsg(IntegrationUpdateMsg.newBuilder() | |
207 | +// .setConfiguration(constructIntegrationConfigProto(configuration, defaultConverterProto, downLinkConverterProto)) | |
208 | +// .build()) | |
209 | +// .build()); | |
210 | + } catch (Exception e) { | |
211 | + log.error("Failed to construct proto objects!", e); | |
212 | + } | |
213 | + } | |
214 | + | |
201 | 215 | void processHandleMessages() throws ExecutionException, InterruptedException { |
202 | 216 | Long queueStartTs = getQueueStartTs().get(); |
203 | 217 | TimePageLink pageLink = new TimePageLink(ctx.getEdgeEventStorageSettings().getMaxReadRecordsCount(), queueStartTs, null, true); | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2020 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.edge.rpc; | |
17 | + | |
18 | +import org.thingsboard.server.common.data.edge.Edge; | |
19 | +import org.thingsboard.server.common.data.id.EdgeId; | |
20 | + | |
21 | +public interface EdgeRpcService { | |
22 | + | |
23 | + void updateEdge(Edge edge); | |
24 | + | |
25 | + void deleteEdge(EdgeId edgeId); | |
26 | +} | ... | ... |
... | ... | @@ -16,9 +16,7 @@ |
16 | 16 | package org.thingsboard.server.dao.edge; |
17 | 17 | |
18 | 18 | import com.google.common.util.concurrent.ListenableFuture; |
19 | -import org.thingsboard.server.common.data.EntityType; | |
20 | 19 | import org.thingsboard.server.common.data.edge.EdgeEvent; |
21 | -import org.thingsboard.server.common.data.edge.EdgeEventType; | |
22 | 20 | import org.thingsboard.server.common.data.id.EdgeId; |
23 | 21 | import org.thingsboard.server.common.data.id.TenantId; |
24 | 22 | import org.thingsboard.server.common.data.page.TimePageData; |
... | ... | @@ -28,6 +26,5 @@ public interface EdgeEventService { |
28 | 26 | |
29 | 27 | ListenableFuture<EdgeEvent> saveAsync(EdgeEvent edgeEvent); |
30 | 28 | |
31 | - TimePageData<EdgeEvent> findEdgeEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink); | |
32 | - | |
29 | + TimePageData<EdgeEvent> findEdgeEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate); | |
33 | 30 | } | ... | ... |
... | ... | @@ -35,7 +35,7 @@ import java.util.List; |
35 | 35 | public class BaseEdgeEventService implements EdgeEventService { |
36 | 36 | |
37 | 37 | @Autowired |
38 | - public EdgeEventDao edgeEventDao; | |
38 | + private EdgeEventDao edgeEventDao; | |
39 | 39 | |
40 | 40 | @Override |
41 | 41 | public ListenableFuture<EdgeEvent> saveAsync(EdgeEvent edgeEvent) { |
... | ... | @@ -44,8 +44,8 @@ public class BaseEdgeEventService implements EdgeEventService { |
44 | 44 | } |
45 | 45 | |
46 | 46 | @Override |
47 | - public TimePageData<EdgeEvent> findEdgeEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink) { | |
48 | - List<EdgeEvent> events = edgeEventDao.findEdgeEvents(tenantId.getId(), edgeId, pageLink); | |
47 | + public TimePageData<EdgeEvent> findEdgeEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate) { | |
48 | + List<EdgeEvent> events = edgeEventDao.findEdgeEvents(tenantId.getId(), edgeId, pageLink, withTsUpdate); | |
49 | 49 | return new TimePageData<>(events, pageLink); |
50 | 50 | } |
51 | 51 | ... | ... |
... | ... | @@ -53,7 +53,7 @@ public class CassandraEdgeEventDao extends CassandraAbstractSearchTimeDao<EdgeEv |
53 | 53 | } |
54 | 54 | |
55 | 55 | @Override |
56 | - public List<EdgeEvent> findEdgeEvents(UUID tenantId, EdgeId edgeId, TimePageLink pageLink) { | |
56 | + public List<EdgeEvent> findEdgeEvents(UUID tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate) { | |
57 | 57 | return null; |
58 | 58 | } |
59 | 59 | } | ... | ... |
... | ... | @@ -46,6 +46,6 @@ public interface EdgeEventDao extends Dao<EdgeEvent> { |
46 | 46 | * @param pageLink the pageLink |
47 | 47 | * @return the event list |
48 | 48 | */ |
49 | - List<EdgeEvent> findEdgeEvents(UUID tenantId, EdgeId edgeId, TimePageLink pageLink); | |
49 | + List<EdgeEvent> findEdgeEvents(UUID tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate); | |
50 | 50 | |
51 | 51 | } | ... | ... |
... | ... | @@ -26,6 +26,7 @@ import org.springframework.data.jpa.domain.Specification; |
26 | 26 | import org.springframework.data.repository.CrudRepository; |
27 | 27 | import org.springframework.stereotype.Component; |
28 | 28 | import org.thingsboard.server.common.data.UUIDConverter; |
29 | +import org.thingsboard.server.common.data.audit.ActionType; | |
29 | 30 | import org.thingsboard.server.common.data.edge.EdgeEvent; |
30 | 31 | import org.thingsboard.server.common.data.id.EdgeEventId; |
31 | 32 | import org.thingsboard.server.common.data.id.EdgeId; |
... | ... | @@ -75,9 +76,9 @@ public class JpaBaseEdgeEventDao extends JpaAbstractSearchTimeDao<EdgeEventEntit |
75 | 76 | } |
76 | 77 | |
77 | 78 | @Override |
78 | - public List<EdgeEvent> findEdgeEvents(UUID tenantId, EdgeId edgeId, TimePageLink pageLink) { | |
79 | + public List<EdgeEvent> findEdgeEvents(UUID tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate) { | |
79 | 80 | Specification<EdgeEventEntity> timeSearchSpec = JpaAbstractSearchTimeDao.getTimeSearchPageSpec(pageLink, "id"); |
80 | - Specification<EdgeEventEntity> fieldsSpec = getEntityFieldsSpec(tenantId, edgeId); | |
81 | + Specification<EdgeEventEntity> fieldsSpec = getEntityFieldsSpec(tenantId, edgeId, withTsUpdate); | |
81 | 82 | Sort.Direction sortDirection = pageLink.isAscOrder() ? Sort.Direction.ASC : Sort.Direction.DESC; |
82 | 83 | Pageable pageable = PageRequest.of(0, pageLink.getLimit(), sortDirection, ID_PROPERTY); |
83 | 84 | return DaoUtil.convertDataList(edgeEventRepository.findAll(Specification.where(timeSearchSpec).and(fieldsSpec), pageable).getContent()); |
... | ... | @@ -95,7 +96,7 @@ public class JpaBaseEdgeEventDao extends JpaAbstractSearchTimeDao<EdgeEventEntit |
95 | 96 | return Optional.of(DaoUtil.getData(edgeEventRepository.save(entity))); |
96 | 97 | } |
97 | 98 | |
98 | - private Specification<EdgeEventEntity> getEntityFieldsSpec(UUID tenantId, EdgeId edgeId) { | |
99 | + private Specification<EdgeEventEntity> getEntityFieldsSpec(UUID tenantId, EdgeId edgeId, boolean withTsUpdate) { | |
99 | 100 | return (root, criteriaQuery, criteriaBuilder) -> { |
100 | 101 | List<Predicate> predicates = new ArrayList<>(); |
101 | 102 | if (tenantId != null) { |
... | ... | @@ -106,6 +107,10 @@ public class JpaBaseEdgeEventDao extends JpaAbstractSearchTimeDao<EdgeEventEntit |
106 | 107 | Predicate entityIdPredicate = criteriaBuilder.equal(root.get("edgeId"), UUIDConverter.fromTimeUUID(edgeId.getId())); |
107 | 108 | predicates.add(entityIdPredicate); |
108 | 109 | } |
110 | + if (!withTsUpdate) { | |
111 | + Predicate edgeEventActionPredicate = criteriaBuilder.notEqual(root.get("edgeEventAction"), ActionType.TIMESERIES_UPDATED.name()); | |
112 | + predicates.add(edgeEventActionPredicate); | |
113 | + } | |
109 | 114 | return criteriaBuilder.and(predicates.toArray(new Predicate[]{})); |
110 | 115 | }; |
111 | 116 | } | ... | ... |
... | ... | @@ -19,6 +19,7 @@ import com.datastax.driver.core.utils.UUIDs; |
19 | 19 | import org.junit.Assert; |
20 | 20 | import org.junit.Test; |
21 | 21 | import org.thingsboard.server.common.data.DataConstants; |
22 | +import org.thingsboard.server.common.data.audit.ActionType; | |
22 | 23 | import org.thingsboard.server.common.data.edge.EdgeEvent; |
23 | 24 | import org.thingsboard.server.common.data.edge.EdgeEventType; |
24 | 25 | import org.thingsboard.server.common.data.id.DeviceId; |
... | ... | @@ -82,7 +83,7 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest { |
82 | 83 | EdgeEvent savedEdgeEvent3 = saveEdgeEventWithProvidedTime(eventTime + 2, edgeId, deviceId, tenantId); |
83 | 84 | saveEdgeEventWithProvidedTime(timeAfterEndTime, edgeId, deviceId, tenantId); |
84 | 85 | |
85 | - TimePageData<EdgeEvent> edgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, new TimePageLink(2, startTime, endTime, false)); | |
86 | + TimePageData<EdgeEvent> edgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, new TimePageLink(2, startTime, endTime, false), true); | |
86 | 87 | |
87 | 88 | Assert.assertNotNull(edgeEvents.getData()); |
88 | 89 | Assert.assertTrue(edgeEvents.getData().size() == 2); |
... | ... | @@ -91,7 +92,7 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest { |
91 | 92 | Assert.assertTrue(edgeEvents.hasNext()); |
92 | 93 | Assert.assertNotNull(edgeEvents.getNextPageLink()); |
93 | 94 | |
94 | - edgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, edgeEvents.getNextPageLink()); | |
95 | + edgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, edgeEvents.getNextPageLink(), true); | |
95 | 96 | |
96 | 97 | Assert.assertNotNull(edgeEvents.getData()); |
97 | 98 | Assert.assertTrue(edgeEvents.getData().size() == 1); |
... | ... | @@ -100,6 +101,26 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest { |
100 | 101 | Assert.assertNull(edgeEvents.getNextPageLink()); |
101 | 102 | } |
102 | 103 | |
104 | + @Test | |
105 | + public void findEdgeEventsWithTsUpdateAndWithout() throws Exception { | |
106 | + EdgeId edgeId = new EdgeId(UUIDs.timeBased()); | |
107 | + DeviceId deviceId = new DeviceId(UUIDs.timeBased()); | |
108 | + TenantId tenantId = new TenantId(UUIDs.timeBased()); | |
109 | + TimePageLink pageLink = new TimePageLink(1); | |
110 | + | |
111 | + EdgeEvent edgeEventWithTsUpdate = generateEdgeEvent(tenantId, edgeId, deviceId, ActionType.TIMESERIES_UPDATED.name()); | |
112 | + edgeEventService.saveAsync(edgeEventWithTsUpdate); | |
113 | + | |
114 | + TimePageData<EdgeEvent> allEdgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, true); | |
115 | + TimePageData<EdgeEvent> edgeEventsWithoutTsUpdate = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, false); | |
116 | + | |
117 | + Assert.assertNotNull(allEdgeEvents.getData()); | |
118 | + Assert.assertNotNull(edgeEventsWithoutTsUpdate.getData()); | |
119 | + Assert.assertEquals(1, allEdgeEvents.getData().size()); | |
120 | + Assert.assertEquals(allEdgeEvents.getData().get(0).getUuidId(), edgeEventWithTsUpdate.getUuidId()); | |
121 | + Assert.assertTrue(edgeEventsWithoutTsUpdate.getData().isEmpty()); | |
122 | + } | |
123 | + | |
103 | 124 | private EdgeEvent saveEdgeEventWithProvidedTime(long time, EdgeId edgeId, EntityId entityId, TenantId tenantId) throws Exception { |
104 | 125 | EdgeEvent edgeEvent = generateEdgeEvent(tenantId, edgeId, entityId, DataConstants.ENTITY_CREATED); |
105 | 126 | edgeEvent.setId(new EdgeEventId(UUIDs.startOf(time))); | ... | ... |
... | ... | @@ -389,6 +389,25 @@ export default angular.module('thingsboard.types', []) |
389 | 389 | customer: "CUSTOMER", |
390 | 390 | relation: "RELATION" |
391 | 391 | }, |
392 | + edgeEventAction: { | |
393 | + updated: "UPDATED", | |
394 | + added: "ADDED", | |
395 | + assignedToEdge: "ASSIGNED_TO_EDGE", | |
396 | + deleted: "DELETED", | |
397 | + unassignedFromEdge: "UNASSIGNED_FROM_EDGE", | |
398 | + alarmAck: "ALARM_ACK", | |
399 | + alarmClear: "ALARM_CLEAR", | |
400 | + credentialsUpdated: "CREDENTIALS_UPDATED", | |
401 | + attributesUpdated: "ATTRIBUTES_UPDATED", | |
402 | + attributesDeleted: "ATTRIBUTES_DELETED", | |
403 | + timeseriesUpdated: "TIMESERIES_UPDATED" | |
404 | + }, | |
405 | + edgeAttributeKeys: { | |
406 | + active: "active", | |
407 | + lastConnectTime: "lastConnectTime", | |
408 | + lastDisconnectTime: "lastDisconnectTime", | |
409 | + queueStartTs: "queueStartTs" | |
410 | + }, | |
392 | 411 | importEntityColumnType: { |
393 | 412 | name: { |
394 | 413 | name: 'import.column-type.name', | ... | ... |
... | ... | @@ -21,6 +21,21 @@ |
21 | 21 | <md-button ng-click="onUnassignFromCustomer({event: $event, isPublic: isPublic})" |
22 | 22 | ng-show="!isEdit && (edgeScope === 'customer' || edgeScope === 'tenant') && isAssignedToCustomer" |
23 | 23 | class="md-raised md-primary">{{ isPublic ? 'edge.make-private' : 'edge.unassign-from-customer' | translate }}</md-button> |
24 | +<md-button ng-click="onManageEdgeAssets({event: $event})" | |
25 | + ng-show="!isEdit && edgeScope === 'tenant'" | |
26 | + class="md-raised md-primary">{{ 'edge.manage-edge-assets' | translate }}</md-button> | |
27 | +<md-button ng-click="onManageEdgeDevices({event: $event})" | |
28 | + ng-show="!isEdit && edgeScope === 'tenant'" | |
29 | + class="md-raised md-primary">{{ 'edge.manage-edge-devices' | translate }}</md-button> | |
30 | +<md-button ng-click="onManageEdgeEntityViews({event: $event})" | |
31 | + ng-show="!isEdit && edgeScope === 'tenant'" | |
32 | + class="md-raised md-primary">{{ 'edge.manage-edge-entity-views' | translate }}</md-button> | |
33 | +<md-button ng-click="onManageEdgeDashboards({event: $event})" | |
34 | + ng-show="!isEdit && edgeScope === 'tenant'" | |
35 | + class="md-raised md-primary">{{ 'edge.manage-edge-dashboards' | translate }}</md-button> | |
36 | +<md-button ng-click="onManageEdgeRuleChains({event: $event})" | |
37 | + ng-show="!isEdit && edgeScope === 'tenant'" | |
38 | + class="md-raised md-primary">{{ 'edge.manage-edge-rulechains' | translate }}</md-button> | |
24 | 39 | <md-button ng-click="onDeleteEdge({event: $event})" |
25 | 40 | ng-show="!isEdit && edgeScope === 'tenant'" |
26 | 41 | class="md-raised md-primary">{{ 'edge.delete' | translate }}</md-button> | ... | ... |
... | ... | @@ -129,6 +129,11 @@ export function EdgeController($rootScope, userService, edgeService, customerSer |
129 | 129 | vm.assignToCustomer = assignToCustomer; |
130 | 130 | vm.makePublic = makePublic; |
131 | 131 | vm.unassignFromCustomer = unassignFromCustomer; |
132 | + vm.openEdgeAssets = openEdgeAssets; | |
133 | + vm.openEdgeDevices = openEdgeDevices; | |
134 | + vm.openEdgeEntityViews = openEdgeEntityViews; | |
135 | + vm.openEdgeDashboards = openEdgeDashboards; | |
136 | + vm.openEdgeRuleChains = openEdgeRuleChains; | |
132 | 137 | |
133 | 138 | initController(); |
134 | 139 | ... | ... |
... | ... | @@ -96,6 +96,11 @@ export default function EdgeDirective($compile, $templateCache, $translate, $mdD |
96 | 96 | onAssignToCustomer: '&', |
97 | 97 | onMakePublic: '&', |
98 | 98 | onUnassignFromCustomer: '&', |
99 | + onManageEdgeAssets: '&', | |
100 | + onManageEdgeDevices: '&', | |
101 | + onManageEdgeEntityViews: '&', | |
102 | + onManageEdgeDashboards: '&', | |
103 | + onManageEdgeRuleChains: '&', | |
99 | 104 | onDeleteEdge: '&' |
100 | 105 | } |
101 | 106 | }; | ... | ... |
... | ... | @@ -29,6 +29,11 @@ |
29 | 29 | on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])" |
30 | 30 | on-make-public="vm.makePublic(event, vm.grid.detailsConfig.currentItem)" |
31 | 31 | on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)" |
32 | + on-manage-edge-assets="vm.openEdgeAssets(event, vm.grid.detailsConfig.currentItem)" | |
33 | + on-manage-edge-devices="vm.openEdgeDevices(event, vm.grid.detailsConfig.currentItem)" | |
34 | + on-manage-edge-entity-views="vm.openEdgeEntityViews(event, vm.grid.detailsConfig.currentItem)" | |
35 | + on-manage-edge-dashboards="vm.openEdgeDashboards(event, vm.grid.detailsConfig.currentItem)" | |
36 | + on-manage-edge-rule-chains="vm.openEdgeRuleChains(event, vm.grid.detailsConfig.currentItem)" | |
32 | 37 | on-delete-edge="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-edge> |
33 | 38 | </md-tab> |
34 | 39 | <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" md-on-select="vm.grid.triggerResize()" label="{{ 'attribute.attributes' | translate }}"> | ... | ... |
... | ... | @@ -17,6 +17,7 @@ |
17 | 17 | --> |
18 | 18 | <div translate class="tb-cell" flex="20">event.event-time</div> |
19 | 19 | <div translate class="tb-cell" flex="20">event.event-type</div> |
20 | -<div translate class="tb-cell" flex="20">edge.event-action</div> | |
21 | -<div translate class="tb-cell" flex="30">edge.entity-id</div> | |
22 | -<div translate class="tb-cell" flex="20">edge.entity-info</div> | |
20 | +<div translate class="tb-cell" flex="40">edge.event-action</div> | |
21 | +<div translate class="tb-cell" flex="20">edge.entity-id</div> | |
22 | +<div translate class="tb-cell" flex="15">edge.status</div> | |
23 | +<div translate class="tb-cell" flex="10">edge.entity-info</div> | ... | ... |
... | ... | @@ -15,11 +15,12 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<div class="tb-cell" flex="20">{{event.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}}</div> | |
19 | -<div class="tb-cell" flex="20">{{event.edgeEventType}}</div> | |
20 | -<div class="tb-cell" flex="20">{{event.edgeEventAction}}</div> | |
21 | -<div class="tb-cell" flex="30">{{event.entityId}}</div> | |
22 | -<div class="tb-cell" flex="20"> | |
18 | +<div class="tb-cell" flex="20">{{ event.createdTime | date : 'yyyy-MM-dd HH:mm:ss' }}</div> | |
19 | +<div class="tb-cell" flex="20">{{ event.edgeEventType }}</div> | |
20 | +<div class="tb-cell" flex="40">{{ event.edgeEventAction }}</div> | |
21 | +<div class="tb-cell" flex="20">{{ event.entityId }}</div> | |
22 | +<div class="tb-cell" flex="15" ng-style="isPending ? {'color': 'rgba(0, 0, 0, .38)'} : {'color': '#000'}">{{ updateStatus(event.createdTime) | translate }}</div> | |
23 | +<div class="tb-cell" flex="10"> | |
23 | 24 | <md-button class="md-icon-button md-primary" |
24 | 25 | ng-click="showEdgeEntityContent($event, 'edge.entity-info', 'JSON')" |
25 | 26 | aria-label="{{ 'action.view' | translate }}"> |
... | ... | @@ -33,3 +34,4 @@ |
33 | 34 | </md-button> |
34 | 35 | </div> |
35 | 36 | |
37 | + | ... | ... |
... | ... | @@ -102,37 +102,45 @@ export default function EventRowDirective($compile, $templateCache, $mdDialog, $ |
102 | 102 | switch(scope.event.edgeEventType) { |
103 | 103 | case types.edgeEventType.relation: |
104 | 104 | content = angular.toJson(scope.event.entityBody); |
105 | + showDialog(); | |
105 | 106 | break; |
106 | 107 | case types.edgeEventType.ruleChainMetaData: |
107 | - content = ruleChainService.getRuleChainMetaData(scope.event.entityId, {}).then( | |
108 | + content = ruleChainService.getRuleChainMetaData(scope.event.entityId, {ignoreErrors: true}).then( | |
108 | 109 | function success(info) { |
110 | + showDialog(); | |
109 | 111 | return angular.toJson(info); |
110 | 112 | }, function fail() { |
111 | - toast.showError($translate.instant('edge.load-entity-error')); | |
113 | + showError(); | |
112 | 114 | }); |
113 | 115 | break; |
114 | 116 | default: |
115 | - content = entityService.getEntity(scope.event.edgeEventType, scope.event.entityId, {}).then( | |
117 | + content = entityService.getEntity(scope.event.edgeEventType, scope.event.entityId, {ignoreErrors: true}).then( | |
116 | 118 | function success(info) { |
119 | + showDialog(); | |
117 | 120 | return angular.toJson(info); |
118 | 121 | }, function fail() { |
119 | - toast.showError($translate.instant('edge.load-entity-error')); | |
122 | + showError(); | |
120 | 123 | }); |
121 | 124 | break; |
122 | 125 | } |
123 | - $mdDialog.show({ | |
124 | - controller: 'EventContentDialogController', | |
125 | - controllerAs: 'vm', | |
126 | - templateUrl: eventErrorDialogTemplate, | |
127 | - locals: {content: content, title: title, contentType: contentType, showingCallback: onShowingCallback}, | |
128 | - parent: angular.element($document[0].body), | |
129 | - fullscreen: true, | |
130 | - targetEvent: $event, | |
131 | - multiple: true, | |
132 | - onShowing: function(scope, element) { | |
133 | - onShowingCallback.onShowing(scope, element); | |
134 | - } | |
135 | - }); | |
126 | + function showDialog() { | |
127 | + $mdDialog.show({ | |
128 | + controller: 'EventContentDialogController', | |
129 | + controllerAs: 'vm', | |
130 | + templateUrl: eventErrorDialogTemplate, | |
131 | + locals: {content: content, title: title, contentType: contentType, showingCallback: onShowingCallback}, | |
132 | + parent: angular.element($document[0].body), | |
133 | + fullscreen: true, | |
134 | + targetEvent: $event, | |
135 | + multiple: true, | |
136 | + onShowing: function(scope, element) { | |
137 | + onShowingCallback.onShowing(scope, element); | |
138 | + } | |
139 | + }); | |
140 | + } | |
141 | + function showError() { | |
142 | + toast.showError($translate.instant('edge.load-entity-error')); | |
143 | + } | |
136 | 144 | } |
137 | 145 | |
138 | 146 | scope.checkTooltip = function($event) { |
... | ... | @@ -144,6 +152,20 @@ export default function EventRowDirective($compile, $templateCache, $mdDialog, $ |
144 | 152 | } |
145 | 153 | |
146 | 154 | $compile(element.contents())(scope); |
155 | + | |
156 | + scope.updateStatus = function(eventCreatedTime) { | |
157 | + if (scope.queueStartTs) { | |
158 | + var status; | |
159 | + if (eventCreatedTime < scope.queueStartTs) { | |
160 | + status = $translate.instant('edge.success'); | |
161 | + scope.isPending = false; | |
162 | + } else { | |
163 | + status = $translate.instant('edge.failed'); | |
164 | + scope.isPending = true; | |
165 | + } | |
166 | + return status; | |
167 | + } | |
168 | + } | |
147 | 169 | } |
148 | 170 | |
149 | 171 | return { | ... | ... |
... | ... | @@ -22,7 +22,8 @@ import eventTableTemplate from './event-table.tpl.html'; |
22 | 22 | /* eslint-enable import/no-unresolved, import/default */ |
23 | 23 | |
24 | 24 | /*@ngInject*/ |
25 | -export default function EventTableDirective($compile, $templateCache, $rootScope, types, eventService, edgeService) { | |
25 | +export default function EventTableDirective($compile, $templateCache, $rootScope, types, | |
26 | + eventService, edgeService, attributeService) { | |
26 | 27 | |
27 | 28 | var linker = function (scope, element, attrs) { |
28 | 29 | |
... | ... | @@ -30,11 +31,16 @@ export default function EventTableDirective($compile, $templateCache, $rootScope |
30 | 31 | |
31 | 32 | element.html(template); |
32 | 33 | |
34 | + scope.eventTypeScope = angular.copy(types.eventType); | |
35 | + if (scope.entityType !== types.entityType.edge) { | |
36 | + delete scope.eventTypeScope.edgeEvent; | |
37 | + } | |
38 | + | |
33 | 39 | if (attrs.disabledEventTypes) { |
34 | 40 | var disabledEventTypes = attrs.disabledEventTypes.split(','); |
35 | 41 | scope.eventTypes = {}; |
36 | - for (var type in types.eventType) { | |
37 | - var eventType = types.eventType[type]; | |
42 | + for (var type in scope.eventTypeScope) { | |
43 | + var eventType = scope.eventTypeScope[type]; | |
38 | 44 | var enabled = true; |
39 | 45 | for (var i=0;i<disabledEventTypes.length;i++) { |
40 | 46 | if (eventType.value === disabledEventTypes[i]) { |
... | ... | @@ -47,7 +53,7 @@ export default function EventTableDirective($compile, $templateCache, $rootScope |
47 | 53 | } |
48 | 54 | } |
49 | 55 | } else { |
50 | - scope.eventTypes = angular.copy(types.eventType); | |
56 | + scope.eventTypes = angular.copy(scope.eventTypeScope); | |
51 | 57 | } |
52 | 58 | |
53 | 59 | if (attrs.debugEventTypes) { |
... | ... | @@ -106,6 +112,7 @@ export default function EventTableDirective($compile, $templateCache, $rootScope |
106 | 112 | scope.eventType, scope.tenantId, scope.events.nextPageLink); |
107 | 113 | } else { |
108 | 114 | promise = edgeService.getEdgeEvents(scope.entityId, scope.events.nextPageLink); |
115 | + scope.loadEdgeInfo(); | |
109 | 116 | } |
110 | 117 | if (promise) { |
111 | 118 | scope.events.pending = true; |
... | ... | @@ -135,6 +142,7 @@ export default function EventTableDirective($compile, $templateCache, $rootScope |
135 | 142 | |
136 | 143 | scope.$watch("entityId", function(newVal, prevVal) { |
137 | 144 | if (newVal && !angular.equals(newVal, prevVal)) { |
145 | + scope.loadEdgeInfo(); | |
138 | 146 | scope.resetFilter(); |
139 | 147 | scope.reload(); |
140 | 148 | } |
... | ... | @@ -212,6 +220,53 @@ export default function EventTableDirective($compile, $templateCache, $rootScope |
212 | 220 | return false; |
213 | 221 | } |
214 | 222 | |
223 | + scope.subscriptionId = null; | |
224 | + scope.queueStartTs; | |
225 | + | |
226 | + scope.loadEdgeInfo = function() { | |
227 | + attributeService.getEntityAttributesValues(scope.entityType, scope.entityId, types.attributesScope.server.value, | |
228 | + types.edgeAttributeKeys.queueStartTs, {}) | |
229 | + .then(function success(attributes) { | |
230 | + scope.onUpdate(attributes); | |
231 | + }); | |
232 | + | |
233 | + scope.checkSubscription(); | |
234 | + | |
235 | + attributeService.getEntityAttributes(scope.entityType, scope.entityId, types.attributesScope.server.value, {order: '', limit: 1, page: 1, search: ''}, | |
236 | + function (attributes) { | |
237 | + if (attributes && attributes.data) { | |
238 | + scope.onUpdate(attributes.data); | |
239 | + } | |
240 | + }); | |
241 | + } | |
242 | + | |
243 | + scope.onUpdate = function(attributes) { | |
244 | + let edge = attributes.reduce(function (map, attribute) { | |
245 | + map[attribute.key] = attribute; | |
246 | + return map; | |
247 | + }, {}); | |
248 | + if (edge.queueStartTs) { | |
249 | + scope.queueStartTs = edge.queueStartTs.lastUpdateTs; | |
250 | + } | |
251 | + } | |
252 | + | |
253 | + scope.checkSubscription = function() { | |
254 | + var newSubscriptionId = null; | |
255 | + if (scope.entityId && scope.entityType && types.attributesScope.server.value) { | |
256 | + newSubscriptionId = attributeService.subscribeForEntityAttributes(scope.entityType, scope.entityId, types.attributesScope.server.value); | |
257 | + } | |
258 | + if (scope.subscriptionId && scope.subscriptionId != newSubscriptionId) { | |
259 | + attributeService.unsubscribeForEntityAttributes(scope.subscriptionId); | |
260 | + } | |
261 | + scope.subscriptionId = newSubscriptionId; | |
262 | + } | |
263 | + | |
264 | + scope.$on('$destroy', function () { | |
265 | + if (scope.subscriptionId) { | |
266 | + attributeService.unsubscribeForEntityAttributes(scope.subscriptionId); | |
267 | + } | |
268 | + }); | |
269 | + | |
215 | 270 | scope.reload(); |
216 | 271 | |
217 | 272 | $compile(element.contents())(scope); | ... | ... |
... | ... | @@ -821,7 +821,7 @@ |
821 | 821 | "make-private-edge-text": "After the confirmation the edge and all its data will be made private and won't be accessible by others.", |
822 | 822 | "import": "Import edge", |
823 | 823 | "label": "Label", |
824 | - "load-entity-error": "Could not load entity info", | |
824 | + "load-entity-error": "Entity not found. Failed to load info", | |
825 | 825 | "assign-new-edge": "Assign new edge", |
826 | 826 | "manage-edge-dashboards": "Manage edge dashboards", |
827 | 827 | "unassign-from-edge": "Unassign from edge", |
... | ... | @@ -843,7 +843,10 @@ |
843 | 843 | "entity-views": "Edge entity views", |
844 | 844 | "set-root-rule-chain-text": "Please select root rule chain for edge(s)", |
845 | 845 | "set-root-rule-chain-to-edges": "Set root rule chain for Edge(s)", |
846 | - "set-root-rule-chain-to-edges-text": "Set root rule chain for { count, plural, 1 {1 edge} other {# edges} }" | |
846 | + "set-root-rule-chain-to-edges-text": "Set root rule chain for { count, plural, 1 {1 edge} other {# edges} }", | |
847 | + "status": "Received by edge", | |
848 | + "success": "Deployed", | |
849 | + "failed": "Pending" | |
847 | 850 | }, |
848 | 851 | "error": { |
849 | 852 | "unable-to-connect": "Unable to connect to the server! Please check your internet connection.", | ... | ... |