Commit 2f4df2803663f01df3bd5974cd7a16d2ce4710e1
1 parent
99dde0cf
Configurable max datapoints limit for Aggregation NONE
Showing
7 changed files
with
80 additions
and
22 deletions
@@ -15,6 +15,8 @@ | @@ -15,6 +15,8 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.controller; | 16 | package org.thingsboard.server.controller; |
17 | 17 | ||
18 | +import lombok.Getter; | ||
19 | +import org.springframework.beans.factory.annotation.Value; | ||
18 | import org.springframework.http.HttpStatus; | 20 | import org.springframework.http.HttpStatus; |
19 | import org.springframework.security.access.prepost.PreAuthorize; | 21 | import org.springframework.security.access.prepost.PreAuthorize; |
20 | import org.springframework.web.bind.annotation.PathVariable; | 22 | import org.springframework.web.bind.annotation.PathVariable; |
@@ -49,6 +51,11 @@ public class DashboardController extends BaseController { | @@ -49,6 +51,11 @@ public class DashboardController extends BaseController { | ||
49 | 51 | ||
50 | public static final String DASHBOARD_ID = "dashboardId"; | 52 | public static final String DASHBOARD_ID = "dashboardId"; |
51 | 53 | ||
54 | + @Value("${dashboard.max_datapoints_limit}") | ||
55 | + @Getter | ||
56 | + private long maxDatapointsLimit; | ||
57 | + | ||
58 | + | ||
52 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | 59 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") |
53 | @RequestMapping(value = "/dashboard/serverTime", method = RequestMethod.GET) | 60 | @RequestMapping(value = "/dashboard/serverTime", method = RequestMethod.GET) |
54 | @ResponseBody | 61 | @ResponseBody |
@@ -57,6 +64,13 @@ public class DashboardController extends BaseController { | @@ -57,6 +64,13 @@ public class DashboardController extends BaseController { | ||
57 | } | 64 | } |
58 | 65 | ||
59 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | 66 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") |
67 | + @RequestMapping(value = "/dashboard/maxDatapointsLimit", method = RequestMethod.GET) | ||
68 | + @ResponseBody | ||
69 | + public long getMaxDatapointsLimit() throws ThingsboardException { | ||
70 | + return maxDatapointsLimit; | ||
71 | + } | ||
72 | + | ||
73 | + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | ||
60 | @RequestMapping(value = "/dashboard/info/{dashboardId}", method = RequestMethod.GET) | 74 | @RequestMapping(value = "/dashboard/info/{dashboardId}", method = RequestMethod.GET) |
61 | @ResponseBody | 75 | @ResponseBody |
62 | public DashboardInfo getDashboardInfoById(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { | 76 | public DashboardInfo getDashboardInfoById(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { |
@@ -77,6 +77,11 @@ security: | @@ -77,6 +77,11 @@ security: | ||
77 | # Enable/disable access to Tenant Administrators JWT token by System Administrator or Customer Users JWT token by Tenant Administrator | 77 | # Enable/disable access to Tenant Administrators JWT token by System Administrator or Customer Users JWT token by Tenant Administrator |
78 | user_token_access_enabled: "${SECURITY_USER_TOKEN_ACCESS_ENABLED:true}" | 78 | user_token_access_enabled: "${SECURITY_USER_TOKEN_ACCESS_ENABLED:true}" |
79 | 79 | ||
80 | +# Dashboard parameters | ||
81 | +dashboard: | ||
82 | + # Maximum allowed datapoints fetched by widgets | ||
83 | + max_datapoints_limit: "${DASHBOARD_MAX_DATAPOINTS_LIMIT:50000}" | ||
84 | + | ||
80 | # Device communication protocol parameters | 85 | # Device communication protocol parameters |
81 | http: | 86 | http: |
82 | request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}" | 87 | request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}" |
@@ -26,15 +26,17 @@ const MIN_INTERVAL = SECOND; | @@ -26,15 +26,17 @@ const MIN_INTERVAL = SECOND; | ||
26 | const MAX_INTERVAL = 365 * 20 * DAY; | 26 | const MAX_INTERVAL = 365 * 20 * DAY; |
27 | 27 | ||
28 | const MIN_LIMIT = 10; | 28 | const MIN_LIMIT = 10; |
29 | -const AVG_LIMIT = 200; | ||
30 | -const MAX_LIMIT = 500; | 29 | +//const AVG_LIMIT = 200; |
30 | +//const MAX_LIMIT = 500; | ||
31 | 31 | ||
32 | /*@ngInject*/ | 32 | /*@ngInject*/ |
33 | -function TimeService($translate, types) { | 33 | +function TimeService($translate, $http, $q, types) { |
34 | 34 | ||
35 | var predefIntervals; | 35 | var predefIntervals; |
36 | + var maxDatapointsLimit; | ||
36 | 37 | ||
37 | var service = { | 38 | var service = { |
39 | + loadMaxDatapointsLimit: loadMaxDatapointsLimit, | ||
38 | minIntervalLimit: minIntervalLimit, | 40 | minIntervalLimit: minIntervalLimit, |
39 | maxIntervalLimit: maxIntervalLimit, | 41 | maxIntervalLimit: maxIntervalLimit, |
40 | boundMinInterval: boundMinInterval, | 42 | boundMinInterval: boundMinInterval, |
@@ -45,20 +47,38 @@ function TimeService($translate, types) { | @@ -45,20 +47,38 @@ function TimeService($translate, types) { | ||
45 | defaultTimewindow: defaultTimewindow, | 47 | defaultTimewindow: defaultTimewindow, |
46 | toHistoryTimewindow: toHistoryTimewindow, | 48 | toHistoryTimewindow: toHistoryTimewindow, |
47 | createSubscriptionTimewindow: createSubscriptionTimewindow, | 49 | createSubscriptionTimewindow: createSubscriptionTimewindow, |
48 | - avgAggregationLimit: function () { | ||
49 | - return AVG_LIMIT; | 50 | + getMaxDatapointsLimit: function () { |
51 | + return maxDatapointsLimit; | ||
52 | + }, | ||
53 | + getMinDatapointsLimit: function () { | ||
54 | + return MIN_LIMIT; | ||
50 | } | 55 | } |
51 | } | 56 | } |
52 | 57 | ||
53 | return service; | 58 | return service; |
54 | 59 | ||
60 | + function loadMaxDatapointsLimit() { | ||
61 | + var deferred = $q.defer(); | ||
62 | + var url = '/api/dashboard/maxDatapointsLimit'; | ||
63 | + $http.get(url, {ignoreLoading: true}).then(function success(response) { | ||
64 | + maxDatapointsLimit = response.data; | ||
65 | + if (!maxDatapointsLimit || maxDatapointsLimit <= MIN_LIMIT) { | ||
66 | + maxDatapointsLimit = MIN_LIMIT + 1; | ||
67 | + } | ||
68 | + deferred.resolve(); | ||
69 | + }, function fail() { | ||
70 | + deferred.reject(); | ||
71 | + }); | ||
72 | + return deferred.promise; | ||
73 | + } | ||
74 | + | ||
55 | function minIntervalLimit(timewindow) { | 75 | function minIntervalLimit(timewindow) { |
56 | - var min = timewindow / MAX_LIMIT; | 76 | + var min = timewindow / 500; |
57 | return boundMinInterval(min); | 77 | return boundMinInterval(min); |
58 | } | 78 | } |
59 | 79 | ||
60 | function avgInterval(timewindow) { | 80 | function avgInterval(timewindow) { |
61 | - var avg = timewindow / AVG_LIMIT; | 81 | + var avg = timewindow / 200; |
62 | return boundMinInterval(avg); | 82 | return boundMinInterval(avg); |
63 | } | 83 | } |
64 | 84 | ||
@@ -230,7 +250,7 @@ function TimeService($translate, types) { | @@ -230,7 +250,7 @@ function TimeService($translate, types) { | ||
230 | }, | 250 | }, |
231 | aggregation: { | 251 | aggregation: { |
232 | type: types.aggregation.avg.value, | 252 | type: types.aggregation.avg.value, |
233 | - limit: AVG_LIMIT | 253 | + limit: Math.floor(maxDatapointsLimit / 2) |
234 | } | 254 | } |
235 | } | 255 | } |
236 | return timewindow; | 256 | return timewindow; |
@@ -246,22 +266,27 @@ function TimeService($translate, types) { | @@ -246,22 +266,27 @@ function TimeService($translate, types) { | ||
246 | } | 266 | } |
247 | 267 | ||
248 | var aggType; | 268 | var aggType; |
269 | + var limit; | ||
249 | if (timewindow.aggregation) { | 270 | if (timewindow.aggregation) { |
250 | aggType = timewindow.aggregation.type || types.aggregation.avg.value; | 271 | aggType = timewindow.aggregation.type || types.aggregation.avg.value; |
272 | + limit = timewindow.aggregation.limit || maxDatapointsLimit; | ||
251 | } else { | 273 | } else { |
252 | aggType = types.aggregation.avg.value; | 274 | aggType = types.aggregation.avg.value; |
275 | + limit = maxDatapointsLimit; | ||
253 | } | 276 | } |
254 | 277 | ||
278 | + | ||
255 | var historyTimewindow = { | 279 | var historyTimewindow = { |
256 | history: { | 280 | history: { |
257 | fixedTimewindow: { | 281 | fixedTimewindow: { |
258 | startTimeMs: startTimeMs, | 282 | startTimeMs: startTimeMs, |
259 | endTimeMs: endTimeMs | 283 | endTimeMs: endTimeMs |
260 | }, | 284 | }, |
261 | - interval: boundIntervalToTimewindow(endTimeMs - startTimeMs, interval, aggType) | 285 | + interval: boundIntervalToTimewindow(endTimeMs - startTimeMs, interval, types.aggregation.avg.value) |
262 | }, | 286 | }, |
263 | aggregation: { | 287 | aggregation: { |
264 | - type: aggType | 288 | + type: aggType, |
289 | + limit: limit | ||
265 | } | 290 | } |
266 | } | 291 | } |
267 | 292 | ||
@@ -275,7 +300,7 @@ function TimeService($translate, types) { | @@ -275,7 +300,7 @@ function TimeService($translate, types) { | ||
275 | realtimeWindowMs: null, | 300 | realtimeWindowMs: null, |
276 | aggregation: { | 301 | aggregation: { |
277 | interval: SECOND, | 302 | interval: SECOND, |
278 | - limit: AVG_LIMIT, | 303 | + limit: maxDatapointsLimit, |
279 | type: types.aggregation.avg.value | 304 | type: types.aggregation.avg.value |
280 | } | 305 | } |
281 | }; | 306 | }; |
@@ -283,14 +308,14 @@ function TimeService($translate, types) { | @@ -283,14 +308,14 @@ function TimeService($translate, types) { | ||
283 | if (stateData) { | 308 | if (stateData) { |
284 | subscriptionTimewindow.aggregation = { | 309 | subscriptionTimewindow.aggregation = { |
285 | interval: SECOND, | 310 | interval: SECOND, |
286 | - limit: MAX_LIMIT, | 311 | + limit: maxDatapointsLimit, |
287 | type: types.aggregation.none.value, | 312 | type: types.aggregation.none.value, |
288 | stateData: true | 313 | stateData: true |
289 | }; | 314 | }; |
290 | } else { | 315 | } else { |
291 | subscriptionTimewindow.aggregation = { | 316 | subscriptionTimewindow.aggregation = { |
292 | interval: SECOND, | 317 | interval: SECOND, |
293 | - limit: AVG_LIMIT, | 318 | + limit: maxDatapointsLimit, |
294 | type: types.aggregation.avg.value | 319 | type: types.aggregation.avg.value |
295 | }; | 320 | }; |
296 | } | 321 | } |
@@ -298,7 +323,7 @@ function TimeService($translate, types) { | @@ -298,7 +323,7 @@ function TimeService($translate, types) { | ||
298 | if (angular.isDefined(timewindow.aggregation) && !stateData) { | 323 | if (angular.isDefined(timewindow.aggregation) && !stateData) { |
299 | subscriptionTimewindow.aggregation = { | 324 | subscriptionTimewindow.aggregation = { |
300 | type: timewindow.aggregation.type || types.aggregation.avg.value, | 325 | type: timewindow.aggregation.type || types.aggregation.avg.value, |
301 | - limit: timewindow.aggregation.limit || AVG_LIMIT | 326 | + limit: timewindow.aggregation.limit || maxDatapointsLimit |
302 | }; | 327 | }; |
303 | } | 328 | } |
304 | if (angular.isDefined(timewindow.realtime)) { | 329 | if (angular.isDefined(timewindow.realtime)) { |
@@ -22,7 +22,7 @@ export default angular.module('thingsboard.api.user', [thingsboardApiLogin, | @@ -22,7 +22,7 @@ export default angular.module('thingsboard.api.user', [thingsboardApiLogin, | ||
22 | .name; | 22 | .name; |
23 | 23 | ||
24 | /*@ngInject*/ | 24 | /*@ngInject*/ |
25 | -function UserService($http, $q, $rootScope, adminService, dashboardService, loginService, toast, store, jwtHelper, $translate, $state, $location) { | 25 | +function UserService($http, $q, $rootScope, adminService, dashboardService, timeService, loginService, toast, store, jwtHelper, $translate, $state, $location) { |
26 | var currentUser = null, | 26 | var currentUser = null, |
27 | currentUserDetails = null, | 27 | currentUserDetails = null, |
28 | lastPublicDashboardId = null, | 28 | lastPublicDashboardId = null, |
@@ -390,6 +390,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | @@ -390,6 +390,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | ||
390 | function loadSystemParams() { | 390 | function loadSystemParams() { |
391 | var promises = []; | 391 | var promises = []; |
392 | promises.push(loadIsUserTokenAccessEnabled()); | 392 | promises.push(loadIsUserTokenAccessEnabled()); |
393 | + promises.push(timeService.loadMaxDatapointsLimit()); | ||
393 | return $q.all(promises); | 394 | return $q.all(promises); |
394 | } | 395 | } |
395 | 396 |
@@ -31,6 +31,8 @@ export default function TimewindowPanelController(mdPanelRef, $scope, timeServic | @@ -31,6 +31,8 @@ export default function TimewindowPanelController(mdPanelRef, $scope, timeServic | ||
31 | vm.maxRealtimeAggInterval = maxRealtimeAggInterval; | 31 | vm.maxRealtimeAggInterval = maxRealtimeAggInterval; |
32 | vm.minHistoryAggInterval = minHistoryAggInterval; | 32 | vm.minHistoryAggInterval = minHistoryAggInterval; |
33 | vm.maxHistoryAggInterval = maxHistoryAggInterval; | 33 | vm.maxHistoryAggInterval = maxHistoryAggInterval; |
34 | + vm.minDatapointsLimit = minDatapointsLimit; | ||
35 | + vm.maxDatapointsLimit = maxDatapointsLimit; | ||
34 | 36 | ||
35 | if (vm.historyOnly) { | 37 | if (vm.historyOnly) { |
36 | vm.timewindow.selectedTab = 1; | 38 | vm.timewindow.selectedTab = 1; |
@@ -86,6 +88,14 @@ export default function TimewindowPanelController(mdPanelRef, $scope, timeServic | @@ -86,6 +88,14 @@ export default function TimewindowPanelController(mdPanelRef, $scope, timeServic | ||
86 | return timeService.maxIntervalLimit(currentHistoryTimewindow()); | 88 | return timeService.maxIntervalLimit(currentHistoryTimewindow()); |
87 | } | 89 | } |
88 | 90 | ||
91 | + function minDatapointsLimit () { | ||
92 | + return timeService.getMinDatapointsLimit(); | ||
93 | + } | ||
94 | + | ||
95 | + function maxDatapointsLimit () { | ||
96 | + return timeService.getMaxDatapointsLimit(); | ||
97 | + } | ||
98 | + | ||
89 | function currentHistoryTimewindow() { | 99 | function currentHistoryTimewindow() { |
90 | if (vm.timewindow.history.historyType === 0) { | 100 | if (vm.timewindow.history.historyType === 0) { |
91 | return vm.timewindow.history.timewindowMs; | 101 | return vm.timewindow.history.timewindowMs; |
@@ -60,19 +60,22 @@ | @@ -60,19 +60,22 @@ | ||
60 | </md-option> | 60 | </md-option> |
61 | </md-select> | 61 | </md-select> |
62 | </md-input-container> | 62 | </md-input-container> |
63 | - <md-slider-container ng-show="vm.showLimit()"> | 63 | + <md-slider-container ng-if="vm.showLimit()"> |
64 | <span translate>aggregation.limit</span> | 64 | <span translate>aggregation.limit</span> |
65 | - <md-slider flex min="10" max="500" ng-model="vm.timewindow.aggregation.limit" aria-label="limit" id="limit-slider"> | 65 | + <md-slider flex min="{{vm.minDatapointsLimit()}}" max="{{vm.maxDatapointsLimit()}}" step="1" |
66 | + ng-model="vm.timewindow.aggregation.limit" aria-label="limit" id="limit-slider"> | ||
66 | </md-slider> | 67 | </md-slider> |
67 | - <md-input-container> | ||
68 | - <input flex type="number" ng-model="vm.timewindow.aggregation.limit" aria-label="limit" aria-controls="limit-slider"> | 68 | + <md-input-container style="max-width: 80px;"> |
69 | + <input flex type="number" step="1" | ||
70 | + min="{{vm.minDatapointsLimit()}}" max="{{vm.maxDatapointsLimit()}}" | ||
71 | + ng-model="vm.timewindow.aggregation.limit" aria-label="limit" aria-controls="limit-slider"> | ||
69 | </md-input-container> | 72 | </md-input-container> |
70 | </md-slider-container> | 73 | </md-slider-container> |
71 | - <tb-timeinterval ng-show="vm.showRealtimeAggInterval()" min="vm.minRealtimeAggInterval()" max="vm.maxRealtimeAggInterval()" | 74 | + <tb-timeinterval ng-if="vm.showRealtimeAggInterval()" min="vm.minRealtimeAggInterval()" max="vm.maxRealtimeAggInterval()" |
72 | predefined-name="'aggregation.group-interval'" | 75 | predefined-name="'aggregation.group-interval'" |
73 | ng-model="vm.timewindow.realtime.interval"> | 76 | ng-model="vm.timewindow.realtime.interval"> |
74 | </tb-timeinterval> | 77 | </tb-timeinterval> |
75 | - <tb-timeinterval ng-show="vm.showHistoryAggInterval()" min="vm.minHistoryAggInterval()" max="vm.maxHistoryAggInterval()" | 78 | + <tb-timeinterval ng-if="vm.showHistoryAggInterval()" min="vm.minHistoryAggInterval()" max="vm.maxHistoryAggInterval()" |
76 | predefined-name="'aggregation.group-interval'" | 79 | predefined-name="'aggregation.group-interval'" |
77 | ng-model="vm.timewindow.history.interval"> | 80 | ng-model="vm.timewindow.history.interval"> |
78 | </tb-timeinterval> | 81 | </tb-timeinterval> |
@@ -228,7 +228,7 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM | @@ -228,7 +228,7 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM | ||
228 | if (angular.isDefined(value.aggregation.type) && value.aggregation.type.length > 0) { | 228 | if (angular.isDefined(value.aggregation.type) && value.aggregation.type.length > 0) { |
229 | model.aggregation.type = value.aggregation.type; | 229 | model.aggregation.type = value.aggregation.type; |
230 | } | 230 | } |
231 | - model.aggregation.limit = value.aggregation.limit || timeService.avgAggregationLimit(); | 231 | + model.aggregation.limit = value.aggregation.limit || Math.floor(timeService.getMaxDatapointsLimit() / 2); |
232 | } | 232 | } |
233 | } | 233 | } |
234 | scope.updateDisplayValue(); | 234 | scope.updateDisplayValue(); |