Commit 933c502ce3878ca511f1b5e4c05b2260dc6978a8

Authored by Andrii Shvaika
2 parents c2e36851 dc151ace

Merge remote-tracking branch 'origin/master'

... ... @@ -75,7 +75,8 @@ CREATE OR REPLACE PROCEDURE drop_all_idx()
75 75 $$
76 76 BEGIN
77 77 DROP INDEX IF EXISTS idx_alarm_originator_alarm_type;
78   - DROP INDEX IF EXISTS idx_alarm_originator_alarm_time;
  78 + DROP INDEX IF EXISTS idx_alarm_originator_created_time;
  79 + DROP INDEX IF EXISTS idx_alarm_tenant_created_time;
79 80 DROP INDEX IF EXISTS idx_event_type_entity_id;
80 81 DROP INDEX IF EXISTS idx_relation_to_id;
81 82 DROP INDEX IF EXISTS idx_relation_from_id;
... ... @@ -93,7 +94,8 @@ CREATE OR REPLACE PROCEDURE create_all_idx()
93 94 $$
94 95 BEGIN
95 96 CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type ON alarm(originator_id, type, start_ts DESC);
96   - CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_time ON alarm(originator_id, created_time DESC);
  97 + CREATE INDEX IF NOT EXISTS idx_alarm_originator_created_time ON alarm(originator_id, created_time DESC);
  98 + CREATE INDEX IF NOT EXISTS idx_alarm_tenant_created_time ON alarm(tenant_id, created_time DESC);
97 99 CREATE INDEX IF NOT EXISTS idx_event_type_entity_id ON event(tenant_id, event_type, entity_type, entity_id);
98 100 CREATE INDEX IF NOT EXISTS idx_relation_to_id ON relation(relation_type_group, to_type, to_id);
99 101 CREATE INDEX IF NOT EXISTS idx_relation_from_id ON relation(relation_type_group, from_type, from_id);
... ...
... ... @@ -286,7 +286,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
286 286 int dynamicQueryInvocationCntValue = stats.getDynamicQueryInvocationCnt().getAndSet(0);
287 287 long dynamicQueryInvocationTimeValue = stats.getDynamicQueryTimeSpent().getAndSet(0);
288 288 long dynamicQueryCnt = subscriptionsBySessionId.values().stream().map(Map::values).count();
289   - if (regularQueryInvocationCntValue > 0 || dynamicQueryInvocationCntValue > 0 || dynamicQueryCnt > 0) {
  289 + if (regularQueryInvocationCntValue > 0 || dynamicQueryInvocationCntValue > 0 || dynamicQueryCnt > 0 || alarmQueryInvocationCntValue > 0) {
290 290 log.info("Stats: regularQueryInvocationCnt = [{}], regularQueryInvocationTime = [{}], " +
291 291 "dynamicQueryCnt = [{}] dynamicQueryInvocationCnt = [{}], dynamicQueryInvocationTime = [{}], " +
292 292 "alarmQueryInvocationCnt = [{}], alarmQueryInvocationTime = [{}]",
... ...
... ... @@ -58,6 +58,9 @@ import org.thingsboard.server.service.queue.TbClusterService;
58 58 import org.thingsboard.server.service.state.DeviceStateService;
59 59
60 60 import java.util.UUID;
  61 +import java.util.concurrent.ConcurrentHashMap;
  62 +import java.util.concurrent.ConcurrentMap;
  63 +import java.util.concurrent.locks.Lock;
61 64 import java.util.concurrent.locks.ReentrantLock;
62 65
63 66 /**
... ... @@ -92,7 +95,7 @@ public class DefaultTransportApiService implements TransportApiService {
92 95 @Autowired
93 96 protected TbClusterService tbClusterService;
94 97
95   - private ReentrantLock deviceCreationLock = new ReentrantLock();
  98 + private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
96 99
97 100 @Override
98 101 public ListenableFuture<TbProtoQueueMsg<TransportApiResponseMsg>> handle(TbProtoQueueMsg<TransportApiRequestMsg> tbProtoQueueMsg) {
... ... @@ -125,6 +128,7 @@ public class DefaultTransportApiService implements TransportApiService {
125 128 DeviceId gatewayId = new DeviceId(new UUID(requestMsg.getGatewayIdMSB(), requestMsg.getGatewayIdLSB()));
126 129 ListenableFuture<Device> gatewayFuture = deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, gatewayId);
127 130 return Futures.transform(gatewayFuture, gateway -> {
  131 + Lock deviceCreationLock = deviceCreationLocks.computeIfAbsent(requestMsg.getDeviceName(), id -> new ReentrantLock());
128 132 deviceCreationLock.lock();
129 133 try {
130 134 Device device = deviceService.findDeviceByTenantIdAndName(gateway.getTenantId(), requestMsg.getDeviceName());
... ...
... ... @@ -20,6 +20,9 @@ import org.apache.commons.lang3.StringUtils;
20 20 import org.springframework.beans.factory.annotation.Autowired;
21 21 import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
22 22 import org.springframework.stereotype.Repository;
  23 +import org.springframework.transaction.TransactionStatus;
  24 +import org.springframework.transaction.support.TransactionCallback;
  25 +import org.springframework.transaction.support.TransactionTemplate;
23 26 import org.thingsboard.server.common.data.EntityType;
24 27 import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
25 28 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
... ... @@ -53,7 +56,6 @@ import java.util.stream.Collectors;
53 56 public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
54 57
55 58 private static final Map<String, String> alarmFieldColumnMap = new HashMap<>();
56   - private static final List<String> uniqueAlarmFields = new ArrayList<>();
57 59
58 60 static {
59 61 alarmFieldColumnMap.put("createdTime", ModelConstants.CREATED_TIME_PROPERTY);
... ... @@ -72,11 +74,9 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
72 74 alarmFieldColumnMap.put("originator_id", ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY);
73 75 alarmFieldColumnMap.put("originator_type", ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY);
74 76 alarmFieldColumnMap.put("originator", "originator_name");
75   -
76   - uniqueAlarmFields.addAll(new HashSet<>(alarmFieldColumnMap.values()));
77 77 }
78 78
79   - public static final String SELECT_ORIGINATOR_NAME = " CASE" +
  79 + private static final String SELECT_ORIGINATOR_NAME = " CASE" +
80 80 " WHEN a.originator_type = " + EntityType.TENANT.ordinal() +
81 81 " THEN (select title from tenant where id = a.originator_id)" +
82 82 " WHEN a.originator_type = " + EntityType.CUSTOMER.ordinal() +
... ... @@ -93,7 +93,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
93 93 " THEN (select name from entity_view where id = a.originator_id)" +
94 94 " END as originator_name";
95 95
96   - public static final String FIELDS_SELECTION = "select a.id as id," +
  96 + private static final String FIELDS_SELECTION = "select a.id as id," +
97 97 " a.created_time as created_time," +
98 98 " a.ack_ts as ack_ts," +
99 99 " a.clear_ts as clear_ts," +
... ... @@ -109,130 +109,136 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
109 109 " a.propagate_relation_types as propagate_relation_types, " +
110 110 " a.type as type," + SELECT_ORIGINATOR_NAME + ", ";
111 111
112   - public static final String JOIN_RELATIONS = "left join relation r on r.relation_type_group = 'ALARM' and r.relation_type = 'ANY' and a.id = r.to_id and r.from_id in (:entity_ids)";
  112 + private static final String JOIN_RELATIONS = "left join relation r on r.relation_type_group = 'ALARM' and r.relation_type = 'ANY' and a.id = r.to_id and r.from_id in (:entity_ids)";
113 113
114   - @Autowired
115   - protected NamedParameterJdbcTemplate jdbcTemplate;
  114 + protected final NamedParameterJdbcTemplate jdbcTemplate;
  115 + private final TransactionTemplate transactionTemplate;
116 116
  117 + public DefaultAlarmQueryRepository(NamedParameterJdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate) {
  118 + this.jdbcTemplate = jdbcTemplate;
  119 + this.transactionTemplate = transactionTemplate;
  120 + }
117 121
118 122 @Override
119 123 public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId,
120 124 AlarmDataQuery query, Collection<EntityId> orderedEntityIds) {
121   - AlarmDataPageLink pageLink = query.getPageLink();
122   - QueryContext ctx = new QueryContext();
123   - ctx.addUuidListParameter("entity_ids", orderedEntityIds.stream().map(EntityId::getId).collect(Collectors.toList()));
  125 + return transactionTemplate.execute(status -> {
  126 + AlarmDataPageLink pageLink = query.getPageLink();
  127 + QueryContext ctx = new QueryContext();
  128 + ctx.addUuidListParameter("entity_ids", orderedEntityIds.stream().map(EntityId::getId).collect(Collectors.toList()));
124 129
125   - StringBuilder selectPart = new StringBuilder(FIELDS_SELECTION);
126   - StringBuilder fromPart = new StringBuilder(" from alarm a ");
127   - StringBuilder wherePart = new StringBuilder(" where ");
128   - StringBuilder sortPart = new StringBuilder(" order by ");
129   - boolean addAnd = false;
130   - if (pageLink.isSearchPropagatedAlarms()) {
131   - selectPart.append(" CASE WHEN r.from_id IS NULL THEN a.originator_id ELSE r.from_id END as entity_id ");
132   - fromPart.append(JOIN_RELATIONS);
133   - wherePart.append(buildPermissionsQuery(tenantId, customerId, ctx));
134   - addAnd = true;
135   - } else {
136   - selectPart.append(" a.originator_id as entity_id ");
137   - }
138   - EntityDataSortOrder sortOrder = pageLink.getSortOrder();
139   - if (sortOrder != null && sortOrder.getKey().getType().equals(EntityKeyType.ALARM_FIELD)) {
140   - String sortOrderKey = sortOrder.getKey().getKey();
141   - sortPart.append(alarmFieldColumnMap.getOrDefault(sortOrderKey, sortOrderKey))
142   - .append(" ").append(sortOrder.getDirection().name());
  130 + StringBuilder selectPart = new StringBuilder(FIELDS_SELECTION);
  131 + StringBuilder fromPart = new StringBuilder(" from alarm a ");
  132 + StringBuilder wherePart = new StringBuilder(" where ");
  133 + StringBuilder sortPart = new StringBuilder(" order by ");
  134 + boolean addAnd = false;
143 135 if (pageLink.isSearchPropagatedAlarms()) {
144   - wherePart.append(" and (a.originator_id in (:entity_ids) or r.from_id IS NOT NULL)");
145   - } else {
146   - addAndIfNeeded(wherePart, addAnd);
  136 + selectPart.append(" CASE WHEN r.from_id IS NULL THEN a.originator_id ELSE r.from_id END as entity_id ");
  137 + fromPart.append(JOIN_RELATIONS);
  138 + wherePart.append(buildPermissionsQuery(tenantId, customerId, ctx));
147 139 addAnd = true;
148   - wherePart.append(" a.originator_id in (:entity_ids)");
  140 + } else {
  141 + selectPart.append(" a.originator_id as entity_id ");
149 142 }
150   - } else {
151   - fromPart.append(" left join (select * from (VALUES");
152   - int entityIdIdx = 0;
153   - int lastEntityIdIdx = orderedEntityIds.size() - 1;
154   - for (EntityId entityId : orderedEntityIds) {
155   - fromPart.append("(uuid('").append(entityId.getId().toString()).append("'), ").append(entityIdIdx).append(")");
156   - if (entityIdIdx != lastEntityIdIdx) {
157   - fromPart.append(",");
  143 + EntityDataSortOrder sortOrder = pageLink.getSortOrder();
  144 + if (sortOrder != null && sortOrder.getKey().getType().equals(EntityKeyType.ALARM_FIELD)) {
  145 + String sortOrderKey = sortOrder.getKey().getKey();
  146 + sortPart.append(alarmFieldColumnMap.getOrDefault(sortOrderKey, sortOrderKey))
  147 + .append(" ").append(sortOrder.getDirection().name());
  148 + if (pageLink.isSearchPropagatedAlarms()) {
  149 + wherePart.append(" and (a.originator_id in (:entity_ids) or r.from_id IS NOT NULL)");
158 150 } else {
159   - fromPart.append(")");
  151 + addAndIfNeeded(wherePart, addAnd);
  152 + addAnd = true;
  153 + wherePart.append(" a.originator_id in (:entity_ids)");
160 154 }
161   - entityIdIdx++;
162   - }
163   - fromPart.append(" as e(id, priority)) e ");
164   - if (pageLink.isSearchPropagatedAlarms()) {
165   - fromPart.append("on (r.from_id IS NULL and a.originator_id = e.id) or (r.from_id IS NOT NULL and r.from_id = e.id)");
166 155 } else {
167   - fromPart.append("on a.originator_id = e.id");
  156 + fromPart.append(" left join (select * from (VALUES");
  157 + int entityIdIdx = 0;
  158 + int lastEntityIdIdx = orderedEntityIds.size() - 1;
  159 + for (EntityId entityId : orderedEntityIds) {
  160 + fromPart.append("(uuid('").append(entityId.getId().toString()).append("'), ").append(entityIdIdx).append(")");
  161 + if (entityIdIdx != lastEntityIdIdx) {
  162 + fromPart.append(",");
  163 + } else {
  164 + fromPart.append(")");
  165 + }
  166 + entityIdIdx++;
  167 + }
  168 + fromPart.append(" as e(id, priority)) e ");
  169 + if (pageLink.isSearchPropagatedAlarms()) {
  170 + fromPart.append("on (r.from_id IS NULL and a.originator_id = e.id) or (r.from_id IS NOT NULL and r.from_id = e.id)");
  171 + } else {
  172 + fromPart.append("on a.originator_id = e.id");
  173 + }
  174 + sortPart.append("e.priority");
168 175 }
169   - sortPart.append("e.priority");
170   - }
171   -
172   - long startTs;
173   - long endTs;
174   - if (pageLink.getTimeWindow() > 0) {
175   - endTs = System.currentTimeMillis();
176   - startTs = endTs - pageLink.getTimeWindow();
177   - } else {
178   - startTs = pageLink.getStartTs();
179   - endTs = pageLink.getEndTs();
180   - }
181 176
182   - if (startTs > 0) {
183   - addAndIfNeeded(wherePart, addAnd);
184   - addAnd = true;
185   - ctx.addLongParameter("startTime", startTs);
186   - wherePart.append("a.created_time >= :startTime");
187   - }
  177 + long startTs;
  178 + long endTs;
  179 + if (pageLink.getTimeWindow() > 0) {
  180 + endTs = System.currentTimeMillis();
  181 + startTs = endTs - pageLink.getTimeWindow();
  182 + } else {
  183 + startTs = pageLink.getStartTs();
  184 + endTs = pageLink.getEndTs();
  185 + }
188 186
189   - if (endTs > 0) {
190   - addAndIfNeeded(wherePart, addAnd);
191   - addAnd = true;
192   - ctx.addLongParameter("endTime", endTs);
193   - wherePart.append("a.created_time <= :endTime");
194   - }
  187 + if (startTs > 0) {
  188 + addAndIfNeeded(wherePart, addAnd);
  189 + addAnd = true;
  190 + ctx.addLongParameter("startTime", startTs);
  191 + wherePart.append("a.created_time >= :startTime");
  192 + }
195 193
196   - if (pageLink.getTypeList() != null && !pageLink.getTypeList().isEmpty()) {
197   - addAndIfNeeded(wherePart, addAnd);
198   - addAnd = true;
199   - ctx.addStringListParameter("alarmTypes", pageLink.getTypeList());
200   - wherePart.append("a.type in (:alarmTypes)");
201   - }
  194 + if (endTs > 0) {
  195 + addAndIfNeeded(wherePart, addAnd);
  196 + addAnd = true;
  197 + ctx.addLongParameter("endTime", endTs);
  198 + wherePart.append("a.created_time <= :endTime");
  199 + }
202 200
203   - if (pageLink.getSeverityList() != null && !pageLink.getSeverityList().isEmpty()) {
204   - addAndIfNeeded(wherePart, addAnd);
205   - addAnd = true;
206   - ctx.addStringListParameter("alarmSeverities", pageLink.getSeverityList().stream().map(AlarmSeverity::name).collect(Collectors.toList()));
207   - wherePart.append("a.severity in (:alarmSeverities)");
208   - }
  201 + if (pageLink.getTypeList() != null && !pageLink.getTypeList().isEmpty()) {
  202 + addAndIfNeeded(wherePart, addAnd);
  203 + addAnd = true;
  204 + ctx.addStringListParameter("alarmTypes", pageLink.getTypeList());
  205 + wherePart.append("a.type in (:alarmTypes)");
  206 + }
209 207
210   - if (pageLink.getStatusList() != null && !pageLink.getStatusList().isEmpty()) {
211   - Set<AlarmStatus> statusSet = toStatusSet(pageLink.getStatusList());
212   - if (!statusSet.isEmpty()) {
  208 + if (pageLink.getSeverityList() != null && !pageLink.getSeverityList().isEmpty()) {
213 209 addAndIfNeeded(wherePart, addAnd);
214 210 addAnd = true;
215   - ctx.addStringListParameter("alarmStatuses", statusSet.stream().map(AlarmStatus::name).collect(Collectors.toList()));
216   - wherePart.append(" a.status in (:alarmStatuses)");
  211 + ctx.addStringListParameter("alarmSeverities", pageLink.getSeverityList().stream().map(AlarmSeverity::name).collect(Collectors.toList()));
  212 + wherePart.append("a.severity in (:alarmSeverities)");
217 213 }
218   - }
219 214
220   - String textSearchQuery = buildTextSearchQuery(ctx, query.getAlarmFields(), pageLink.getTextSearch());
221   - String mainQuery = selectPart.toString() + fromPart.toString() + wherePart.toString();
222   - if (!textSearchQuery.isEmpty()) {
223   - mainQuery = String.format("select * from (%s) a WHERE %s", mainQuery, textSearchQuery);
224   - }
225   - String countQuery = mainQuery;
226   - int totalElements = jdbcTemplate.queryForObject(String.format("select count(*) from (%s) result", countQuery), ctx, Integer.class);
  215 + if (pageLink.getStatusList() != null && !pageLink.getStatusList().isEmpty()) {
  216 + Set<AlarmStatus> statusSet = toStatusSet(pageLink.getStatusList());
  217 + if (!statusSet.isEmpty()) {
  218 + addAndIfNeeded(wherePart, addAnd);
  219 + addAnd = true;
  220 + ctx.addStringListParameter("alarmStatuses", statusSet.stream().map(AlarmStatus::name).collect(Collectors.toList()));
  221 + wherePart.append(" a.status in (:alarmStatuses)");
  222 + }
  223 + }
227 224
228   - String dataQuery = mainQuery + sortPart;
  225 + String textSearchQuery = buildTextSearchQuery(ctx, query.getAlarmFields(), pageLink.getTextSearch());
  226 + String mainQuery = selectPart.toString() + fromPart.toString() + wherePart.toString();
  227 + if (!textSearchQuery.isEmpty()) {
  228 + mainQuery = String.format("select * from (%s) a WHERE %s", mainQuery, textSearchQuery);
  229 + }
  230 + String countQuery = mainQuery;
  231 + int totalElements = jdbcTemplate.queryForObject(String.format("select count(*) from (%s) result", countQuery), ctx, Integer.class);
229 232
230   - int startIndex = pageLink.getPageSize() * pageLink.getPage();
231   - if (pageLink.getPageSize() > 0) {
232   - dataQuery = String.format("%s limit %s offset %s", dataQuery, pageLink.getPageSize(), startIndex);
233   - }
234   - List<Map<String, Object>> rows = jdbcTemplate.queryForList(dataQuery, ctx);
235   - return AlarmDataAdapter.createAlarmData(pageLink, rows, totalElements, orderedEntityIds);
  233 + String dataQuery = mainQuery + sortPart;
  234 +
  235 + int startIndex = pageLink.getPageSize() * pageLink.getPage();
  236 + if (pageLink.getPageSize() > 0) {
  237 + dataQuery = String.format("%s limit %s offset %s", dataQuery, pageLink.getPageSize(), startIndex);
  238 + }
  239 + List<Map<String, Object>> rows = jdbcTemplate.queryForList(dataQuery, ctx);
  240 + return AlarmDataAdapter.createAlarmData(pageLink, rows, totalElements, orderedEntityIds);
  241 + });
236 242 }
237 243
238 244 private String buildTextSearchQuery(QueryContext ctx, List<EntityKey> selectionMapping, String searchText) {
... ...
... ... @@ -20,6 +20,9 @@ import org.apache.commons.lang3.StringUtils;
20 20 import org.springframework.beans.factory.annotation.Autowired;
21 21 import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
22 22 import org.springframework.stereotype.Repository;
  23 +import org.springframework.transaction.TransactionStatus;
  24 +import org.springframework.transaction.support.TransactionCallback;
  25 +import org.springframework.transaction.support.TransactionTemplate;
23 26 import org.thingsboard.server.common.data.EntityType;
24 27 import org.thingsboard.server.common.data.id.CustomerId;
25 28 import org.thingsboard.server.common.data.id.EntityId;
... ... @@ -58,37 +61,36 @@ import java.util.stream.Collectors;
58 61 @Repository
59 62 @Slf4j
60 63 public class DefaultEntityQueryRepository implements EntityQueryRepository {
61   - //TODO: rafactoring to protect from SQL injections;
62 64 private static final Map<EntityType, String> entityTableMap = new HashMap<>();
63   - public static final String SELECT_PHONE = " CASE WHEN entity.entity_type = 'TENANT' THEN (select phone from tenant where id = entity_id)" +
  65 + private static final String SELECT_PHONE = " CASE WHEN entity.entity_type = 'TENANT' THEN (select phone from tenant where id = entity_id)" +
64 66 " WHEN entity.entity_type = 'CUSTOMER' THEN (select phone from customer where id = entity_id) END as phone";
65   - public static final String SELECT_ZIP = " CASE WHEN entity.entity_type = 'TENANT' THEN (select zip from tenant where id = entity_id)" +
  67 + private static final String SELECT_ZIP = " CASE WHEN entity.entity_type = 'TENANT' THEN (select zip from tenant where id = entity_id)" +
66 68 " WHEN entity.entity_type = 'CUSTOMER' THEN (select zip from customer where id = entity_id) END as zip";
67   - public static final String SELECT_ADDRESS_2 = " CASE WHEN entity.entity_type = 'TENANT'" +
  69 + private static final String SELECT_ADDRESS_2 = " CASE WHEN entity.entity_type = 'TENANT'" +
68 70 " THEN (select address2 from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " +
69 71 " THEN (select address2 from customer where id = entity_id) END as address2";
70   - public static final String SELECT_ADDRESS = " CASE WHEN entity.entity_type = 'TENANT'" +
  72 + private static final String SELECT_ADDRESS = " CASE WHEN entity.entity_type = 'TENANT'" +
71 73 " THEN (select address from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " +
72 74 " THEN (select address from customer where id = entity_id) END as address";
73   - public static final String SELECT_CITY = " CASE WHEN entity.entity_type = 'TENANT'" +
  75 + private static final String SELECT_CITY = " CASE WHEN entity.entity_type = 'TENANT'" +
74 76 " THEN (select city from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " +
75 77 " THEN (select city from customer where id = entity_id) END as city";
76   - public static final String SELECT_STATE = " CASE WHEN entity.entity_type = 'TENANT'" +
  78 + private static final String SELECT_STATE = " CASE WHEN entity.entity_type = 'TENANT'" +
77 79 " THEN (select state from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " +
78 80 " THEN (select state from customer where id = entity_id) END as state";
79   - public static final String SELECT_COUNTRY = " CASE WHEN entity.entity_type = 'TENANT'" +
  81 + private static final String SELECT_COUNTRY = " CASE WHEN entity.entity_type = 'TENANT'" +
80 82 " THEN (select country from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " +
81 83 " THEN (select country from customer where id = entity_id) END as country";
82   - public static final String SELECT_TITLE = " CASE WHEN entity.entity_type = 'TENANT'" +
  84 + private static final String SELECT_TITLE = " CASE WHEN entity.entity_type = 'TENANT'" +
83 85 " THEN (select title from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " +
84 86 " THEN (select title from customer where id = entity_id) END as title";
85   - public static final String SELECT_LAST_NAME = " CASE WHEN entity.entity_type = 'USER'" +
  87 + private static final String SELECT_LAST_NAME = " CASE WHEN entity.entity_type = 'USER'" +
86 88 " THEN (select last_name from tb_user where id = entity_id) END as last_name";
87   - public static final String SELECT_FIRST_NAME = " CASE WHEN entity.entity_type = 'USER'" +
  89 + private static final String SELECT_FIRST_NAME = " CASE WHEN entity.entity_type = 'USER'" +
88 90 " THEN (select first_name from tb_user where id = entity_id) END as first_name";
89   - public static final String SELECT_REGION = " CASE WHEN entity.entity_type = 'TENANT'" +
  91 + private static final String SELECT_REGION = " CASE WHEN entity.entity_type = 'TENANT'" +
90 92 " THEN (select region from tenant where id = entity_id) END as region";
91   - public static final String SELECT_EMAIL = " CASE" +
  93 + private static final String SELECT_EMAIL = " CASE" +
92 94 " WHEN entity.entity_type = 'TENANT'" +
93 95 " THEN (select email from tenant where id = entity_id)" +
94 96 " WHEN entity.entity_type = 'CUSTOMER' " +
... ... @@ -96,7 +98,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
96 98 " WHEN entity.entity_type = 'USER'" +
97 99 " THEN (select email from tb_user where id = entity_id)" +
98 100 " END as email";
99   - public static final String SELECT_CUSTOMER_ID = "CASE" +
  101 + private static final String SELECT_CUSTOMER_ID = "CASE" +
100 102 " WHEN entity.entity_type = 'TENANT'" +
101 103 " THEN UUID('" + TenantId.NULL_UUID + "')" +
102 104 " WHEN entity.entity_type = 'CUSTOMER' THEN entity_id" +
... ... @@ -112,7 +114,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
112 114 " WHEN entity.entity_type = 'ENTITY_VIEW'" +
113 115 " THEN (select customer_id from entity_view where id = entity_id)" +
114 116 " END as customer_id";
115   - public static final String SELECT_TENANT_ID = "SELECT CASE" +
  117 + private static final String SELECT_TENANT_ID = "SELECT CASE" +
116 118 " WHEN entity.entity_type = 'TENANT' THEN entity_id" +
117 119 " WHEN entity.entity_type = 'CUSTOMER'" +
118 120 " THEN (select tenant_id from customer where id = entity_id)" +
... ... @@ -127,7 +129,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
127 129 " WHEN entity.entity_type = 'ENTITY_VIEW'" +
128 130 " THEN (select tenant_id from entity_view where id = entity_id)" +
129 131 " END as tenant_id";
130   - public static final String SELECT_CREATED_TIME = " CASE" +
  132 + private static final String SELECT_CREATED_TIME = " CASE" +
131 133 " WHEN entity.entity_type = 'TENANT'" +
132 134 " THEN (select created_time from tenant where id = entity_id)" +
133 135 " WHEN entity.entity_type = 'CUSTOMER' " +
... ... @@ -143,7 +145,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
143 145 " WHEN entity.entity_type = 'ENTITY_VIEW'" +
144 146 " THEN (select created_time from entity_view where id = entity_id)" +
145 147 " END as created_time";
146   - public static final String SELECT_NAME = " CASE" +
  148 + private static final String SELECT_NAME = " CASE" +
147 149 " WHEN entity.entity_type = 'TENANT'" +
148 150 " THEN (select title from tenant where id = entity_id)" +
149 151 " WHEN entity.entity_type = 'CUSTOMER' " +
... ... @@ -159,7 +161,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
159 161 " WHEN entity.entity_type = 'ENTITY_VIEW'" +
160 162 " THEN (select name from entity_view where id = entity_id)" +
161 163 " END as name";
162   - public static final String SELECT_TYPE = " CASE" +
  164 + private static final String SELECT_TYPE = " CASE" +
163 165 " WHEN entity.entity_type = 'USER'" +
164 166 " THEN (select authority from tb_user where id = entity_id)" +
165 167 " WHEN entity.entity_type = 'ASSET'" +
... ... @@ -169,7 +171,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
169 171 " WHEN entity.entity_type = 'ENTITY_VIEW'" +
170 172 " THEN (select type from entity_view where id = entity_id)" +
171 173 " ELSE entity.entity_type END as type";
172   - public static final String SELECT_LABEL = " CASE" +
  174 + private static final String SELECT_LABEL = " CASE" +
173 175 " WHEN entity.entity_type = 'TENANT'" +
174 176 " THEN (select title from tenant where id = entity_id)" +
175 177 " WHEN entity.entity_type = 'CUSTOMER' " +
... ... @@ -196,7 +198,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
196 198 entityTableMap.put(EntityType.TENANT, "tenant");
197 199 }
198 200
199   - public static final String HIERARCHICAL_QUERY_TEMPLATE = " FROM (WITH RECURSIVE related_entities(from_id, from_type, to_id, to_type, relation_type, lvl) AS (" +
  201 + private static final String HIERARCHICAL_QUERY_TEMPLATE = " FROM (WITH RECURSIVE related_entities(from_id, from_type, to_id, to_type, relation_type, lvl) AS (" +
200 202 " SELECT from_id, from_type, to_id, to_type, relation_type, 1 as lvl" +
201 203 " FROM relation" +
202 204 " WHERE $in_id = :relation_root_id and $in_type = :relation_root_type and relation_type_group = 'COMMON'" +
... ... @@ -209,11 +211,16 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
209 211 " SELECT re.$out_id entity_id, re.$out_type entity_type, re.lvl lvl" +
210 212 " from related_entities re" +
211 213 " %s ) entity";
212   - public static final String HIERARCHICAL_TO_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "to").replace("$out", "from");
213   - public static final String HIERARCHICAL_FROM_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "from").replace("$out", "to");
  214 + private static final String HIERARCHICAL_TO_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "to").replace("$out", "from");
  215 + private static final String HIERARCHICAL_FROM_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "from").replace("$out", "to");
214 216
215   - @Autowired
216   - protected NamedParameterJdbcTemplate jdbcTemplate;
  217 + private final NamedParameterJdbcTemplate jdbcTemplate;
  218 + private final TransactionTemplate transactionTemplate;
  219 +
  220 + public DefaultEntityQueryRepository(NamedParameterJdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate) {
  221 + this.jdbcTemplate = jdbcTemplate;
  222 + this.transactionTemplate = transactionTemplate;
  223 + }
217 224
218 225 @Override
219 226 public long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query) {
... ... @@ -223,89 +230,91 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
223 230 ctx.append(addEntityTableQuery(ctx, query.getEntityFilter(), entityType));
224 231 ctx.append(" e where ");
225 232 ctx.append(buildEntityWhere(ctx, tenantId, customerId, query.getEntityFilter(), Collections.emptyList(), entityType));
226   - return jdbcTemplate.queryForObject(ctx.getQuery(), ctx, Long.class);
  233 + return transactionTemplate.execute(status -> jdbcTemplate.queryForObject(ctx.getQuery(), ctx, Long.class));
227 234 }
228 235
229 236 @Override
230 237 public PageData<EntityData> findEntityDataByQuery(TenantId tenantId, CustomerId customerId, EntityDataQuery query) {
231   - QueryContext ctx = new QueryContext();
232   - EntityType entityType = resolveEntityType(query.getEntityFilter());
233   - EntityDataPageLink pageLink = query.getPageLink();
234   -
235   - List<EntityKeyMapping> mappings = EntityKeyMapping.prepareKeyMapping(query);
236   -
237   - List<EntityKeyMapping> selectionMapping = mappings.stream().filter(EntityKeyMapping::isSelection)
238   - .collect(Collectors.toList());
239   - List<EntityKeyMapping> entityFieldsSelectionMapping = selectionMapping.stream().filter(mapping -> !mapping.isLatest())
240   - .collect(Collectors.toList());
241   - List<EntityKeyMapping> latestSelectionMapping = selectionMapping.stream().filter(EntityKeyMapping::isLatest)
242   - .collect(Collectors.toList());
243   -
244   - List<EntityKeyMapping> filterMapping = mappings.stream().filter(EntityKeyMapping::hasFilter)
245   - .collect(Collectors.toList());
246   - List<EntityKeyMapping> entityFieldsFiltersMapping = filterMapping.stream().filter(mapping -> !mapping.isLatest())
247   - .collect(Collectors.toList());
248   - List<EntityKeyMapping> latestFiltersMapping = filterMapping.stream().filter(EntityKeyMapping::isLatest)
249   - .collect(Collectors.toList());
250   -
251   - List<EntityKeyMapping> allLatestMappings = mappings.stream().filter(EntityKeyMapping::isLatest)
252   - .collect(Collectors.toList());
253   -
254   -
255   - String entityWhereClause = this.buildEntityWhere(ctx, tenantId, customerId, query.getEntityFilter(), entityFieldsFiltersMapping, entityType);
256   - String latestJoins = EntityKeyMapping.buildLatestJoins(ctx, query.getEntityFilter(), entityType, allLatestMappings);
257   - String whereClause = this.buildWhere(ctx, latestFiltersMapping, query.getEntityFilter().getType(), entityType);
258   - String textSearchQuery = this.buildTextSearchQuery(ctx, selectionMapping, pageLink.getTextSearch());
259   - String entityFieldsSelection = EntityKeyMapping.buildSelections(entityFieldsSelectionMapping, query.getEntityFilter().getType(), entityType);
260   - String entityTypeStr;
261   - if (query.getEntityFilter().getType().equals(EntityFilterType.RELATIONS_QUERY)) {
262   - entityTypeStr = "e.entity_type";
263   - } else {
264   - entityTypeStr = "'" + entityType.name() + "'";
265   - }
266   - if (!StringUtils.isEmpty(entityFieldsSelection)) {
267   - entityFieldsSelection = String.format("e.id id, %s entity_type, %s", entityTypeStr, entityFieldsSelection);
268   - } else {
269   - entityFieldsSelection = String.format("e.id id, %s entity_type", entityTypeStr);
270   - }
271   - String latestSelection = EntityKeyMapping.buildSelections(latestSelectionMapping, query.getEntityFilter().getType(), entityType);
272   - String topSelection = "entities.*";
273   - if (!StringUtils.isEmpty(latestSelection)) {
274   - topSelection = topSelection + ", " + latestSelection;
275   - }
  238 + return transactionTemplate.execute(status -> {
  239 + QueryContext ctx = new QueryContext();
  240 + EntityType entityType = resolveEntityType(query.getEntityFilter());
  241 + EntityDataPageLink pageLink = query.getPageLink();
  242 +
  243 + List<EntityKeyMapping> mappings = EntityKeyMapping.prepareKeyMapping(query);
  244 +
  245 + List<EntityKeyMapping> selectionMapping = mappings.stream().filter(EntityKeyMapping::isSelection)
  246 + .collect(Collectors.toList());
  247 + List<EntityKeyMapping> entityFieldsSelectionMapping = selectionMapping.stream().filter(mapping -> !mapping.isLatest())
  248 + .collect(Collectors.toList());
  249 + List<EntityKeyMapping> latestSelectionMapping = selectionMapping.stream().filter(EntityKeyMapping::isLatest)
  250 + .collect(Collectors.toList());
  251 +
  252 + List<EntityKeyMapping> filterMapping = mappings.stream().filter(EntityKeyMapping::hasFilter)
  253 + .collect(Collectors.toList());
  254 + List<EntityKeyMapping> entityFieldsFiltersMapping = filterMapping.stream().filter(mapping -> !mapping.isLatest())
  255 + .collect(Collectors.toList());
  256 + List<EntityKeyMapping> latestFiltersMapping = filterMapping.stream().filter(EntityKeyMapping::isLatest)
  257 + .collect(Collectors.toList());
  258 +
  259 + List<EntityKeyMapping> allLatestMappings = mappings.stream().filter(EntityKeyMapping::isLatest)
  260 + .collect(Collectors.toList());
  261 +
  262 +
  263 + String entityWhereClause = DefaultEntityQueryRepository.this.buildEntityWhere(ctx, tenantId, customerId, query.getEntityFilter(), entityFieldsFiltersMapping, entityType);
  264 + String latestJoins = EntityKeyMapping.buildLatestJoins(ctx, query.getEntityFilter(), entityType, allLatestMappings);
  265 + String whereClause = DefaultEntityQueryRepository.this.buildWhere(ctx, latestFiltersMapping, query.getEntityFilter().getType(), entityType);
  266 + String textSearchQuery = DefaultEntityQueryRepository.this.buildTextSearchQuery(ctx, selectionMapping, pageLink.getTextSearch());
  267 + String entityFieldsSelection = EntityKeyMapping.buildSelections(entityFieldsSelectionMapping, query.getEntityFilter().getType(), entityType);
  268 + String entityTypeStr;
  269 + if (query.getEntityFilter().getType().equals(EntityFilterType.RELATIONS_QUERY)) {
  270 + entityTypeStr = "e.entity_type";
  271 + } else {
  272 + entityTypeStr = "'" + entityType.name() + "'";
  273 + }
  274 + if (!StringUtils.isEmpty(entityFieldsSelection)) {
  275 + entityFieldsSelection = String.format("e.id id, %s entity_type, %s", entityTypeStr, entityFieldsSelection);
  276 + } else {
  277 + entityFieldsSelection = String.format("e.id id, %s entity_type", entityTypeStr);
  278 + }
  279 + String latestSelection = EntityKeyMapping.buildSelections(latestSelectionMapping, query.getEntityFilter().getType(), entityType);
  280 + String topSelection = "entities.*";
  281 + if (!StringUtils.isEmpty(latestSelection)) {
  282 + topSelection = topSelection + ", " + latestSelection;
  283 + }
276 284
277   - String fromClause = String.format("from (select %s from (select %s from %s e where %s) entities %s %s) result %s",
278   - topSelection,
279   - entityFieldsSelection,
280   - addEntityTableQuery(ctx, query.getEntityFilter(), entityType),
281   - entityWhereClause,
282   - latestJoins,
283   - whereClause,
284   - textSearchQuery);
285   -
286   - int totalElements = jdbcTemplate.queryForObject(String.format("select count(*) %s", fromClause), ctx, Integer.class);
287   -
288   - String dataQuery = String.format("select * %s", fromClause);
289   -
290   - EntityDataSortOrder sortOrder = pageLink.getSortOrder();
291   - if (sortOrder != null) {
292   - Optional<EntityKeyMapping> sortOrderMappingOpt = mappings.stream().filter(EntityKeyMapping::isSortOrder).findFirst();
293   - if (sortOrderMappingOpt.isPresent()) {
294   - EntityKeyMapping sortOrderMapping = sortOrderMappingOpt.get();
295   - dataQuery = String.format("%s order by %s", dataQuery, sortOrderMapping.getValueAlias());
296   - if (sortOrder.getDirection() == EntityDataSortOrder.Direction.ASC) {
297   - dataQuery += " asc";
298   - } else {
299   - dataQuery += " desc";
  285 + String fromClause = String.format("from (select %s from (select %s from %s e where %s) entities %s %s) result %s",
  286 + topSelection,
  287 + entityFieldsSelection,
  288 + addEntityTableQuery(ctx, query.getEntityFilter(), entityType),
  289 + entityWhereClause,
  290 + latestJoins,
  291 + whereClause,
  292 + textSearchQuery);
  293 +
  294 + int totalElements = jdbcTemplate.queryForObject(String.format("select count(*) %s", fromClause), ctx, Integer.class);
  295 +
  296 + String dataQuery = String.format("select * %s", fromClause);
  297 +
  298 + EntityDataSortOrder sortOrder = pageLink.getSortOrder();
  299 + if (sortOrder != null) {
  300 + Optional<EntityKeyMapping> sortOrderMappingOpt = mappings.stream().filter(EntityKeyMapping::isSortOrder).findFirst();
  301 + if (sortOrderMappingOpt.isPresent()) {
  302 + EntityKeyMapping sortOrderMapping = sortOrderMappingOpt.get();
  303 + dataQuery = String.format("%s order by %s", dataQuery, sortOrderMapping.getValueAlias());
  304 + if (sortOrder.getDirection() == EntityDataSortOrder.Direction.ASC) {
  305 + dataQuery += " asc";
  306 + } else {
  307 + dataQuery += " desc";
  308 + }
300 309 }
301 310 }
302   - }
303   - int startIndex = pageLink.getPageSize() * pageLink.getPage();
304   - if (pageLink.getPageSize() > 0) {
305   - dataQuery = String.format("%s limit %s offset %s", dataQuery, pageLink.getPageSize(), startIndex);
306   - }
307   - List<Map<String, Object>> rows = jdbcTemplate.queryForList(dataQuery, ctx);
308   - return EntityDataAdapter.createEntityData(pageLink, selectionMapping, rows, totalElements);
  311 + int startIndex = pageLink.getPageSize() * pageLink.getPage();
  312 + if (pageLink.getPageSize() > 0) {
  313 + dataQuery = String.format("%s limit %s offset %s", dataQuery, pageLink.getPageSize(), startIndex);
  314 + }
  315 + List<Map<String, Object>> rows = jdbcTemplate.queryForList(dataQuery, ctx);
  316 + return EntityDataAdapter.createEntityData(pageLink, selectionMapping, rows, totalElements);
  317 + });
309 318 }
310 319
311 320 private String buildEntityWhere(QueryContext ctx,
... ...
... ... @@ -16,7 +16,9 @@
16 16
17 17 CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type ON alarm(originator_id, type, start_ts DESC);
18 18
19   -CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_time ON alarm(originator_id, created_time DESC);
  19 +CREATE INDEX IF NOT EXISTS idx_alarm_originator_created_time ON alarm(originator_id, created_time DESC);
  20 +
  21 +CREATE INDEX IF NOT EXISTS idx_alarm_tenant_created_time ON alarm(tenant_id, created_time DESC);
20 22
21 23 CREATE INDEX IF NOT EXISTS idx_event_type_entity_id ON event(tenant_id, event_type, entity_type, entity_id);
22 24
... ...
... ... @@ -119,7 +119,7 @@
119 119 </mat-cell>
120 120 </ng-container>
121 121 <mat-header-row [ngClass]="{'mat-row-select': enableSelection}" *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
122   - <mat-row [ngClass]="{'mat-row-select': enableSelection,
  122 + <mat-row [fxShow]="!alarmsDatasource.dataLoading" [ngClass]="{'mat-row-select': enableSelection,
123 123 'mat-selected': alarmsDatasource.isSelected(alarm),
124 124 'tb-current-entity': alarmsDatasource.isCurrentAlarm(alarm)}"
125 125 *matRowDef="let alarm; columns: displayedColumns;"
... ...
... ... @@ -368,7 +368,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
368 368 dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label);
369 369 dataKey.def = 'def' + this.columns.length;
370 370 const keySettings: TableWidgetDataKeySettings = dataKey.settings;
371   -
  371 + if (dataKey.type === DataKeyType.alarm && !isDefined(keySettings.columnWidth)) {
  372 + const alarmField = alarmFields[dataKey.name];
  373 + if (alarmField && alarmField.time) {
  374 + keySettings.columnWidth = '120px';
  375 + }
  376 + }
372 377 this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings);
373 378 this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, alarm, ctx');
374 379 this.columnWidth[dataKey.def] = getColumnWidth(keySettings);
... ... @@ -863,7 +868,7 @@ class AlarmsDatasource implements DataSource<AlarmDataInfo> {
863 868
864 869 loadAlarms(pageLink: AlarmDataPageLink, sortOrderLabel: string, keyFilters: KeyFilter[]) {
865 870 this.dataLoading = true;
866   - this.clear();
  871 + // this.clear();
867 872 this.appliedPageLink = pageLink;
868 873 this.appliedSortOrderLabel = sortOrderLabel;
869 874 this.subscription.subscribeForAlarms(pageLink, keyFilters);
... ...
... ... @@ -82,7 +82,7 @@
82 82 </mat-cell>
83 83 </ng-container>
84 84 <mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
85   - <mat-row [ngClass]="{'tb-current-entity': entityDatasource.isCurrentEntity(entity)}"
  85 + <mat-row [fxShow]="!entityDatasource.dataLoading" [ngClass]="{'tb-current-entity': entityDatasource.isCurrentEntity(entity)}"
86 86 *matRowDef="let entity; columns: displayedColumns;"
87 87 (click)="onRowClick($event, entity)" (dblclick)="onRowClick($event, entity, true)"></mat-row>
88 88 </table>
... ...
... ... @@ -87,6 +87,7 @@ import {
87 87 import { sortItems } from '@shared/models/page/page-link';
88 88 import { entityFields } from '@shared/models/entity.models';
89 89 import { DatePipe } from '@angular/common';
  90 +import { alarmFields } from '@shared/models/alarm.models';
90 91
91 92 interface EntitiesTableWidgetSettings extends TableWidgetSettings {
92 93 entitiesTitle: string;
... ... @@ -348,6 +349,13 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
348 349 dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label);
349 350 dataKey.def = 'def' + this.columns.length;
350 351 const keySettings: TableWidgetDataKeySettings = dataKey.settings;
  352 + if (dataKey.type === DataKeyType.entityField &&
  353 + !isDefined(keySettings.columnWidth) || keySettings.columnWidth === '0px') {
  354 + const entityField = entityFields[dataKey.name];
  355 + if (entityField && entityField.time) {
  356 + keySettings.columnWidth = '120px';
  357 + }
  358 + }
351 359
352 360 this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings);
353 361 this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, entity, ctx');
... ... @@ -595,7 +603,7 @@ class EntityDatasource implements DataSource<EntityData> {
595 603
596 604 loadEntities(pageLink: EntityDataPageLink, sortOrderLabel: string, keyFilters: KeyFilter[]) {
597 605 this.dataLoading = true;
598   - this.clear();
  606 + // this.clear();
599 607 this.appliedPageLink = pageLink;
600 608 this.appliedSortOrderLabel = sortOrderLabel;
601 609 this.subscription.subscribeForPaginatedData(0, pageLink, keyFilters);
... ...