Browse Source

Merge branch 'rc' into release-4.3

pull/15304/head
Igor Kulikov 9 months ago
parent
commit
e1edb8691c
  1. 3
      application/src/main/data/json/system/widget_bundles/gateway_widgets.json
  2. 5
      application/src/main/data/json/system/widget_types/gateway_configuration.json
  3. 5
      application/src/main/data/json/system/widget_types/gateway_configuration__single_device_.json
  4. 4
      application/src/main/data/json/system/widget_types/gateway_connectors.json
  5. 2
      application/src/main/data/json/system/widget_types/gateway_custom_statistics.json
  6. 2
      application/src/main/data/json/system/widget_types/gateway_general_chart_statistics.json
  7. 4
      application/src/main/data/json/system/widget_types/gateway_general_configuration.json
  8. 4
      application/src/main/data/json/system/widget_types/gateway_logs.json
  9. 29
      application/src/main/data/json/system/widget_types/gateway_markdown_html_card.json
  10. 3
      application/src/main/data/json/system/widget_types/gateway_status.json
  11. 2
      application/src/main/data/json/system/widget_types/service_rpc.json
  12. 1984
      application/src/main/data/resources/dashboards/gateways_dashboard.json
  13. 34
      application/src/main/data/resources/js_extensions/gateway-management-extension.js
  14. 2
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldArgumentResetMsg.java
  15. 38
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java
  16. 21
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java
  17. 2
      application/src/main/java/org/thingsboard/server/actors/calculatedField/EntityInitCalculatedFieldMsg.java
  18. 2
      application/src/main/resources/thingsboard.yml
  19. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  20. 30
      common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldEventType.java
  21. 16
      dao/src/test/java/org/thingsboard/server/dao/service/CalculatedFieldServiceTest.java
  22. 78
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java
  23. 15
      ui-ngx/src/app/modules/home/components/calculated-fields/components/related-entities-aggregation-configuration/related-entities-aggregation-component.component.ts
  24. 2
      ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.html
  25. 2
      ui-ngx/src/app/shared/models/calculated-field.models.ts
  26. 712
      ui-ngx/src/assets/locale/locale.constant-da_DK.json
  27. 731
      ui-ngx/src/assets/locale/locale.constant-de_DE.json
  28. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  29. 715
      ui-ngx/src/assets/locale/locale.constant-fr_FR.json
  30. 9547
      ui-ngx/src/assets/locale/locale.constant-hi_IN.json
  31. 721
      ui-ngx/src/assets/locale/locale.constant-it_IT.json
  32. 11699
      ui-ngx/src/assets/locale/locale.constant-ja_JP.json
  33. 719
      ui-ngx/src/assets/locale/locale.constant-nl_NL.json
  34. 886
      ui-ngx/src/assets/locale/locale.constant-tr_TR.json

3
application/src/main/data/json/system/widget_bundles/gateway_widgets.json

@ -17,6 +17,7 @@
"gateway_widgets.gateway_custom_statistics",
"gateway_widgets.gateway_general_chart_statistics",
"gateway_widgets.service_rpc",
"gateway_widgets.gateway_status"
"gateway_widgets.gateway_status",
"gateway_widgets.markdown_card"
]
}

5
application/src/main/data/json/system/widget_types/gateway_configuration.json

@ -17,9 +17,10 @@
"templateHtml": "<tb-gateway-form\n [ctx]=\"ctx\">\n</tb-gateway-form>\n",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n}\n",
"settingsSchema": "",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-gateway-config-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"widgetTitle\":\"Gateway Configuration\",\"archiveFileName\":\"configurationGateway\"},\"title\":\"Gateway Configuration\",\"dropShadow\":true,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false,\"actions\":{}}"
"defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"widgetTitle\":\"Gateway Configuration\",\"archiveFileName\":\"configurationGateway\"},\"title\":\"Gateway Configuration\",\"dropShadow\":true,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
},
"externalId": null,
"tags": [
@ -41,4 +42,4 @@
"ble",
"bluetooth"
]
}
}

5
application/src/main/data/json/system/widget_types/gateway_configuration__single_device_.json

@ -17,9 +17,10 @@
"templateHtml": "<tb-gateway-form\n [ctx]=\"ctx\"\n [isStateForm]=\"true\">\n</tb-gateway-form>",
"templateCss": "#container {\n overflow: auto;\n}\n\n.tbDatasource-container {\n margin: 5px;\n padding: 8px;\n}\n\n.tbDatasource-title {\n font-size: 1.200rem;\n font-weight: 500;\n padding-bottom: 10px;\n}\n\n.tbDatasource-table {\n width: 100%;\n box-shadow: 0 0 10px #ccc;\n border-collapse: collapse;\n white-space: nowrap;\n font-size: 1.000rem;\n color: #757575;\n}\n\n.tbDatasource-table td {\n position: relative;\n border-top: 1px solid rgba(0, 0, 0, 0.12);\n border-bottom: 1px solid rgba(0, 0, 0, 0.12);\n padding: 0px 18px;\n box-sizing: border-box;\n}",
"controllerScript": "self.onInit = function() {\n}\n\n\nself.onDestroy = function() {\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\t\t\t\n dataKeysOptional: true,\n singleEntity: true\n };\n}\n\n",
"settingsSchema": "",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-gateway-config-single-device-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"gatewayTitle\":\"Gateway configuration (Single device)\"},\"title\":\"Gateway configuration (Single device)\"}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"gatewayTitle\":\"Gateway configuration (Single device)\"},\"title\":\"Gateway configuration (Single device)\"}"
},
"externalId": null,
"tags": [
@ -41,4 +42,4 @@
"ble",
"bluetooth"
]
}
}

4
application/src/main/data/json/system/widget_types/gateway_connectors.json

@ -19,7 +19,7 @@
"controllerScript": "self.onInit = function() {\n if (self.ctx.datasources && self.ctx.datasources.length) {\n self.ctx.$scope.entityId = self.ctx.datasources[0].entity.id;\n }\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.gatewayConnectors?.onDataUpdated();\n};\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true,\n singleEntity: true\n };\n}",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Gateway connectors\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":500},\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Gateway connectors\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":500},\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false}"
},
"tags": [
"router",
@ -40,4 +40,4 @@
"ble",
"bluetooth"
]
}
}

2
application/src/main/data/json/system/widget_types/gateway_custom_statistics.json

@ -44,4 +44,4 @@
"ble",
"bluetooth"
]
}
}

2
application/src/main/data/json/system/widget_types/gateway_general_chart_statistics.json

@ -44,4 +44,4 @@
"ble",
"bluetooth"
]
}
}

4
application/src/main/data/json/system/widget_types/gateway_general_configuration.json

@ -18,7 +18,7 @@
"templateCss": "",
"controllerScript": "self.onInit = function() {\n if (self.ctx.datasources && self.ctx.datasources.length) {\n self.ctx.$scope.entityId = self.ctx.datasources[0].entity.id;\n self.ctx.$scope.defaultTab = self.ctx.stateController?.getStateParams()?.defaultTab;\n }\n};\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true,\n singleEntity: true\n };\n}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Gateway configuration\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":500},\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Gateway configuration\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":500},\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false}"
},
"tags": [
"router",
@ -39,4 +39,4 @@
"ble",
"bluetooth"
]
}
}

4
application/src/main/data/json/system/widget_types/gateway_logs.json

@ -20,7 +20,7 @@
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-gateway-logs-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":25000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Gateway logs\",\"showTitleIcon\":false,\"dropShadow\":false,\"enableFullscreen\":true,\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false,\"useDashboardTimewindow\":false,\"displayTimewindow\":true}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":0,\"realtime\":{\"realtimeType\":0,\"timewindowMs\":86400000,\"quickInterval\":\"CURRENT_DAY\",\"interval\":300000},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Gateway logs\",\"showTitleIcon\":false,\"dropShadow\":false,\"enableFullscreen\":true,\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false,\"useDashboardTimewindow\":false,\"displayTimewindow\":true}"
},
"tags": [
"router",
@ -41,4 +41,4 @@
"ble",
"bluetooth"
]
}
}

29
application/src/main/data/json/system/widget_types/gateway_markdown_html_card.json

@ -0,0 +1,29 @@
{
"fqn": "gateway_widgets.markdown_card",
"name": "Gateway Markdown/HTML Card",
"deprecated": false,
"image": "tb-image;/api/images/system/markdown_html_card_system_widget_image.png",
"description": "Renders markdown/HTML using the configurable pattern or function with applied attributes or time series values.",
"descriptor": {
"type": "latest",
"sizeX": 5,
"sizeY": 3.5,
"resources": [
{
"url": "tb-resource;/api/resource/js_module/system/gateway-management-extension.js",
"isModule": true
}
],
"templateHtml": "<tb-markdown-widget \n [ctx]=\"ctx\">\n</tb-markdown-widget>",
"templateCss": "#container tb-markdown-widget {\n height: 100%;\n display: block;\n}\n\n#container tb-markdown-widget .tb-markdown-view {\n height: 100%;\n overflow: auto;\n}\n",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.markdownWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true,\n datasourcesOptional: true,\n hasDataPageLink: true\n };\n}\n\nself.onDestroy = function() {\n}\n\n",
"settingsDirective": "tb-markdown-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"markdownTextPattern\":\"### Markdown/HTML card\\n - **Current entity**: ${entityName}.\\n - **Current value**: ${Random}.\",\"markdownTextFunction\":\"return '# Some title\\\\n - Entity name: ' + data[0]['entityName'];\",\"useMarkdownTextFunction\":false},\"title\":\"Markdown/HTML Card\",\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}"
},
"resources": null,
"scada": false,
"tags": [
"web",
"markup"
]
}

3
application/src/main/data/json/system/widget_types/gateway_status.json

@ -18,7 +18,8 @@
"templateCss": "",
"controllerScript": "self.onInit = function() {\n if (self.ctx.datasources && self.ctx.datasources.length) {\n self.ctx.$scope.entityId = self.ctx.datasources[0].entity.id;\n }\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.gatewayStatus?.onDataUpdated();\n};",
"settingsSchema": "",
"defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"cardHtml\":\"<div class='card'>HTML code here</div>\",\"cardCss\":\".card {\\n font-weight: bold;\\n font-size: 32px;\\n color: #999;\\n width: 100%;\\n height: 100%;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n}\"},\"title\":\"HTML Card\",\"dropShadow\":true}"
"dataKeySettingsSchema": "",
"defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"cardHtml\":\"<div class='card'>HTML code here</div>\",\"cardCss\":\".card {\\n font-weight: bold;\\n font-size: 32px;\\n color: #999;\\n width: 100%;\\n height: 100%;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n}\"},\"title\":\"HTML Card\",\"dropShadow\":true}"
},
"tags": [
"router",

2
application/src/main/data/json/system/widget_types/service_rpc.json

@ -35,4 +35,4 @@
"inter-process communication",
"server request"
]
}
}

1984
application/src/main/data/resources/dashboards/gateways_dashboard.json

File diff suppressed because it is too large

34
application/src/main/data/resources/js_extensions/gateway-management-extension.js

File diff suppressed because one or more lines are too long

2
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldArgumentResetMsg.java

@ -16,6 +16,7 @@
package org.thingsboard.server.actors.calculatedField;
import lombok.Data;
import org.thingsboard.server.common.data.cf.CalculatedFieldEventType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
@ -27,6 +28,7 @@ public class CalculatedFieldArgumentResetMsg implements ToCalculatedFieldSystemM
private final TenantId tenantId;
private final CalculatedFieldCtx ctx;
private final CalculatedFieldEventType eventType;
private final TbCallback callback;
@Override

38
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.cf.CalculatedFieldEventType;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.Argument;
import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
@ -75,7 +76,6 @@ import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.DataConstants.REEVALUATION_MSG;
import static org.thingsboard.server.common.data.cf.configuration.PropagationCalculatedFieldConfiguration.PROPAGATION_CONFIG_ARGUMENT;
import static org.thingsboard.server.service.cf.ctx.state.TsRollingArgumentEntry.getValueForTsRecord;
import static org.thingsboard.server.utils.CalculatedFieldArgumentUtils.createStateByType;
@ -169,7 +169,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
}
if (msg.getStateAction() != StateAction.REFRESH_CTX) {
if (state.isSizeOk()) {
processStateIfReady(state, Collections.emptyMap(), ctx, Collections.singletonList(ctx.getCfId()), null, null, msg.getCallback());
processStateIfReady(state, Collections.emptyMap(), ctx, Collections.singletonList(ctx.getCfId()), null, msg.getEventType().name(), msg.getCallback());
} else {
throw new RuntimeException(ctx.getSizeExceedsLimitMessage());
}
@ -196,7 +196,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
Map<String, ArgumentEntry> fetchedArgs = cfService.fetchArgsFromDb(tenantId, entityId, dynamicSourceArgs);
fetchedArgs.values().forEach(arg -> arg.setForceResetPrevious(true));
processArgumentValuesUpdate(ctx, Collections.singletonList(ctx.getCfId()), msg.getCallback(), fetchedArgs, null, null);
processArgumentValuesUpdate(ctx, Collections.singletonList(ctx.getCfId()), msg.getCallback(), fetchedArgs, null, msg.getEventType().name());
} catch (Exception e) {
throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).cause(e).build();
}
@ -256,7 +256,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
state.checkStateSize(new CalculatedFieldEntityCtxId(tenantId, ctx.getCfId(), entityId), ctx.getMaxStateSize());
}
if (state.isSizeOk()) {
processStateIfReady(state, updatedArgs, ctx, Collections.singletonList(ctx.getCfId()), null, null, msg.getCallback());
processStateIfReady(state, updatedArgs, ctx, Collections.singletonList(ctx.getCfId()), null, TbMsgType.RELATION_ADD_OR_UPDATE.name(), msg.getCallback());
} else {
throw CalculatedFieldException.builder().ctx(ctx).eventEntity(entityId).errorMessage(ctx.getSizeExceedsLimitMessage()).build();
}
@ -283,7 +283,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
state.checkStateSize(new CalculatedFieldEntityCtxId(tenantId, ctx.getCfId(), entityId), ctx.getMaxStateSize());
if (state.isSizeOk()) {
processStateIfReady(state, Collections.emptyMap(), ctx, Collections.singletonList(ctx.getCfId()), null, null, msg.getCallback());
processStateIfReady(state, Collections.emptyMap(), ctx, Collections.singletonList(ctx.getCfId()), null, TbMsgType.RELATION_DELETED.name(), msg.getCallback());
} else {
throw new RuntimeException(ctx.getSizeExceedsLimitMessage());
}
@ -293,6 +293,9 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
PropagationArgumentEntry entry = new PropagationArgumentEntry();
entry.setRemoved(msg.getRelatedEntityId());
propagationState.update(Map.of(PROPAGATION_CONFIG_ARGUMENT, entry), ctx);
if (DebugModeUtil.isDebugAllAvailable(ctx.getCalculatedField())) {
systemContext.persistCalculatedFieldDebugEvent(tenantId, ctx.getCfId(), entityId, state.getArgumentsJson(), null, TbMsgType.RELATION_DELETED.name(), null, null);
}
}
msg.getCallback().onSuccess();
}
@ -323,13 +326,13 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
callback.onSuccess();
} else {
if (proto.getTsDataCount() > 0) {
processArgumentValuesUpdate(ctx, cfIds, callback, mapToArguments(ctx, msg.getEntityId(), proto.getTsDataList()), toTbMsgId(proto), toTbMsgType(proto));
processArgumentValuesUpdate(ctx, cfIds, callback, mapToArguments(ctx, msg.getEntityId(), proto.getTsDataList()), toTbMsgId(proto), toMsgType(proto));
} else if (proto.getAttrDataCount() > 0) {
processArgumentValuesUpdate(ctx, cfIds, callback, mapToArguments(ctx, msg.getEntityId(), proto.getScope(), proto.getAttrDataList()), toTbMsgId(proto), toTbMsgType(proto));
processArgumentValuesUpdate(ctx, cfIds, callback, mapToArguments(ctx, msg.getEntityId(), proto.getScope(), proto.getAttrDataList()), toTbMsgId(proto), toMsgType(proto));
} else if (proto.getRemovedTsKeysCount() > 0) {
processArgumentValuesUpdate(ctx, cfIds, callback, mapToArgumentsWithFetchedValue(ctx, msg.getEntityId(), proto.getRemovedTsKeysList()), toTbMsgId(proto), toTbMsgType(proto));
processArgumentValuesUpdate(ctx, cfIds, callback, mapToArgumentsWithFetchedValue(ctx, msg.getEntityId(), proto.getRemovedTsKeysList()), toTbMsgId(proto), toMsgType(proto));
} else if (proto.getRemovedAttrKeysCount() > 0) {
processArgumentValuesUpdate(ctx, cfIds, callback, mapToArgumentsWithDefaultValue(ctx, msg.getEntityId(), proto.getScope(), proto.getRemovedAttrKeysList()), toTbMsgId(proto), toTbMsgType(proto));
processArgumentValuesUpdate(ctx, cfIds, callback, mapToArgumentsWithDefaultValue(ctx, msg.getEntityId(), proto.getScope(), proto.getRemovedAttrKeysList()), toTbMsgId(proto), toMsgType(proto));
} else {
callback.onSuccess();
}
@ -379,7 +382,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
}
if (state.isSizeOk()) {
log.debug("[{}][{}] Reevaluating CF state", entityId, cfId);
processStateIfReady(state, null, ctx, Collections.singletonList(cfId), null, REEVALUATION_MSG, msg.getCallback());
processStateIfReady(state, null, ctx, Collections.singletonList(cfId), null, CalculatedFieldEventType.REEVALUATION_MSG.name(), msg.getCallback());
} else {
throw new RuntimeException(ctx.getSizeExceedsLimitMessage());
}
@ -399,23 +402,23 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
}
private void processTelemetry(CalculatedFieldCtx ctx, CalculatedFieldTelemetryMsgProto proto, List<CalculatedFieldId> cfIdList, TbCallback callback) throws CalculatedFieldException {
processArgumentValuesUpdate(ctx, cfIdList, callback, mapToArguments(ctx, proto.getTsDataList()), toTbMsgId(proto), toTbMsgType(proto));
processArgumentValuesUpdate(ctx, cfIdList, callback, mapToArguments(ctx, proto.getTsDataList()), toTbMsgId(proto), toMsgType(proto));
}
private void processAttributes(CalculatedFieldCtx ctx, CalculatedFieldTelemetryMsgProto proto, List<CalculatedFieldId> cfIdList, TbCallback callback) throws CalculatedFieldException {
processArgumentValuesUpdate(ctx, cfIdList, callback, mapToArguments(ctx, proto.getScope(), proto.getAttrDataList()), toTbMsgId(proto), toTbMsgType(proto));
processArgumentValuesUpdate(ctx, cfIdList, callback, mapToArguments(ctx, proto.getScope(), proto.getAttrDataList()), toTbMsgId(proto), toMsgType(proto));
}
private void processRemovedTelemetry(CalculatedFieldCtx ctx, CalculatedFieldTelemetryMsgProto proto, List<CalculatedFieldId> cfIdList, TbCallback callback) throws CalculatedFieldException {
processArgumentValuesUpdate(ctx, cfIdList, callback, mapToArgumentsWithFetchedValue(ctx, entityId, proto.getRemovedTsKeysList()), toTbMsgId(proto), toTbMsgType(proto));
processArgumentValuesUpdate(ctx, cfIdList, callback, mapToArgumentsWithFetchedValue(ctx, entityId, proto.getRemovedTsKeysList()), toTbMsgId(proto), toMsgType(proto));
}
private void processRemovedAttributes(CalculatedFieldCtx ctx, CalculatedFieldTelemetryMsgProto proto, List<CalculatedFieldId> cfIdList, TbCallback callback) throws CalculatedFieldException {
processArgumentValuesUpdate(ctx, cfIdList, callback, mapToArgumentsWithDefaultValue(ctx, proto.getScope(), proto.getRemovedAttrKeysList()), toTbMsgId(proto), toTbMsgType(proto));
processArgumentValuesUpdate(ctx, cfIdList, callback, mapToArgumentsWithDefaultValue(ctx, proto.getScope(), proto.getRemovedAttrKeysList()), toTbMsgId(proto), toMsgType(proto));
}
private void processArgumentValuesUpdate(CalculatedFieldCtx ctx, List<CalculatedFieldId> cfIdList, TbCallback callback,
Map<String, ArgumentEntry> newArgValues, UUID tbMsgId, TbMsgType tbMsgType) throws CalculatedFieldException {
Map<String, ArgumentEntry> newArgValues, UUID tbMsgId, String msgType) throws CalculatedFieldException {
if (newArgValues.isEmpty()) {
log.debug("[{}] No new argument values to process for CF.", ctx.getCfId());
callback.onSuccess();
@ -453,7 +456,6 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
if (!updatedArgs.isEmpty() || justRestored) {
cfIdList = new ArrayList<>(cfIdList);
cfIdList.add(ctx.getCfId());
String msgType = tbMsgType == null ? null : tbMsgType.name();
processStateIfReady(state, updatedArgs, ctx, cfIdList, tbMsgId, msgType, callback);
} else {
callback.onSuccess();
@ -777,9 +779,9 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
return null;
}
private TbMsgType toTbMsgType(CalculatedFieldTelemetryMsgProto proto) {
private String toMsgType(CalculatedFieldTelemetryMsgProto proto) {
if (!proto.getTbMsgType().isEmpty()) {
return TbMsgType.valueOf(proto.getTbMsgType());
return proto.getTbMsgType();
}
return null;
}

21
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java

@ -17,6 +17,7 @@ package org.thingsboard.server.actors.calculatedField;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.function.TriConsumer;
import org.thingsboard.common.util.DebugModeUtil;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorCtx;
@ -34,6 +35,7 @@ import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldEventType;
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.HasRelationPathLevel;
@ -43,6 +45,7 @@ import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.relation.EntityRelation;
@ -278,7 +281,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
if (!cfsToReinit.isEmpty()) {
MultipleTbCallback cfsReinitCallback = new MultipleTbCallback(cfsToReinit.size(), callback);
cfsToReinit.forEach(ctx -> applyToTargetCfEntityActors(ctx, cfsReinitCallback, (id, cb) -> initCfForEntity(id, ctx, StateAction.REINIT, cb)));
cfsToReinit.forEach(ctx -> applyToTargetCfEntityActors(ctx, cfsReinitCallback, (id, cb) -> initCfForEntity(id, ctx, StateAction.REINIT, CalculatedFieldEventType.TENANT_PROFILE_UPDATED, cb)));
} else {
callback.onSuccess();
}
@ -309,8 +312,8 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
var fieldsCount = entityIdFields.size() + profileIdFields.size();
if (fieldsCount > 0) {
MultipleTbCallback multiCallback = new MultipleTbCallback(fieldsCount, callback);
entityIdFields.forEach(ctx -> initCfForEntity(entityId, ctx, StateAction.INIT, multiCallback));
profileIdFields.forEach(ctx -> initCfForEntity(entityId, ctx, StateAction.INIT, multiCallback));
entityIdFields.forEach(ctx -> initCfForEntity(entityId, ctx, StateAction.INIT, CalculatedFieldEventType.INITIALIZED, multiCallback));
profileIdFields.forEach(ctx -> initCfForEntity(entityId, ctx, StateAction.INIT, CalculatedFieldEventType.INITIALIZED, multiCallback));
} else {
callback.onSuccess();
}
@ -329,7 +332,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
MultipleTbCallback multiCallback = new MultipleTbCallback(fieldsCount, callback);
var entityId = msg.getEntityId();
oldProfileCfs.forEach(ctx -> deleteCfForEntity(entityId, ctx.getCfId(), multiCallback));
newProfileCfs.forEach(ctx -> initCfForEntity(entityId, ctx, StateAction.INIT, multiCallback));
newProfileCfs.forEach(ctx -> initCfForEntity(entityId, ctx, StateAction.INIT, CalculatedFieldEventType.INITIALIZED, multiCallback));
} else {
callback.onSuccess();
}
@ -429,7 +432,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
// Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
entityIdCalculatedFields.computeIfAbsent(cf.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(cfCtx);
addLinks(cf);
applyToTargetCfEntityActors(cfCtx, callback, (id, cb) -> initCfForEntity(id, cfCtx, StateAction.INIT, cb));
applyToTargetCfEntityActors(cfCtx, callback, (id, cb) -> initCfForEntity(id, cfCtx, StateAction.INIT, CalculatedFieldEventType.INITIALIZED, cb));
}
}
}
@ -503,7 +506,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
oldCfCtx.close();
callback.onFailure(t);
}
}, (id, cb) -> initCfForEntity(id, newCfCtx, stateAction, cb));
}, (id, cb) -> initCfForEntity(id, newCfCtx, stateAction, CalculatedFieldEventType.UPDATED, cb));
}
}
}
@ -643,7 +646,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
cfs.forEach(cf -> {
if (isMyPartition(entityId, callback)) {
if (cf.hasCurrentOwnerSourceArguments()) {
CalculatedFieldArgumentResetMsg argResetMsg = new CalculatedFieldArgumentResetMsg(tenantId, cf, callback);
CalculatedFieldArgumentResetMsg argResetMsg = new CalculatedFieldArgumentResetMsg(tenantId, cf, CalculatedFieldEventType.OWNER_CHANGED, callback);
log.debug("Pushing CF argument reset msg to specific actor [{}]", entityId);
getOrCreateActor(entityId).tell(argResetMsg);
} else {
@ -750,9 +753,9 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
getOrCreateActor(entityId).tell(new CalculatedFieldEntityDeleteMsg(tenantId, cfId, callback));
}
private void initCfForEntity(EntityId entityId, CalculatedFieldCtx cfCtx, StateAction stateAction, TbCallback callback) {
private void initCfForEntity(EntityId entityId, CalculatedFieldCtx cfCtx, StateAction stateAction, CalculatedFieldEventType eventType, TbCallback callback) {
log.debug("Pushing entity init CF msg to specific actor [{}]", entityId);
getOrCreateActor(entityId).tell(new EntityInitCalculatedFieldMsg(tenantId, cfCtx, stateAction, callback));
getOrCreateActor(entityId).tell(new EntityInitCalculatedFieldMsg(tenantId, cfCtx, stateAction, eventType, callback));
}
private boolean isMyPartition(EntityId entityId, TbCallback callback) {

2
application/src/main/java/org/thingsboard/server/actors/calculatedField/EntityInitCalculatedFieldMsg.java

@ -16,6 +16,7 @@
package org.thingsboard.server.actors.calculatedField;
import lombok.Data;
import org.thingsboard.server.common.data.cf.CalculatedFieldEventType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
@ -28,6 +29,7 @@ public class EntityInitCalculatedFieldMsg implements ToCalculatedFieldSystemMsg
private final TenantId tenantId;
private final CalculatedFieldCtx ctx;
private final StateAction stateAction;
private final CalculatedFieldEventType eventType;
private final TbCallback callback;
@Override

2
application/src/main/resources/thingsboard.yml

@ -1330,7 +1330,7 @@ transport:
# URL of gateways dashboard repository
repository_url: "${TB_GATEWAY_DASHBOARD_SYNC_REPOSITORY_URL:https://github.com/thingsboard/gateway-management-extensions-dist.git}"
# Branch of gateways dashboard repository to work with
branch: "${TB_GATEWAY_DASHBOARD_SYNC_BRANCH:release/4.0.0}"
branch: "${TB_GATEWAY_DASHBOARD_SYNC_BRANCH:release/4.3.0}"
# Fetch frequency in hours for gateways dashboard repository
fetch_frequency: "${TB_GATEWAY_DASHBOARD_SYNC_FETCH_FREQUENCY:24}"

2
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -106,8 +106,6 @@ public class DataConstants {
public static final String RPC_FAILED = "RPC_FAILED";
public static final String RPC_DELETED = "RPC_DELETED";
public static final String REEVALUATION_MSG = "REEVALUATION_MSG";
public static final String DEFAULT_SECRET_KEY = "";
public static final String SECRET_KEY_FIELD_NAME = "secretKey";
public static final String DURATION_MS_FIELD_NAME = "durationMs";

30
common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldEventType.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2026 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.cf;
public enum CalculatedFieldEventType {
INITIALIZED,
UPDATED,
TENANT_PROFILE_UPDATED,
OWNER_CHANGED,
RELATION_ADD_OR_UPDATE,
RELATION_DELETED,
REEVALUATION_MSG
}

16
dao/src/test/java/org/thingsboard/server/dao/service/CalculatedFieldServiceTest.java

@ -17,11 +17,13 @@ package org.thingsboard.server.dao.service;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.Argument;
import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
import org.thingsboard.server.common.data.cf.configuration.AttributesOutput;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
import org.thingsboard.server.common.data.cf.configuration.RelationPathQueryDynamicSourceConfiguration;
@ -36,8 +38,8 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationPathLevel;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.exception.DataValidationException;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.exception.DataValidationException;
import java.util.ArrayList;
import java.util.List;
@ -103,6 +105,10 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
zoneGroupConfiguration.setRefDynamicSourceConfiguration(dynamicSourceConfiguration);
cfg.setZoneGroups(Map.of("allowed", zoneGroupConfiguration));
AttributesOutput out = new AttributesOutput();
out.setScope(AttributeScope.SERVER_SCOPE);
cfg.setOutput(out);
// Get tenant profile min.
int min = tbTenantProfileCache.get(tenantId)
.getDefaultProfileConfiguration()
@ -157,6 +163,10 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
zoneGroupConfiguration.setRefDynamicSourceConfiguration(dynamicSourceConfiguration);
cfg.setZoneGroups(Map.of("allowed", zoneGroupConfiguration));
AttributesOutput out = new AttributesOutput();
out.setScope(AttributeScope.SERVER_SCOPE);
cfg.setOutput(out);
// Create & save Calculated Field
CalculatedField cf = new CalculatedField();
cf.setTenantId(tenantId);
@ -191,6 +201,10 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
zoneGroupConfiguration.setRefDynamicSourceConfiguration(dynamicSourceConfiguration);
cfg.setZoneGroups(Map.of("allowed", zoneGroupConfiguration));
AttributesOutput out = new AttributesOutput();
out.setScope(AttributeScope.SERVER_SCOPE);
cfg.setOutput(out);
// Get tenant profile min.
int min = tbTenantProfileCache.get(tenantId)
.getDefaultProfileConfiguration()

78
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java

@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldEventType;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.Argument;
import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
@ -51,11 +52,15 @@ import org.thingsboard.server.common.data.debug.DebugSettings;
import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration;
import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.DeviceData;
import org.thingsboard.server.common.data.event.EventType;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.page.SortOrder;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationPathLevel;
@ -791,6 +796,79 @@ public class CalculatedFieldTest extends AbstractContainerTest {
testRestClient.deleteAsset(asset2.getId());
}
@Test
public void testDebugEvents() {
// --- Arrange entities ---
String deviceToken = "12345678901";
Device device = testRestClient.postDevice(deviceToken, createDevice("Propagation Device", deviceProfileId));
Asset asset1 = testRestClient.postAsset(createAsset("Propagated Asset 1", null));
// Create relations FROM asset 1 TO device
EntityRelation rel1 = new EntityRelation(asset1.getId(), device.getId(), EntityRelation.CONTAINS_TYPE);
testRestClient.postEntityRelation(rel1);
// --- Build CF: PROPAGATION ---
CalculatedField saved = createPropagationCF(device.getId());
// Create relations FROM asset 2 TO device
Asset asset2 = testRestClient.postAsset(createAsset("Propagated Asset 2", null));
EntityRelation rel2 = new EntityRelation(asset2.getId(), device.getId(), EntityRelation.CONTAINS_TYPE);
testRestClient.postEntityRelation(rel2);
// Telemetry on device
testRestClient.postTelemetry(deviceToken, JacksonUtil.toJsonNode("{\"temperature\":25.1}"));
// Delete relation between asset 1 and device
testRestClient.deleteEntityRelation(asset1.getId(), EntityRelation.CONTAINS_TYPE, device.getId());
// --- Assert propagated calculation (arguments-only mode) ---
await().alias("check debug events")
.atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
List<String> eventTypes = testRestClient.getEvents(saved.getId(), EventType.DEBUG_CALCULATED_FIELD, tenantId, new TimePageLink(4, 0, null, SortOrder.BY_CREATED_TIME_DESC)).getData().stream()
.map(e -> e.getBody().get("msgType").asText())
.toList();
assertThat(eventTypes).as("Check sequence of debug events")
.containsSequence(
CalculatedFieldEventType.RELATION_DELETED.name(),
TbMsgType.POST_TELEMETRY_REQUEST.name(),
CalculatedFieldEventType.RELATION_ADD_OR_UPDATE.name(),
CalculatedFieldEventType.INITIALIZED.name()
);
});
testRestClient.deleteCalculatedFieldIfExists(saved.getId());
testRestClient.deleteDeviceIfExists(device.getId());
testRestClient.deleteAsset(asset1.getId());
testRestClient.deleteAsset(asset2.getId());
}
private CalculatedField createPropagationCF(EntityId entityId) {
CalculatedField cf = new CalculatedField();
cf.setEntityId(entityId);
cf.setType(CalculatedFieldType.PROPAGATION);
cf.setName("Propagation CF (args-only)");
cf.setConfigurationVersion(1);
PropagationCalculatedFieldConfiguration cfg = new PropagationCalculatedFieldConfiguration();
cfg.setRelation(new RelationPathLevel(EntitySearchDirection.TO, EntityRelation.CONTAINS_TYPE));
cfg.setApplyExpressionToResolvedArguments(false); // arguments-only mode
Argument arg = new Argument();
arg.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null));
cfg.setArguments(Map.of("deviceTemperature", arg));
cfg.setOutput(new TimeSeriesOutput());
cf.setConfiguration(cfg);
cf.setDebugSettings(DebugSettings.all());
return testRestClient.postCalculatedField(cf);
}
private CalculatedField createOccupancyCF(EntityId entityId) {
CalculatedField calculatedField = new CalculatedField();
calculatedField.setName("Occupancy");

15
ui-ngx/src/app/modules/home/components/calculated-fields/components/related-entities-aggregation-configuration/related-entities-aggregation-component.component.ts

@ -120,7 +120,12 @@ export class RelatedEntitiesAggregationComponentComponent implements ControlValu
takeUntilDestroyed()
).subscribe((value: CalculatedFieldRelatedAggregationConfiguration) => {
this.updatedModel(value);
})
});
this.relatedAggregationConfiguration.get('output').valueChanges.pipe(
takeUntilDestroyed(),
).subscribe(() => {
this.toggleScopeByOutputType();
});
}
validate(): ValidationErrors | null {
@ -158,4 +163,12 @@ export class RelatedEntitiesAggregationComponentComponent implements ControlValu
value.scheduledUpdateInterval = this.minAllowedScheduledUpdateIntervalInSecForCF;
this.propagateChange(value);
}
private toggleScopeByOutputType(): void {
if (this.relatedAggregationConfiguration.get('output').value.type === OutputType.Attribute) {
this.relatedAggregationConfiguration.get('useLatestTs').disable({emitEvent: false});
} else {
this.relatedAggregationConfiguration.get('useLatestTs').enable({emitEvent: false});
}
}
}

2
ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.html

@ -208,7 +208,7 @@
<mat-expansion-panel class="tb-settings">
<mat-expansion-panel-header>
<mat-panel-title>
<mat-slide-toggle class="mat-slide flex items-center justify-start" (mousedown)="toggleExtensionPanel($event, providersForm.length, twoFaFormGroup.get('verificationCodeCheckRateLimitEnable').value)"
<mat-slide-toggle class="mat-slide flex items-center justify-start" (mousedown)="toggleExtensionPanel($event, providersForm.length+1, twoFaFormGroup.get('verificationCodeCheckRateLimitEnable').value)"
formControlName="verificationCodeCheckRateLimitEnable">
{{ 'admin.2fa.verification-code-check-rate-limit' | translate }}
</mat-slide-toggle>

2
ui-ngx/src/app/shared/models/calculated-field.models.ts

@ -91,7 +91,7 @@ export enum CalculatedFieldType {
ALARM = 'ALARM',
}
interface CalculatedFieldTypeTranslate {
export interface CalculatedFieldTypeTranslate {
name: string;
hint?: string;
}

712
ui-ngx/src/assets/locale/locale.constant-da_DK.json

File diff suppressed because it is too large

731
ui-ngx/src/assets/locale/locale.constant-de_DE.json

File diff suppressed because it is too large

1
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -10216,6 +10216,7 @@
"es_ES": "español (España)",
"fa_IR": "فارسی (ایران)",
"fr_FR": "français (France)",
"hi_IN": "हिन्दी (भारत)",
"it_IT": "italiano (Italia)",
"ja_JP": "日本語 (日本)",
"ka_GE": "ქართული (საქართველო)",

715
ui-ngx/src/assets/locale/locale.constant-fr_FR.json

File diff suppressed because it is too large

9547
ui-ngx/src/assets/locale/locale.constant-hi_IN.json

File diff suppressed because it is too large

721
ui-ngx/src/assets/locale/locale.constant-it_IT.json

File diff suppressed because it is too large

11699
ui-ngx/src/assets/locale/locale.constant-ja_JP.json

File diff suppressed because it is too large

719
ui-ngx/src/assets/locale/locale.constant-nl_NL.json

File diff suppressed because it is too large

886
ui-ngx/src/assets/locale/locale.constant-tr_TR.json

File diff suppressed because it is too large
Loading…
Cancel
Save