diff --git a/application/src/main/data/json/system/widget_bundles/home_page_widgets.json b/application/src/main/data/json/system/widget_bundles/home_page_widgets.json
index bfee44b341..2d382b5958 100644
--- a/application/src/main/data/json/system/widget_bundles/home_page_widgets.json
+++ b/application/src/main/data/json/system/widget_bundles/home_page_widgets.json
@@ -3,16 +3,35 @@
"alias": "home_page_widgets",
"title": "Home page widgets",
"image": "data:image/png;base64,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",
- "description": "Useful to define home dashboard of the user. Contains widgets that enable navigation to necessary links.",
+ "description": "Contains useful widgets to design the user's home page.",
"externalId": null,
"name": "Home page widgets"
},
"widgetTypes": [
+ {
+ "alias": "getting_started",
+ "name": "Getting started",
+ "image": "data:image/png;base64,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",
+ "description": "Getting started instruction.",
+ "descriptor": {
+ "type": "static",
+ "sizeX": 7.5,
+ "sizeY": 6.5,
+ "resources": [],
+ "templateHtml": "\n\n",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n}",
+ "settingsSchema": "",
+ "dataKeySettingsSchema": "",
+ "settingsDirective": "",
+ "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Getting started\",\"dropShadow\":true}"
+ }
+ },
{
"alias": "documentation_links",
"name": "Documentation links",
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZQAAAFECAMAAAA3JTi9AAAA9lBMVEUAAAAAAAAAAAAAAADo6OhtbW34+Pj7+/vt7e26urr////9/f31+fn7+/v39/f5+fny8vIwVoAhISF1dXX19fXj4+Oenp7z9PRYWFg8PDx0dHT5+/uQkJDW3+Xg4OC6urrIyMisrKzE0NupqamGhobu7u7Hx8eCgoLLy8vc5eovLy+Sp7wuLi7V1dWOjo70+PhJao+ru8uru8y5ublmZmbo7/Hc3NyXl5d5k619fX3m5uaYmJjd3d3Ozs6rvMvW1tZKSkrDw8Pu8/SxsbFhfp5LS0vr6+vh6e2ysrKhoaHQ2uK3xtOGnbQ8YIeescRtiKXh4eFuiKUhOgT+AAAACnRSTlMfAAwYnTHV46tXmfAXZwAAIUdJREFUeNrs1dENwCAMA9EjAdpm/4ErYAj84bfCyTJti0zsssxoGzsJpuFk4TT5+lN22XwHxIkSjF4moY9VhdVklomYqwot8U6EdGgEo0zIR5C8ZUI6SeJHkfKQQJkUcBQ5jiLIUQQ5iiBHEeQoghxFkKMIchRBP3v1z9o4DMZxfP+VcwOPSLdokARa4iUgIzkZhKEk273/V3OPJd8lJlHtoUeC6y8U1OjPoA+JVpQXbEV5wVaUF2xFeU4hoNSK8qRO2+0JhVaU58QmZZUV5Sm9q+0XKk9GMfiRGWyyygceV0Y5t9zF4v9lqcYP7LR1WUVt8LgySkOp2uAb6nQcH64DsCeNid4/r71hEfF7klSKJl+jSCBoavENKZK4TdMRgMBU1e7aLywhNskqh5LJJAqET9dXSaVsvqaYR3vpAEQZ4KQ78mdimOGsUtEARkbedzYQsqZGGuB4VmcL3uFJRQRp04F5NawM/Tm47a3KfS4FhU0GlWLTKOhIAcETpwVg9DBSaVqThaT0WVsTF4EqDbSBI9/PeGOIY9wD9UsVZFoAS+2/AwPQUNqocJ/Z7X4vAoVNcuodpaZR8musqROmphZo8kiOURxsf7GiI90z1oJHLaPkxR3yarYxMJ5C/vnKKIoaIQ49c9OfE4nEI5OPsASUq8kGxWah6OFFNkTCkAfg9GWEovIrkZYA6dYFL3ZpMU8PKMIFADXZGxTBUMM5DUUA/P8jEywBZZ7JzG+KJDW8zntqkSqjBCLFeTKO9AgFVby0rR+hpDV5viGbUR6ZLAFlpsmsN+UCSd2AYqdRHJFO3aNoajqpp1HuTZaAMtdkEiVfe2QYzlMI+Q6NKaPwn0BqjDI8T2hGKIE8uAvFAkoyWQLKbJNJlEqRBhx5A0QeCv/3NmUPVfl7FKSXHdbcoeQteoQCPiHtMyOUsckSUP6wZ8cqDMJAGID3lELhLB07eAEXXQJKkAxdir7/C/VOKbYapLeEVO6ffjyT5UORKDDZRcG+8YB2eofhMABUJMPNU2sBygFhi8KT8T5C/4niAEu7LOHP33JGqfmiJ8cFZW1yBBSByT4KBV1hOM4D+MfUkFrFhcZdE0ExNfLo60kpEGjOu3QjA9TUZhRT8YbORFFO17eJufz1MYvERHBKbO26FZbBtllG2/W2iNxhucdzvpkjpJWY6P+UNAkSE0VJkyAxUZQ0CRITRUmTIDFRlDR5tpRfTRQlyyhKhlGUDKMoGUZRXuzUUW7DQAgE0P/M/e9bqZnoabHSbSS7yUdxVccsDAyD/YH2L8oH2laUzB+b4BMsm5PLLW/l/7Mo2c0nt+i5//Nqf9kfZXP+ikWP+7J+XsH/dVFYMttVPkNDPlELxQylp2MC8qk5gzmc74cT4EeYP+T/e1Ey20+KqH7MrUvTPp00rt0kB2iRazE0ymPkiSQBqDHFuhRKkltE5/7XlBRW+GX8WXai2JDozA1Za2Ry7aA80TCgB2ii/UeO5zpwrx2+HI0psr5jYvc82qwqw6QF/vJO5X/DH+gUZfUirTTOrfl9M73m4D9EaILQdbetbCyBw0CxkyHE0rQyTUUBiN70Y4SX8yeEUlOUJ3oXJ15qxNeFbFhd2u5JoltbEsfmMYD67CxqgYQftONzFYGo6MTNwzX8bc1DGP4hCiMiCDtpBaMT+iNh+fVus5A0La2DlgkYcW4QkLTnzEWK8ZnSUj1n84/LW8+GKF4oiK2GP6cKCnKxMe1l0tThHsFHYBEoywHE4YzRVH0HouGdwd+hF8k3+KkoFXAyQdFtsTFfl9zJXQpTFo4sjzQdgWwoNGH1hZIQiewM/g59zkiSIQoLwc83Xb3dvtg1m1WHQSAK7x2PZiHMwvd/z3sbD5wMdpFQpBT8SskPjk34OIkDTfZNFNPQCs1STAvQzXKUd0aEbkxSrlHi+I/BzVGwpYDf59e3/v6vj9E5KbGJe/sD0K4AxmZ8AJ4BjExH4IYyoPqoSVPzAKOYqFgzBweaMJZxJlbw8uJ0WHv/s5KxP0kZJymNw2pzPzZLcG9VWhK9JIuPL5pTSMqrruJFvwE2T6i1HX5qCctEJUWdv7SgeRlD0PM9+i//BfsLWHGHKQzUEJIyPgTeuCLo+T7byjNsWAm9V0qSEvsqeGNZznlbWUc7rciJpJy7ISjuLOp5W1lKcxPXd8rUmBQ3BeURPW0eYV70iEoW3inGcwxKeRuUHZUFlCP2LRY7egKrh7EibymrMa8hKv9QSlh7obW0pfyxc3Y7asNAFL734saM1sGOUpH0IhEs7CJBbguKipD69/6P03GcMCHABloXVamPdr1jr8UFn86MsR0epudnW+uJTF1TWlcGOEd2HsrjFExaRFC0+iIhFQ/lkQoEP7FKA6XqEBXBb4DyqfBQnIgLfqRyUlPIPBx1C5Tv4y8einMoFkwFxQSoe5wyG4/H3zwUR1Ce2lapoVDuutkpK4SyKjwUV06xUOqMdXQK64Pys22Mz2OjFw/FWfriT4SFakoPlNn4x4j0pYKy8lAcQUFdcAoxuQplXKD+FSgyEWwgIignS2IMboHyEVXeCyXJo00smWvFkLCB6HT1RemrocLfhXJA3euUHBa7HUB8OppSnxSn7JKyWPwHUDjvQqE+fw/Kb9SUV4gEY1J33kIF7FygrnhCDh1KcITSvjhB25FuoYiFZkYZRLafSILSLQ+gmv6W1drKFpRtU0bEdjssKJYJfVSh8xTySuAMyrTJW3GIjVAAsJcsApRuMtkcwNhJAwrRLc0kbTjARoFKwaieN69eLcRIR0ODwjntSdr0ReJOocSwZiS9eM1S9M4y20O2rG0Bu3WWg2JZBvtMMjnX6yVOMlBgF6YyiyDNkAmodbaB1ES7NQ4OCUpgoXSd0r4/GPRD+Ty2eumFklAnhRTbEKZN+qIZG92kr7UytHLAFrSgmqJ1lQ2ViUx/UE4JiAlt3XeM0ltTkAlR6YeiUBtDQlTWyNtQ1pDLbqGXMocE+xEV+szmwQjEEqLBFXrepsLOV189NYWYEJXe9IVMTD7ag1VEUCpTgMolQUmUKTJdKCnUkohncFBOnNJafd0DZdzW7D0oKYQ20Bp/53ElSl+VZLhZQGyh2BpzwSkpbOJKYohQOCcql/a+uFMoAhRBUXNx/jlFStPsQNRQcjAD8ZX0ZdPf4KBYpxCWSzWlH8qPlV19rXqgMFW/mSZ9vdo4aUFpSG1A1lAiWJq2C0Us5qJGqOdicIU+IKsQFEZIbnHKj6+z70afZqseKFJDNE1CBTljQkOeTKtlbQThtElw+2kSzzWGej7NsK/TVAGsj1BCfAWBrcbXMfNSjKbDWhJfTF8muGP1tfo6GhW4OXnA0VXxLhQmowUA6NDGGC7WGC01fXjUOKhk9e4bDK+mG0PcQDEsQdbzNoLhDIzU8NIXPyv0RyGT3vSFerNQbtq6l1I2oTiGUtD/m1hUAc2hCc08GhiQyCmd9HVjTSEmBwPFn9E7PQ6+uvfVU1PIJqhZ8eahuHdKF0pvoS/x7LFElzQqccBDcbzN8tSpKQTl8nGw8cfHjmYeitMNSSr0tzjl4xUdCg/F6S6xAyiowkNxfhzMes9TUC8vNnUdGn9UjYfy16Aw1ltTUGU5mhTl6K0s30pceE3wrxnAcZ++3EGh1ZcJaZfYPwpxh9zXFOuU0517/3zKYxVMOIruTdAucfsyS+Afr3uknp+Dzg3vizckP0z8g6gP05P4wI2OSDr3vpqaEvzBI9vM6z59mARtp+DP+W0Wm7/8lxv8Yu9cdtuGgSi6F0uNWzAg+gAadFGgQIEAXXWXffv//1PzIR5yHNtyQ3MR8MaWaL7EmasRLUaeGQXjnVT/dmxdS7ULLd795/XLDAjeYK5mkLu/pNPIb+3Fe7ERsFI/4L0Yrl9r5TDn6QZO7MUYEtqZ/5W0Yf9KLZj9tUiSvhn75Xd+Vc+y1MssrBNnVnzs5NoF7On4gjbDUY3aaIUbxpqS5e8U5pqNIPQLTuV1lgbNGNseHumbUeyTn1znBUPRlqJoEZH1UJywKfWnVEqk3TH/iHefT8NuMG5YUEhFlBpzMa4FGijVa4fWLRc6SoBi2HAsdAuNdIQYlFbd4tX9JvnN6oOdCJzkis0d/QIrNtBycJlG2YHpr/AmiLigX4ESZvrtjr5ZaMmsTL+ed4VfJcAKrCz1z+u2GAiwIpGVVeJmDXBpw/aYD5xIznTsYoGjYdoDYb81d2xd7om2bUvyOKDE+qmpqu2k9CW0UcIACvrLn8SFEmMNYEGyjmgAKZEQkSwsIxEnaU+BArmOLJ124qgHAWlLER2e1R354HxjcVQX8lE4zCFxV/kl8WITJ7Cy1F6M8MjP5ctKJMYGRsv5IhJfdC7lNE9v6sYPJGspy6hq5OM4oU7MpOdSbxcLQh2Rtieab0nVJTnpwH3lx0r0LUraVKToxySZ4FGD2jHqRnxKSEirLxRBQ4CgWTRUSeO2T9o5Wia42OQckY6eNC11I9dV/iBS4sTUlnLq2FO5nUiWkjqA7O1UQHAoC4WcE2xyM8autSABCACEhFjMK9NEH3ygBX+CjcBOxTxiMQzVdT/58yHsWlmK9uuJw5zylFFlK5Kgz1GUw3ARsUxE0o6XSxQ8CF1wTSO7VlWuj0YUx/SHfigJYOhUr4+KUKok5/aRPyY31doIAxZWiYnJhDP93CztIGTjB0ktmk0l1Mn1MHKhIAFbKdl25VtfMJCcKLqgKxGyrBSeOInJYtBQJnVvqA8aY7+pqLv8MAIr23qICtWRwGzfEsMsll85f2O/aI88xlUacPJjJegXhRX5aEGHtCzTT0UsnzIwqcbO0BKsM4vGzKz27vLb+LYS5xMd+wjHngGwEjDvHu8I791qbT2psPwHKSeWMqNC3AlEhViDpYDWixGxnzAVP6NC3A0sSIqFlnLBwgVuu/41o0KMgPGemxQdqoMl6YgZFWIYnBcMRQcgaKNx2RkVYhScS1+K6+C9L/m6n1EhBsL41VplLIWUZpF4RoUYh/VgWCRW9yn1Hf06o0KMg/FSwh8uhrWvbDcF87HVkXBO3aow0cPJjAoxFuKN+k7MnMJUP5+6HwqJ3lZZkMRSmh9zHewkZRB0VIilCf+Ea89JylhAyhI32uPEJGUkIMWcfWx1uUbKj18b/h5j2pQPfyYpHSzlzGOrAfYSKd/eB2Q3q7/xWDhJ6TWn6N+nZGImKQMBKRhKvUoMJUNJeX4MW5x77232Zpx9RVIkkGL1nAInfS3l8CXgggbxW/g9eMy7juKS8u1Au5aClNpUpBspDx8iPn6dpFwlRf8UwjDR286kfEoeCR8nKfssRd+nwEpPUuLuZ/KQfiCwQ0hFUo7J50wK4YOOqYeFmiUZUqFSJuX5TYQb+sfeufU6CURR+J2mUieFQDWp+FBibb3EmvikPuiLRo3//8+4Z2bTzxmKFaUoyvKcMncZ1tmzuXUvH8F70RVW3bmbn7SUDw8bPLpKSvLE2AXs/t6ovE1uU7UjxSbLo4s8XDTVmU0VWdDyvqn3pvJVdVk2VZvp0/IdKcErRjaJn/8ZSwlwlZSjPa61qY+NsMPphaTuW1LKfJebwpJi6he70uTSq5TCygkNPJE+R9fyvhBQHZOTqXcvXKzWo1eHqJOpw8dm+f5J1wWfkv4UKY9+0lLYFrWL6X1qIkK/LSwpmbOXldO9URWcjS2UglqSp0RweCukWGZoJQVukNP7ZOroCFf43TOuWzh6tqssM4WL5b3C0XsWxKdo0nEgKPc6iFiOxlF3n+roc1P/G5FwwyhGLF+avRkpR7td3S+NoJDURjZ51ibF/f1bY/Klwlzu+igprqUnRfUk/gVesBSFkvJzZ18vHzb40JOUrcntAX1y9JYiLFmvXl0k5cFKO52ElFpcCJYSkOL0JIwMPHU0se7jr0JcjbYaoxcp3nVo2hQavjs77GNSlAldvt6XrrUAUnKWL68nUe4nf/pFZLzw3tc1UrAURT9SRFhQz8Dks/AaEY4QSFEXnllCanfkt1KwMoUrbEhxrbSgVD2Jf4AULKUzhmQ6pE95f/9+XZjCHrnS1JVcqrx1Qg8PHtRmE5IihVVpKndKXFkFiMzWb6QPPiUpfCvn6E+iJyGsTR2dwaJvdkUvOBSVz5SSzt0f+BOb3KxCUnJXH148SkrKxByUFHeOYOqiTPQcoJi+p9eLxw71uqHvfQH0H4IkQBaiO0UBg0x+7QpJURCukAv6dH7INRYgpTOsunIykzIqusU3YWV+HDwyIEXBDUnFbCmjo62Iel1SELx83sC+YnTOzK8YDXCdgqcnuEF49jW/jDceuu8SYykzKSMDS2mfEs+k/BnwkAtaWL5gJZ2/CjEiOPuKwhUmLjuTMjZQhehavhSzKsTYWK+XKZYS3rpPZlWI8YEqBHaCqE3g6WdViBGAKoQ1FJ7Qh8uXYlaFGBULDXbffp4SX6j8nipEG4secg1D4m9ZSxdXVSFghGf0gadP5acrYM7TS0mwvBarPy4iRe76jOzOd1X1JYSmi19WjgB9578WToJ38cLIeME3VCJViDYBqjwQ1bkh0Q0Ai7YOAM38JsjoF2W7lBvoQiNJQxU9qAALKulPp6BVB/v0Dep6zT9QhbgoQBCuXwIXhG3RqELo4QdWCSJS6rjH8Yl3lPIFM2Xe+sFh0gpN6z8mxrDYDBUcde1D/jwQY2EgBELTGpq1/hOGkQRte81/cWclnHA536FeFyiorGdViNuAAJ3rcyDc710KV/RYCqHu1+s5rufNYLVTBERWRy6FcIWYioIIx1ZnIZBF0MRaU6mmfCuy2puGmqWeNr6YiMLab30eOUWhoKlGECAYh73VDaAxVVENQ9Fu0PlrsQcCBMTwvhwGhCWLKL3teayZHmoBDYvMiRQjkA0oicUnmqmvW//Lmv/gcuz61KW0SEdAVMP+aBk8W7h+vudaSGIyg8yfMuITL2OPEgbMiWS5GloujMiRC7lnSyUF7CWp8AOZhVY74pVTGG8IBk62rSmgnFCJvAI19AcDzj/Vlcv+2uMdGAoveCdtU3Fd7IDyEawr0dEiUDnhxAH7GO9/vO9s15fmkTYrGJYVcktlVEENJRcZjXaI9kPOnzDfeJQmjDcxJFFE5e0vLAVrIRS8/qTxNOPp04A46sF6GEf3R5+Abr6G2ShahYxPqbbSdOfBIpy/gE6Y1rDzB0trKIGmTSRqoyWE8EYVQkZbsjToL2YexH2njElpTjPNhj3VXlTDCxNmoto5XvR0QBwhy5k1d0ZBHSJW2kjZaCP2coj5pxEnyzT0KIFUh6Wk09VraH3GZm8CTRDfghLdaJLTDQpZFfRHEGsM6DBL1QHgoHG0U7hcQp62xJxCr7VMWXBYgNAQiU1xwPlLCfoDeJT4yaMFpKjvSTGWZnSGxtUwWcRGlvYDySTqw2a6iYfHiFLaMDcYRGMG21tqqSKNFg6gPbEQKGE/MYwh569oc6K2wnVKECzaL1/LO7MqxK3gLufVAsLlK4yMF3j6xawKcUOgCqFunpt07WDRfJ0+nVUhbgdUIYQTWJEPfIrNBzclZ1WIGwNVCL8scYuF65TW6desCnFzoAqBT4k0ucIbxbMqxGhY2xUMBDckQ/nNWRXi9uDFiVBRkItHC1iZVSFGhBiA+pTW8hVoEKSzKsQIQBViiaEEF4/BA5X5tdUxsV7jVYLrFFQGBbMqxJhAFQJH335DciZlXKScf3m7CMKqz6oQ44MI3rGlBLeIZwGCkQApcBI4ev03W8r4wFLaYdV5ltJNClIQ4ONMynCkJNENSeykm5THdy/g2cuZlAFIgRYsBUP5gX7K3Yv4PJMyYATv8zN6HnBdjUsM/nAYkFWVbzObeHBMpg1HCgJQBPYMTGUoUq6rQuS/HIA7K43AsjL52Hhdjt4lf5qUd68EX54NoApxMhDWD4V5kR2r5J8hRYBPUUffSxXitc9++X1ViOyXlh6o+PdICb4K0aAHKR96qkJ0yziIMASpTiUi//mGgSElUCrKJqYV4UnhLUli3fcj5eW7d3J9Aik/pwqxVVWIYr+yOVNp7Odaissq8boRrHWVqUrJ3neL1RPrQLLCeAmJjREUtruSQijQanJaEWEUo4Tv0fe0lEfOXPqEVa8tC4XVgsiTvAnG7X5dlO7dWynzqXKfNaSYJzu/9BXmUOerVXmwBftjcnxhit3xO1LKg+hIHCSVWa2IJ5Na0iClOzKeYEhS2JZuDTuUIuqwsYmTI8bXSVmhqZ2plRTVgVDxCGc6bqxCB4QUX/PEPNBIuacpKRE5UvAoDSkug6HchhSxFpUVcOvX1rzwR1Ujpe+2Z8mHt+fly27en6OvF5JSYY+QFK3JhMUX09OKUFKgpRXrfmhLQRViaxRZspOjvyn1qNbyK3A5jxJSPItKSlkkKhwRk1Jqz41zUO+nxUtXWPVQ1GZIn4IqRGVO9x1Wbv0qNxdIqV2DPCDlBClvlZQHLVL2fuit14o4iMlMB12BPTGUgR09qhB++VLU++3Z2efmhWsq9EgZ6Fy+TvHy5dp4qFbE2ynJEuh1CrTgU/pZyrt3D3uQ8sRtDvtVc9R25n0ZykGsysL5dME2w9HbIU5KigyTX3b0WpMIp4VqRUxoAWtI6VaFGPDiMVaFyI0Xe3ArkTcbPSXePNi+935fUvfNefkyuVWAyJQUIc4VHLKIFFdTP9gWZieOvhStiP3Ezr7CmDlYSm9SXn7tqQqR2GtB4cf/0WeOFC4ed6744IUhlJS6lGyFRme2sQMck4gUX6NjVJPTimhF8E4uLV/Xb0hafOp96x4ZB4AnIIVP0SxYBQVRzUS1IhAgCGOzAEvKX/I8xZLyP0AtBVO5rJ8ykzIO8ClXSUl7Pg5+fiNSvrF3N0lSwzAUgPcxJizYcA6KU3D/+wCxnA/bhBRT7lQBfvSk/SNZkl8rSWcG+/Onz9v/gO6BpGuKXzuuP5x4FDIFK7Y+v76mgK0g4OuX9SdGU5dV95R47QoxAZO+PLr7Wn+2OgFTnhLLlLUrBDwL+9FbVh0pLvVrA4JH4fQ1LoEbrKxdIR7FuCvEto0PJNeuEE/j40d7crULe/6oYWXtCvEA7ArRPiQeH0gWVtauEM/hfSzijYR+WfW8doV4FHaFwIlMaRclvtgVYp29ZsKuEAdwYhE2tGDlz1Mlbwt/hI8fypKrLihOX8Oq6t/Xxk8/s7Ly5BVIH8vJq2Fl80DSf4YIWizC9i6wFmGbBYuw7TtSup2GjmLgXL/7/doV4iWwK8T3+a0nr+wpS79NLVrWrhAvg10hrIPbbUAwLqt+JoeFrRemwtrTuSI4aU9fbr9yBVbusb/Zu7eK7Y6gquJ9gvdzRrA4caVEprSrrUJOSGlXGd+toL93a/PzrBiM/tJlAKsm0xlWtDeyBZjPQfRZyJuHgTClU0f1aze4gwiMydEZ8VencdIQ0q3gnRCT5QpwwytQGr207Njk6356d6qETO2P4ilinKZPFxEdCIkeA5uz1oyjdsW3xC821QqswCZTCk2WK7TcPQB/VBVuIBqgK0hQVgIwnxomYHb8COl2GbIRnEyJLytdruR9YT4kSb8rRLMMiG+PLisNMznYdahdmIvOfO55QCtAXU1lhC6wt0snNPgRnnhl3Z0kGBumx5/zmCg1JWRKtLisuDf22rNC2KyNO9HilpqjIUKKfO0+lRrJmIQ4Ut1/FmkVQ4Hr0RSmQzwOjDKiYUb8hoSgxK/nf7VVxzZ8s3eGP7PGDQoDbvYOh9LhwtnGI7vB7GbcrVKx2OjuktXNUbQUuZ2RzJlqRJsB6WqrbIVq38bp2hbucIaRyndrBBE13L0QIkeCAaQcVbQU7IV3zpQqwrX5BAdMJ1R2tJrzQbfEBmGXEYrG09i7LRAD9qNJglaMjdbD0WxvRn6AgZFScqRdlzhZmBgtdXMP1xcl0PEi9OYd05sti0lBj0pYmRx/mdiUfI/HzAGZ0lHi2mKYnI93Y+YcTRistSIczdGVqsI5nIBSLZOubcUFZuub0VvGqm2mjIbPlMNdqsYuChiiexl/eb+Ln0i7cFGz4bNv9G6K2/uw+GkIMWTrY+LdABKkMwt+cKseoo1+AVkjVIK5R8VQrY+mEdvHq52Ht8ePRZApduoYvqeUVpLAkcagVNQ1CimMEwmaGtlRbJS56L8zIIJLcGNK/IAShKThMUupl2SJt4VXAiXlAEiRPyE9zewt/ukPwHbXHf8kzEjK0ZmJ9wM8MMVvscnhuXhZ/BzOZ54c76nNlPMt56NAL4rqihqYukaj0JugTPyiSIdf+oANrfrG3j7WF8a/1buqfFSgkgKHsEQJ654CXFsjxlENiKDQz4eyZjFcm2dLgUXdERGHYPRZv8LU+HmRzX26IqVyhw0j/8y00M1aN5+mSANt45Kx36TWUu11ObExEp0unxjZjMHVCNiMUY4yn6fET5WlHJ4czUjpQKqoC5c9brTsxalRuEW+IALXYVJD1WCbIQWAn2Kl8wSFVb1aF4FZOWRw3p4QkWCYufGfQtk91kCKi00mEyMWs84BwvRT3sxvHA1TSuVldmIUExg/aOHDGSqTOPXSHMWe2zMFZUGoqIifzRnx15zwyhj5babQ3iol9NjatDElSblrungNOk0+xVBXoC8Rfm6KdpPISn/SpFWlmozR9ML4XVNuSUG9tGQYa3wD7oxz6GPVCYPs19Imgh7XEtWLoRErrkHAfAszaJ8SP56oZOWRFNQZ6g/gBNVBkDfw2TWSmnITNumm70qGAPgcOFM9F3/K6L3LlGur1yb03CP90RAa3uyB5nvFR+P37XEghXDKf+Vfan9j5w5yIASBIIruuyK4Ycv9jzkJLiY6JjgbUtXWu4H5aTQGKK3V+AfJU2I71779+kKIRum9hKL5pCAgmUQ4SiCmyxfLWL8oyt2LnnOtfU+UqyNKCo5CyFGY1DLsve9lUG+TIUrrF0q3Ev9yFEoZopTr8lVDW4Yog1/0lByFkKMQchRCyaP41/1K2OIkVZTammSUyBxFVepJkeUofDwpjByFjyeFkaPw8aQQehgFvvN5oe1RFDjKQk/OPAZkd+OpwnyDdwCqG1c1fQ8AHTwpBEaTSZRAyO7x/rRzbysMwkAQhicnDxHf/3W7gcqWEoqUghM7X++2BYs/0ZvoiDoXJo/iBt7kPZ7eqUbTzVeH3ek9En9e0/WjHI+p+2ud/BvnPz89cz+YmS9mPAf2l62cieJ3+/3p9S/uB5/t7zPjs+3jbOvO/uLAW3+l3GrjxH0oCiFFIaQohBSFkKIQUhRCikJIUQgBSJirEJmQ7JOrEFmQELFWIVIQEaClwiQjBYSIMlUhMRVEi9KqaK2QyK2JRWlVUBatlsvNeYU1aVFMTBAOMRxRWhZ1uVxKlqR5AIswd8iFiqq+AAAAAElFTkSuQmCC",
- "description": "Display quick links to the platform or documentation.",
+ "description": "Configure and display links to documentation and any other resources.",
"descriptor": {
"type": "static",
"sizeX": 7.5,
@@ -24,27 +43,65 @@
"settingsSchema": "",
"dataKeySettingsSchema": "",
"settingsDirective": "tb-doc-links-widget-settings",
- "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"columns\":3},\"title\":\"Documentation links\",\"dropShadow\":true}"
+ "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"columns\":3},\"title\":\"Documentation links\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false}"
}
},
{
- "alias": "getting_started",
- "name": "Getting started",
- "image": "data:image/png;base64,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",
- "description": "Gettings started instruction.",
+ "alias": "usage_info",
+ "name": "Usage info",
+ "image": "data:image/png;base64,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",
+ "description": "Display statistics about platform entities and API usage.",
"descriptor": {
"type": "static",
"sizeX": 7.5,
"sizeY": 6.5,
"resources": [],
- "templateHtml": "\n\n",
+ "templateHtml": "\n\n",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n}",
"settingsSchema": "",
"dataKeySettingsSchema": "",
"settingsDirective": "",
- "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Getting started\",\"dropShadow\":true}"
+ "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Usage info\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false}"
+ }
+ },
+ {
+ "alias": "quick_links",
+ "name": "Quick links",
+ "image": "data:image/png;base64,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",
+ "description": "Configure and display quick links to the platform's UI components.",
+ "descriptor": {
+ "type": "static",
+ "sizeX": 7.5,
+ "sizeY": 3,
+ "resources": [],
+ "templateHtml": "\n\n",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n}",
+ "settingsSchema": "",
+ "dataKeySettingsSchema": "",
+ "settingsDirective": "tb-quick-links-widget-settings",
+ "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"columns\":3},\"title\":\"Quick links\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false}"
+ }
+ },
+ {
+ "alias": "dashboards",
+ "name": "Dashboards",
+ "image": "data:image/png;base64,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",
+ "description": "Display and manage the 'last viewed' or 'starred' dashboards.",
+ "descriptor": {
+ "type": "static",
+ "sizeX": 7.5,
+ "sizeY": 6.5,
+ "resources": [],
+ "templateHtml": "\n\n",
+ "templateCss": "",
+ "controllerScript": "self.onInit = function() {\n}",
+ "settingsSchema": "",
+ "dataKeySettingsSchema": "",
+ "settingsDirective": "",
+ "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Dashboards\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showLegend\":false}"
}
}
]
-}
+}
\ No newline at end of file
diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql
index 995b43967f..aa53f98b74 100644
--- a/application/src/main/data/upgrade/3.4.4/schema_update.sql
+++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql
@@ -177,256 +177,6 @@ CREATE TABLE IF NOT EXISTS user_settings (
CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE,
CONSTRAINT user_settings_pkey PRIMARY KEY (user_id, type)
);
--- ALARM INFO VIEW
-
-DROP VIEW IF EXISTS alarm_info CASCADE;
-CREATE VIEW alarm_info AS
-SELECT a.*,
-(CASE WHEN a.acknowledged AND a.cleared THEN 'CLEARED_ACK'
- WHEN NOT a.acknowledged AND a.cleared THEN 'CLEARED_UNACK'
- WHEN a.acknowledged AND NOT a.cleared THEN 'ACTIVE_ACK'
- WHEN NOT a.acknowledged AND NOT a.cleared THEN 'ACTIVE_UNACK' END) as status,
-COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id)
- WHEN a.originator_type = 1 THEN (select title from customer where id = a.originator_id)
- WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id)
- WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id)
- WHEN a.originator_type = 4 THEN (select name from asset where id = a.originator_id)
- WHEN a.originator_type = 5 THEN (select name from device where id = a.originator_id)
- WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id)
- WHEN a.originator_type = 13 THEN (select name from device_profile where id = a.originator_id)
- WHEN a.originator_type = 14 THEN (select name from asset_profile where id = a.originator_id)
- WHEN a.originator_type = 18 THEN (select name from edge where id = a.originator_id) END
- , 'Deleted') originator_name,
-COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id)
- WHEN a.originator_type = 1 THEN (select COALESCE(NULLIF(title, ''), email) from customer where id = a.originator_id)
- WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id)
- WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id)
- WHEN a.originator_type = 4 THEN (select COALESCE(NULLIF(label, ''), name) from asset where id = a.originator_id)
- WHEN a.originator_type = 5 THEN (select COALESCE(NULLIF(label, ''), name) from device where id = a.originator_id)
- WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id)
- WHEN a.originator_type = 13 THEN (select name from device_profile where id = a.originator_id)
- WHEN a.originator_type = 14 THEN (select name from asset_profile where id = a.originator_id)
- WHEN a.originator_type = 18 THEN (select COALESCE(NULLIF(label, ''), name) from edge where id = a.originator_id) END
- , 'Deleted') as originator_label,
-u.first_name as assignee_first_name, u.last_name as assignee_last_name, u.email as assignee_email
-FROM alarm a
-LEFT JOIN tb_user u ON u.id = a.assignee_id;
-
--- ALARM INFO VIEW END
-
--- ALARM FUNCTIONS START
-
-DROP FUNCTION IF EXISTS create_or_update_active_alarm;
-CREATE OR REPLACE FUNCTION create_or_update_active_alarm(
- t_id uuid, c_id uuid, a_id uuid, a_created_ts bigint,
- a_o_id uuid, a_o_type integer, a_type varchar,
- a_severity varchar, a_start_ts bigint, a_end_ts bigint,
- a_details varchar,
- a_propagate boolean, a_propagate_to_owner boolean,
- a_propagate_to_tenant boolean, a_propagation_types varchar,
- a_creation_enabled boolean)
- RETURNS varchar
- LANGUAGE plpgsql
-AS
-$$
-DECLARE
- null_id constant uuid = '13814000-1dd2-11b2-8080-808080808080'::uuid;
- existing alarm;
- result alarm_info;
- row_count integer;
-BEGIN
- SELECT * INTO existing FROM alarm a WHERE a.originator_id = a_o_id AND a.type = a_type AND a.cleared = false ORDER BY a.start_ts DESC FOR UPDATE;
- IF existing.id IS NULL THEN
- IF a_creation_enabled = FALSE THEN
- RETURN json_build_object('success', false)::text;
- END IF;
- IF c_id = null_id THEN
- c_id = NULL;
- end if;
- INSERT INTO alarm
- (tenant_id, customer_id, id, created_time,
- originator_id, originator_type, type,
- severity, start_ts, end_ts,
- additional_info,
- propagate, propagate_to_owner, propagate_to_tenant, propagate_relation_types,
- acknowledged, ack_ts,
- cleared, clear_ts,
- assignee_id, assign_ts)
- VALUES
- (t_id, c_id, a_id, a_created_ts,
- a_o_id, a_o_type, a_type,
- a_severity, a_start_ts, a_end_ts,
- a_details,
- a_propagate, a_propagate_to_owner, a_propagate_to_tenant, a_propagation_types,
- false, 0, false, 0, NULL, 0);
- SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
- RETURN json_build_object('success', true, 'created', true, 'modified', true, 'alarm', row_to_json(result))::text;
- ELSE
- UPDATE alarm a
- SET severity = a_severity,
- start_ts = a_start_ts,
- end_ts = a_end_ts,
- additional_info = a_details,
- propagate = a_propagate,
- propagate_to_owner = a_propagate_to_owner,
- propagate_to_tenant = a_propagate_to_tenant,
- propagate_relation_types = a_propagation_types
- WHERE a.id = existing.id
- AND a.tenant_id = t_id
- AND (severity != a_severity OR start_ts != a_start_ts OR end_ts != a_end_ts OR additional_info != a_details
- OR propagate != a_propagate OR propagate_to_owner != a_propagate_to_owner OR
- propagate_to_tenant != a_propagate_to_tenant OR propagate_relation_types != a_propagation_types);
- GET DIAGNOSTICS row_count = ROW_COUNT;
- SELECT * INTO result FROM alarm_info a WHERE a.id = existing.id AND a.tenant_id = t_id;
- IF row_count > 0 THEN
- RETURN json_build_object('success', true, 'modified', true, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text;
- ELSE
- RETURN json_build_object('success', true, 'modified', false, 'alarm', row_to_json(result))::text;
- END IF;
- END IF;
-END
-$$;
-
-DROP FUNCTION IF EXISTS update_alarm;
-CREATE OR REPLACE FUNCTION update_alarm(t_id uuid, a_id uuid, a_severity varchar, a_start_ts bigint, a_end_ts bigint,
- a_details varchar,
- a_propagate boolean, a_propagate_to_owner boolean,
- a_propagate_to_tenant boolean, a_propagation_types varchar)
- RETURNS varchar
- LANGUAGE plpgsql
-AS
-$$
-DECLARE
- existing alarm;
- result alarm_info;
- row_count integer;
-BEGIN
- SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
- IF existing IS NULL THEN
- RETURN json_build_object('success', false)::text;
- END IF;
- UPDATE alarm a
- SET severity = a_severity,
- start_ts = a_start_ts,
- end_ts = a_end_ts,
- additional_info = a_details,
- propagate = a_propagate,
- propagate_to_owner = a_propagate_to_owner,
- propagate_to_tenant = a_propagate_to_tenant,
- propagate_relation_types = a_propagation_types
- WHERE a.id = a_id
- AND a.tenant_id = t_id
- AND (severity != a_severity OR start_ts != a_start_ts OR end_ts != a_end_ts OR additional_info != a_details
- OR propagate != a_propagate OR propagate_to_owner != a_propagate_to_owner OR
- propagate_to_tenant != a_propagate_to_tenant OR propagate_relation_types != a_propagation_types);
- GET DIAGNOSTICS row_count = ROW_COUNT;
- SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
- IF row_count > 0 THEN
- RETURN json_build_object('success', true, 'modified', row_count > 0, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text;
- ELSE
- RETURN json_build_object('success', true, 'modified', row_count > 0, 'alarm', row_to_json(result))::text;
- END IF;
-END
-$$;
-
-DROP FUNCTION IF EXISTS acknowledge_alarm;
-CREATE OR REPLACE FUNCTION acknowledge_alarm(t_id uuid, a_id uuid, a_ts bigint)
- RETURNS varchar
- LANGUAGE plpgsql
-AS
-$$
-DECLARE
- existing alarm;
- result alarm_info;
- modified boolean = FALSE;
-BEGIN
- SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
- IF existing IS NULL THEN
- RETURN json_build_object('success', false)::text;
- END IF;
-
- IF NOT (existing.acknowledged) THEN
- modified = TRUE;
- UPDATE alarm a SET acknowledged = true, ack_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
- END IF;
- SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
- RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text;
-END
-$$;
-
-DROP FUNCTION IF EXISTS clear_alarm;
-CREATE OR REPLACE FUNCTION clear_alarm(t_id uuid, a_id uuid, a_ts bigint, a_details varchar)
- RETURNS varchar
- LANGUAGE plpgsql
-AS
-$$
-DECLARE
- existing alarm;
- result alarm_info;
- cleared boolean = FALSE;
-BEGIN
- SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
- IF existing IS NULL THEN
- RETURN json_build_object('success', false)::text;
- END IF;
- IF NOT(existing.cleared) THEN
- cleared = TRUE;
- UPDATE alarm a SET cleared = true, clear_ts = a_ts, additional_info = a_details WHERE a.id = a_id AND a.tenant_id = t_id;
- END IF;
- SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
- RETURN json_build_object('success', true, 'cleared', cleared, 'alarm', row_to_json(result))::text;
-END
-$$;
-
-DROP FUNCTION IF EXISTS assign_alarm;
-CREATE OR REPLACE FUNCTION assign_alarm(t_id uuid, a_id uuid, u_id uuid, a_ts bigint)
- RETURNS varchar
- LANGUAGE plpgsql
-AS
-$$
-DECLARE
- existing alarm;
- result alarm_info;
- modified boolean = FALSE;
-BEGIN
- SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
- IF existing IS NULL THEN
- RETURN json_build_object('success', false)::text;
- END IF;
- IF existing.assignee_id IS NULL OR existing.assignee_id != u_id THEN
- modified = TRUE;
- UPDATE alarm a SET assignee_id = u_id, assign_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
- END IF;
- SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
- RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
-END
-$$;
-
-DROP FUNCTION IF EXISTS unassign_alarm;
-CREATE OR REPLACE FUNCTION unassign_alarm(t_id uuid, a_id uuid, a_ts bigint)
- RETURNS varchar
- LANGUAGE plpgsql
-AS
-$$
-DECLARE
- existing alarm;
- result alarm_info;
- modified boolean = FALSE;
-BEGIN
- SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE;
- IF existing IS NULL THEN
- RETURN json_build_object('success', false)::text;
- END IF;
- IF existing.assignee_id IS NOT NULL THEN
- modified = TRUE;
- UPDATE alarm a SET assignee_id = NULL, assign_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id;
- END IF;
- SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id;
- RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
-END
-$$;
-
--- ALARM FUNCTIONS END
-- TTL DROP PARTITIONS FUNCTIONS UPDATE START
@@ -614,3 +364,16 @@ END
$$;
-- TTL DROP PARTITIONS FUNCTIONS UPDATE END
+
+-- RULE NODE SINGLETON MODE SUPPORT
+
+ALTER TABLE rule_node ADD COLUMN IF NOT EXISTS singleton_mode bool DEFAULT false;
+
+UPDATE rule_node SET singleton_mode = true WHERE type IN ('org.thingsboard.rule.engine.mqtt.azure.TbAzureIotHubNode', 'org.thingsboard.rule.engine.mqtt.TbMqttNode');
+
+ALTER TABLE component_descriptor ADD COLUMN IF NOT EXISTS clustering_mode varchar(255) DEFAULT 'ENABLED';
+
+UPDATE component_descriptor SET clustering_mode = 'USER_PREFERENCE' WHERE clazz = 'org.thingsboard.rule.engine.mqtt.TbMqttNode';
+
+UPDATE component_descriptor SET clustering_mode = 'SINGLETON' WHERE clazz = 'org.thingsboard.rule.engine.mqtt.azure.TbAzureIotHubNode';
+
diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
index e9d89ad6a2..515c635e68 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
@@ -87,6 +87,7 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
+import org.thingsboard.server.queue.discovery.DiscoveryService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.notification.NotificationRuleProcessor;
@@ -179,6 +180,10 @@ public class ActorSystemContext {
@Setter
private ComponentDiscoveryService componentService;
+ @Autowired
+ @Getter
+ private DiscoveryService discoveryService;
+
@Autowired
@Getter
private DataDecodingEncodingService encodingService;
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
index 0c3589a077..b8c890b75e 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
@@ -21,7 +21,6 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
-import com.google.protobuf.InvalidProtocolBufferException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.thingsboard.common.util.JacksonUtil;
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfoMetaData.java b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfoMetaData.java
index 2a84a628ef..7dd7525561 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfoMetaData.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfoMetaData.java
@@ -16,7 +16,6 @@
package org.thingsboard.server.actors.device;
import lombok.Data;
-import org.thingsboard.server.gen.transport.TransportProtos.SessionType;
/**
* @author Andrew Shvayka
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
index 0e1e25bd27..4bd082ee38 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
@@ -23,6 +23,7 @@ import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
+import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
@@ -49,7 +50,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.common.MultipleTbQueueTbMsgCallbackWrapper;
import org.thingsboard.server.queue.common.TbQueueTbMsgCallbackWrapper;
-import org.thingsboard.server.cluster.TbClusterService;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainInputMsg.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainInputMsg.java
index 3f60390f63..e0d88efef0 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainInputMsg.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainInputMsg.java
@@ -16,10 +16,8 @@
package org.thingsboard.server.actors.ruleChain;
import lombok.EqualsAndHashCode;
-import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.id.RuleChainId;
-import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbMsg;
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java
index abe8ef1e15..bb81a9351e 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java
@@ -20,11 +20,7 @@ import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.msg.MsgType;
-import org.thingsboard.server.common.msg.TbActorStopReason;
import org.thingsboard.server.common.msg.TbMsg;
-import org.thingsboard.server.common.msg.TbRuleEngineActorMsg;
-import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
-import org.thingsboard.server.common.msg.queue.RuleEngineException;
/**
* Created by ashvayka on 19.03.18.
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
index a6049ce8f2..0c43b212a0 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
@@ -31,7 +31,10 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.RuleNodeException;
import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
+import org.thingsboard.server.common.msg.queue.ServiceType;
+import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
+import org.thingsboard.server.gen.transport.TransportProtos;
/**
* @author Andrew Shvayka
@@ -39,11 +42,10 @@ import org.thingsboard.server.common.stats.TbApiUsageReportClient;
public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor {
private final String ruleChainName;
- private final TbActorRef self;
private final TbApiUsageReportClient apiUsageClient;
+ private final DefaultTbContext defaultCtx;
private RuleNode ruleNode;
private TbNode tbNode;
- private DefaultTbContext defaultCtx;
private RuleNodeInfo info;
RuleNodeActorMessageProcessor(TenantId tenantId, String ruleChainName, RuleNodeId ruleNodeId, ActorSystemContext systemContext
@@ -51,7 +53,6 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor VcUtils.checkBranchName(config.getBranch()));
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.WRITE);
return autoCommitSettingsService.save(getTenantId(), settings);
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
index 59ed65c6c9..44263d19bd 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
@@ -30,10 +30,10 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.StringUtils;
-import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
+import org.thingsboard.server.common.data.alarm.AlarmQueryV2;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
@@ -47,11 +47,13 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmService;
-import org.thingsboard.server.service.security.model.SecurityUser;
-import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
@@ -85,9 +87,15 @@ public class AlarmController extends BaseController {
private static final String ALARM_SECURITY_CHECK = "If the user has the authority of 'Tenant Administrator', the server checks that the originator of alarm is owned by the same tenant. " +
"If the user has the authority of 'Customer User', the server checks that the originator of alarm belongs to the customer. ";
private static final String ALARM_QUERY_SEARCH_STATUS_DESCRIPTION = "A string value representing one of the AlarmSearchStatus enumeration value";
+
+ private static final String ALARM_QUERY_SEARCH_STATUS_ARRAY_DESCRIPTION = "A list of string values separated by comma ',' representing one of the AlarmSearchStatus enumeration value";
private static final String ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES = "ANY, ACTIVE, CLEARED, ACK, UNACK";
private static final String ALARM_QUERY_STATUS_DESCRIPTION = "A string value representing one of the AlarmStatus enumeration value";
private static final String ALARM_QUERY_STATUS_ALLOWABLE_VALUES = "ACTIVE_UNACK, ACTIVE_ACK, CLEARED_UNACK, CLEARED_ACK";
+ private static final String ALARM_QUERY_SEVERITY_ARRAY_DESCRIPTION = "A list of string values separated by comma ',' representing one of the AlarmSeverity enumeration value";
+ private static final String ALARM_QUERY_SEVERITY_ALLOWABLE_VALUES = "CRITICAL, MAJOR, MINOR, WARNING, INDETERMINATE";
+
+ private static final String ALARM_QUERY_TYPE_ARRAY_DESCRIPTION = "A list of string values separated by comma ',' representing alarm types";
private static final String ALARM_QUERY_ASSIGNEE_DESCRIPTION = "A string value representing the assignee user id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
private static final String ALARM_QUERY_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on of next alarm fields: type, severity or status";
private static final String ALARM_QUERY_START_TIME_DESCRIPTION = "The start timestamp in milliseconds of the search time range over the Alarm class field: 'createdTime'.";
@@ -331,6 +339,132 @@ public class AlarmController extends BaseController {
}
}
+ @ApiOperation(value = "Get Alarms (getAlarmsV2)",
+ notes = "Returns a page of alarms for the selected entity. " +
+ PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE)
+ @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/v2/alarm/{entityType}/{entityId}", method = RequestMethod.GET)
+ @ResponseBody
+ public PageData getAlarmsV2(
+ @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE")
+ @PathVariable(ENTITY_TYPE) String strEntityType,
+ @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true)
+ @PathVariable(ENTITY_ID) String strEntityId,
+ @ApiParam(value = ALARM_QUERY_SEARCH_STATUS_ARRAY_DESCRIPTION, allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES)
+ @RequestParam(required = false) String[] statusList,
+ @ApiParam(value = ALARM_QUERY_SEVERITY_ARRAY_DESCRIPTION, allowableValues = ALARM_QUERY_SEVERITY_ALLOWABLE_VALUES)
+ @RequestParam(required = false) String[] severityList,
+ @ApiParam(value = ALARM_QUERY_TYPE_ARRAY_DESCRIPTION)
+ @RequestParam(required = false) String[] typeList,
+ @ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION)
+ @RequestParam(required = false) String assigneeId,
+ @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
+ @RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
+ @RequestParam int page,
+ @ApiParam(value = ALARM_QUERY_TEXT_SEARCH_DESCRIPTION)
+ @RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ALARM_SORT_PROPERTY_ALLOWABLE_VALUES)
+ @RequestParam(required = false) String sortProperty,
+ @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
+ @RequestParam(required = false) String sortOrder,
+ @ApiParam(value = ALARM_QUERY_START_TIME_DESCRIPTION)
+ @RequestParam(required = false) Long startTime,
+ @ApiParam(value = ALARM_QUERY_END_TIME_DESCRIPTION)
+ @RequestParam(required = false) Long endTime
+ ) throws ThingsboardException, ExecutionException, InterruptedException {
+ checkParameter("EntityId", strEntityId);
+ checkParameter("EntityType", strEntityType);
+ EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
+ checkEntityId(entityId, Operation.READ);
+ List alarmStatusList = new ArrayList<>();
+ if (statusList != null) {
+ for (String strStatus : statusList) {
+ if (!StringUtils.isEmpty(strStatus)) {
+ alarmStatusList.add(AlarmSearchStatus.valueOf(strStatus));
+ }
+ }
+ }
+ List alarmSeverityList = new ArrayList<>();
+ if (severityList != null) {
+ for (String strSeverity : severityList) {
+ if (!StringUtils.isEmpty(strSeverity)) {
+ alarmSeverityList.add(AlarmSeverity.valueOf(strSeverity));
+ }
+ }
+ }
+ List alarmTypeList = typeList != null ? Arrays.asList(typeList) : Collections.emptyList();
+ UserId assigneeUserId = null;
+ if (assigneeId != null) {
+ assigneeUserId = new UserId(UUID.fromString(assigneeId));
+ }
+ TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
+
+ return checkNotNull(alarmService.findAlarmsV2(getCurrentUser().getTenantId(), new AlarmQueryV2(entityId, pageLink, alarmTypeList, alarmStatusList, alarmSeverityList, assigneeUserId)).get());
+ }
+
+ @ApiOperation(value = "Get All Alarms (getAllAlarmsV2)",
+ notes = "Returns a page of alarms that belongs to the current user owner. " +
+ "If the user has the authority of 'Tenant Administrator', the server returns alarms that belongs to the tenant of current user. " +
+ "If the user has the authority of 'Customer User', the server returns alarms that belongs to the customer of current user. " +
+ PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE)
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/v2/alarms", method = RequestMethod.GET)
+ @ResponseBody
+ public PageData getAllAlarmsV2(
+ @ApiParam(value = ALARM_QUERY_SEARCH_STATUS_ARRAY_DESCRIPTION, allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES)
+ @RequestParam(required = false) String[] statusList,
+ @ApiParam(value = ALARM_QUERY_SEVERITY_ARRAY_DESCRIPTION, allowableValues = ALARM_QUERY_SEVERITY_ALLOWABLE_VALUES)
+ @RequestParam(required = false) String[] severityList,
+ @ApiParam(value = ALARM_QUERY_TYPE_ARRAY_DESCRIPTION)
+ @RequestParam(required = false) String[] typeList,
+ @ApiParam(value = ALARM_QUERY_ASSIGNEE_DESCRIPTION)
+ @RequestParam(required = false) String assigneeId,
+ @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
+ @RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
+ @RequestParam int page,
+ @ApiParam(value = ALARM_QUERY_TEXT_SEARCH_DESCRIPTION)
+ @RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ALARM_SORT_PROPERTY_ALLOWABLE_VALUES)
+ @RequestParam(required = false) String sortProperty,
+ @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
+ @RequestParam(required = false) String sortOrder,
+ @ApiParam(value = ALARM_QUERY_START_TIME_DESCRIPTION)
+ @RequestParam(required = false) Long startTime,
+ @ApiParam(value = ALARM_QUERY_END_TIME_DESCRIPTION)
+ @RequestParam(required = false) Long endTime
+ ) throws ThingsboardException, ExecutionException, InterruptedException {
+ List alarmStatusList = new ArrayList<>();
+ if (statusList != null) {
+ for (String strStatus : statusList) {
+ if (!StringUtils.isEmpty(strStatus)) {
+ alarmStatusList.add(AlarmSearchStatus.valueOf(strStatus));
+ }
+ }
+ }
+ List alarmSeverityList = new ArrayList<>();
+ if (severityList != null) {
+ for (String strSeverity : severityList) {
+ if (!StringUtils.isEmpty(strSeverity)) {
+ alarmSeverityList.add(AlarmSeverity.valueOf(strSeverity));
+ }
+ }
+ }
+ List alarmTypeList = typeList != null ? Arrays.asList(typeList) : Collections.emptyList();
+ UserId assigneeUserId = null;
+ if (assigneeId != null) {
+ assigneeUserId = new UserId(UUID.fromString(assigneeId));
+ }
+ TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
+
+ if (getCurrentUser().isCustomerUser()) {
+ return checkNotNull(alarmService.findCustomerAlarmsV2(getCurrentUser().getTenantId(), getCurrentUser().getCustomerId(), new AlarmQueryV2(null, pageLink, alarmTypeList, alarmStatusList, alarmSeverityList, assigneeUserId)).get());
+ } else {
+ return checkNotNull(alarmService.findAlarmsV2(getCurrentUser().getTenantId(), new AlarmQueryV2(null, pageLink, alarmTypeList, alarmStatusList, alarmSeverityList, assigneeUserId)).get());
+ }
+ }
+
@ApiOperation(value = "Get Highest Alarm Severity (getHighestAlarmSeverity)",
notes = "Search the alarms by originator ('entityType' and entityId') and optional 'status' or 'searchStatus' filters and returns the highest AlarmSeverity(CRITICAL, MAJOR, MINOR, WARNING or INDETERMINATE). " +
"Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH
diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java b/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java
index ebae4169ce..47a7eede4b 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java
@@ -19,7 +19,6 @@ import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
@@ -36,7 +35,6 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
-import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.asset.profile.TbAssetProfileService;
import org.thingsboard.server.service.security.permission.Operation;
diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java
index 32b997c21f..f3a3a2397c 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java
@@ -46,6 +46,7 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent;
import org.thingsboard.server.common.data.security.event.UserSessionInvalidationEvent;
+import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.common.data.security.model.SecuritySettings;
import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
@@ -54,7 +55,6 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails;
import org.thingsboard.server.service.security.model.ActivateUserRequest;
import org.thingsboard.server.service.security.model.ChangePasswordRequest;
-import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.service.security.model.ResetPasswordEmailRequest;
import org.thingsboard.server.service.security.model.ResetPasswordRequest;
import org.thingsboard.server.service.security.model.SecurityUser;
diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
index a060335713..f425bde86c 100644
--- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
+++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
@@ -66,6 +66,7 @@ public class ControllerConstants {
protected static final String PAGE_SIZE_DESCRIPTION = "Maximum amount of entities in a one page";
protected static final String PAGE_NUMBER_DESCRIPTION = "Sequence number of page starting from 0";
protected static final String DEVICE_TYPE_DESCRIPTION = "Device type as the name of the device profile";
+ protected static final String DEVICE_ACTIVE_PARAM_DESCRIPTION = "A boolean value representing the device active flag.";
protected static final String ENTITY_VIEW_TYPE_DESCRIPTION = "Entity View type";
protected static final String ASSET_TYPE_DESCRIPTION = "Asset type";
protected static final String EDGE_TYPE_DESCRIPTION = "A string value representing the edge type. For example, 'default'";
diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
index 0e6e19ad8d..bb34f6d5b2 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
@@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceInfo;
+import org.thingsboard.server.common.data.DeviceInfoFilter;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest;
import org.thingsboard.server.common.data.Tenant;
@@ -59,6 +60,8 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest;
+import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult;
import org.thingsboard.server.dao.device.claim.ClaimResponse;
import org.thingsboard.server.dao.device.claim.ClaimResult;
import org.thingsboard.server.dao.device.claim.ReclaimResult;
@@ -66,8 +69,6 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.device.DeviceBulkImportService;
-import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest;
-import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult;
import org.thingsboard.server.service.entitiy.device.TbDeviceService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
@@ -83,6 +84,7 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ACTIVE_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID;
import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.DEVICE_INFO_DESCRIPTION;
@@ -333,6 +335,8 @@ public class DeviceController extends BaseController {
@RequestParam(required = false) String type,
@ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION)
@RequestParam(required = false) String deviceProfileId,
+ @ApiParam(value = DEVICE_ACTIVE_PARAM_DESCRIPTION)
+ @RequestParam(required = false) Boolean active,
@ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES)
@@ -342,14 +346,15 @@ public class DeviceController extends BaseController {
) throws ThingsboardException {
TenantId tenantId = getCurrentUser().getTenantId();
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
+ DeviceInfoFilter.DeviceInfoFilterBuilder filter = DeviceInfoFilter.builder();
+ filter.tenantId(tenantId);
+ filter.active(active);
if (type != null && type.trim().length() > 0) {
- return checkNotNull(deviceService.findDeviceInfosByTenantIdAndType(tenantId, type, pageLink));
+ filter.type(type);
} else if (deviceProfileId != null && deviceProfileId.length() > 0) {
- DeviceProfileId profileId = new DeviceProfileId(toUUID(deviceProfileId));
- return checkNotNull(deviceService.findDeviceInfosByTenantIdAndDeviceProfileId(tenantId, profileId, pageLink));
- } else {
- return checkNotNull(deviceService.findDeviceInfosByTenantId(tenantId, pageLink));
+ filter.deviceProfileId(new DeviceProfileId(toUUID(deviceProfileId)));
}
+ return checkNotNull(deviceService.findDeviceInfosByFilter(filter.build(), pageLink));
}
@ApiOperation(value = "Get Tenant Device (getTenantDevice)",
@@ -415,6 +420,8 @@ public class DeviceController extends BaseController {
@RequestParam(required = false) String type,
@ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION)
@RequestParam(required = false) String deviceProfileId,
+ @ApiParam(value = DEVICE_ACTIVE_PARAM_DESCRIPTION)
+ @RequestParam(required = false) Boolean active,
@ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES)
@@ -426,14 +433,16 @@ public class DeviceController extends BaseController {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId, Operation.READ);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
+ DeviceInfoFilter.DeviceInfoFilterBuilder filter = DeviceInfoFilter.builder();
+ filter.tenantId(tenantId);
+ filter.customerId(customerId);
+ filter.active(active);
if (type != null && type.trim().length() > 0) {
- return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
+ filter.type(type);
} else if (deviceProfileId != null && deviceProfileId.length() > 0) {
- DeviceProfileId profileId = new DeviceProfileId(toUUID(deviceProfileId));
- return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId(tenantId, customerId, profileId, pageLink));
- } else {
- return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink));
+ filter.deviceProfileId(new DeviceProfileId(toUUID(deviceProfileId)));
}
+ return checkNotNull(deviceService.findDeviceInfosByFilter(filter.build(), pageLink));
}
@ApiOperation(value = "Get Devices By Ids (getDevicesByIds)",
@@ -671,7 +680,7 @@ public class DeviceController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/{edgeId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
- public PageData getEdgeDevices(
+ public PageData getEdgeDevices(
@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(EDGE_ID) String strEdgeId,
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@@ -680,6 +689,10 @@ public class DeviceController extends BaseController {
@RequestParam int page,
@ApiParam(value = DEVICE_TYPE_DESCRIPTION)
@RequestParam(required = false) String type,
+ @ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION)
+ @RequestParam(required = false) String deviceProfileId,
+ @ApiParam(value = DEVICE_ACTIVE_PARAM_DESCRIPTION)
+ @RequestParam(required = false) Boolean active,
@ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES)
@@ -695,25 +708,16 @@ public class DeviceController extends BaseController {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
checkEdgeId(edgeId, Operation.READ);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
- PageData nonFilteredResult;
+ DeviceInfoFilter.DeviceInfoFilterBuilder filter = DeviceInfoFilter.builder();
+ filter.tenantId(tenantId);
+ filter.edgeId(edgeId);
+ filter.active(active);
if (type != null && type.trim().length() > 0) {
- nonFilteredResult = deviceService.findDevicesByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink);
- } else {
- nonFilteredResult = deviceService.findDevicesByTenantIdAndEdgeId(tenantId, edgeId, pageLink);
+ filter.type(type);
+ } else if (deviceProfileId != null && deviceProfileId.length() > 0) {
+ filter.deviceProfileId(new DeviceProfileId(toUUID(deviceProfileId)));
}
- List filteredDevices = nonFilteredResult.getData().stream().filter(device -> {
- try {
- accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, Operation.READ, device.getId(), device);
- return true;
- } catch (ThingsboardException e) {
- return false;
- }
- }).collect(Collectors.toList());
- PageData filteredResult = new PageData<>(filteredDevices,
- nonFilteredResult.getTotalPages(),
- nonFilteredResult.getTotalElements(),
- nonFilteredResult.hasNext());
- return checkNotNull(filteredResult);
+ return checkNotNull(deviceService.findDeviceInfosByFilter(filter.build(), pageLink));
}
@ApiOperation(value = "Count devices by device profile (countByDeviceProfileAndEmptyOtaPackage)",
diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java
index 5ef1c1b572..9a021643cc 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java
@@ -50,6 +50,8 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain;
+import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest;
+import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult;
import org.thingsboard.server.common.msg.edge.FromEdgeSyncResponse;
import org.thingsboard.server.common.msg.edge.ToEdgeSyncRequest;
import org.thingsboard.server.dao.exception.DataValidationException;
@@ -58,8 +60,6 @@ import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeBulkImportService;
import org.thingsboard.server.service.entitiy.edge.TbEdgeService;
-import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest;
-import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
diff --git a/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java b/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java
index 86d0e2c344..b5c8e18ed2 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java
@@ -32,7 +32,6 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
-import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
@@ -61,16 +60,16 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.controller.ControllerConstants.BRANCH_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_PARAM_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END;
import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_START;
import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
-import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
-import static org.thingsboard.server.controller.ControllerConstants.ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION;
-import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
-import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES;
+import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.VC_REQUEST_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.VERSION_ID_PARAM_DESCRIPTION;
diff --git a/application/src/main/java/org/thingsboard/server/controller/EventController.java b/application/src/main/java/org/thingsboard/server/controller/EventController.java
index 59fb18d82e..ec75f981a7 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EventController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EventController.java
@@ -29,7 +29,6 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
-import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.event.EventFilter;
import org.thingsboard.server.common.data.event.EventType;
@@ -41,12 +40,9 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.event.EventService;
-import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation;
-import java.util.Locale;
-
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE;
diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java
index 23aaba0e48..0f4a288e33 100644
--- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java
@@ -16,6 +16,7 @@
package org.thingsboard.server.controller;
import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -47,11 +48,13 @@ import org.thingsboard.server.common.data.notification.targets.NotificationRecip
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
+import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation;
import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
+import org.thingsboard.server.common.data.page.SortOrder;
import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationService;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
@@ -74,6 +77,16 @@ import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
+import static org.thingsboard.server.controller.ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER;
+import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END;
+import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_START;
+import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE;
+import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
+import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.service.security.permission.Resource.NOTIFICATION;
@RestController
@@ -91,7 +104,10 @@ public class NotificationController extends BaseController {
private final NotificationSettingsService notificationSettingsService;
@ApiOperation(value = "Get notifications (getNotifications)",
- notes = "**WebSocket API**:\n\n" +
+ notes = "Returns the page of notifications for current user." + NEW_LINE +
+ PAGE_DATA_PARAMETERS +
+ AVAILABLE_FOR_ANY_AUTHORIZED_USER + NEW_LINE +
+ "**WebSocket API**:\n\n" +
"There are 2 types of subscriptions: one for unread notifications count, another for unread notifications themselves.\n\n" +
"The URI for opening WS session for notifications: `/api/ws/plugins/notifications`.\n\n" +
"Subscription command for unread notifications count:\n" +
@@ -142,11 +158,17 @@ public class NotificationController extends BaseController {
"}\n```")
@GetMapping("/notifications")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
- public PageData getNotifications(@RequestParam int pageSize,
+ public PageData getNotifications(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
+ @RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
+ @ApiParam(value = "Case-insensitive 'substring' filter based on notification subject or text")
@RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION)
@RequestParam(required = false) String sortProperty,
+ @ApiParam(value = SORT_ORDER_DESCRIPTION)
@RequestParam(required = false) String sortOrder,
+ @ApiParam(value = "To search for unread notifications only")
@RequestParam(defaultValue = "false") boolean unreadOnly,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
// no permissions
@@ -154,6 +176,9 @@ public class NotificationController extends BaseController {
return notificationService.findNotificationsByRecipientIdAndReadStatus(user.getTenantId(), user.getId(), unreadOnly, pageLink);
}
+ @ApiOperation(value = "Mark notification as read (markNotificationAsRead)",
+ notes = "Marks notification as read by its id." +
+ AVAILABLE_FOR_ANY_AUTHORIZED_USER)
@PutMapping("/notification/{id}/read")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
public void markNotificationAsRead(@PathVariable UUID id,
@@ -163,6 +188,9 @@ public class NotificationController extends BaseController {
notificationCenter.markNotificationAsRead(user.getTenantId(), user.getId(), notificationId);
}
+ @ApiOperation(value = "Mark all notifications as read (markAllNotificationsAsRead)",
+ notes = "Marks all unread notifications as read." +
+ AVAILABLE_FOR_ANY_AUTHORIZED_USER)
@PutMapping("/notifications/read")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
public void markAllNotificationsAsRead(@AuthenticationPrincipal SecurityUser user) {
@@ -170,6 +198,9 @@ public class NotificationController extends BaseController {
notificationCenter.markAllNotificationsAsRead(user.getTenantId(), user.getId());
}
+ @ApiOperation(value = "Delete notification (deleteNotification)",
+ notes = "Deletes notification by its id." +
+ AVAILABLE_FOR_ANY_AUTHORIZED_USER)
@DeleteMapping("/notification/{id}")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
public void deleteNotification(@PathVariable UUID id,
@@ -179,6 +210,35 @@ public class NotificationController extends BaseController {
notificationCenter.deleteNotification(user.getTenantId(), user.getId(), notificationId);
}
+ @ApiOperation(value = "Create notification request (createNotificationRequest)",
+ notes = "Processes notification request.\n" +
+ "Mandatory request properties are `targets` (list of targets ids to send notification to), " +
+ "and either `templateId` (existing notification template id) or `template` (to send notification without saving the template).\n" +
+ "Optionally, you can set `sendingDelayInSec` inside the `additionalConfig` field to schedule the notification." + NEW_LINE +
+ "For each enabled delivery method in the notification template, there must be a target in the `targets` list that supports this delivery method: " +
+ "if you chose `WEB`, `EMAIL` or `SMS` - there must be at least one target in `targets` of `PLATFORM_USERS` type.\n" +
+ "For `SLACK` delivery method - you need to chose at least one `SLACK` notification target." + NEW_LINE +
+ "Notification request object with `PROCESSING` status will be returned immediately, " +
+ "and the notification sending itself is done asynchronously. After all notifications are sent, " +
+ "the `status` of the request becomes `SENT`. Use `getNotificationRequestById` to see " +
+ "the notification request processing status and some sending stats. " + NEW_LINE +
+ "Here is an example of notification request to one target using saved template:\n" +
+ MARKDOWN_CODE_BLOCK_START +
+ "{\n" +
+ " \"templateId\": {\n" +
+ " \"entityType\": \"NOTIFICATION_TEMPLATE\",\n" +
+ " \"id\": \"6dbc3670-e4dd-11ed-9401-dbcc5dff78be\"\n" +
+ " },\n" +
+ " \"targets\": [\n" +
+ " \"320e3ed0-d785-11ed-a06c-21dd57dd88ca\"\n" +
+ " ],\n" +
+ " \"additionalConfig\": {\n" +
+ " \"sendingDelayInSec\": 0\n" +
+ " }\n" +
+ "}" +
+ MARKDOWN_CODE_BLOCK_END +
+ SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH
+ )
@PostMapping("/notification/request")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationRequest createNotificationRequest(@RequestBody @Valid NotificationRequest notificationRequest,
@@ -198,9 +258,15 @@ public class NotificationController extends BaseController {
return doSaveAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, (tenantId, request) -> notificationCenter.processNotificationRequest(tenantId, request, null));
}
+ @ApiOperation(value = "Get notification request preview (getNotificationRequestPreview)",
+ notes = "Returns preview for notification request." + NEW_LINE +
+ "`processedTemplates` shows how the notifications for each delivery method will look like " +
+ "for the first recipient of the corresponding notification target." +
+ SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PostMapping("/notification/request/preview")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationRequestPreview getNotificationRequestPreview(@RequestBody @Valid NotificationRequest request,
+ @ApiParam(value = "Amount of the recipients to show in preview")
@RequestParam(defaultValue = "20") int recipientsPreviewSize,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
NotificationTemplate template;
@@ -230,7 +296,8 @@ public class NotificationController extends BaseController {
NotificationTargetType targetType = target.getConfiguration().getType();
if (targetType == NotificationTargetType.PLATFORM_USERS) {
PageData recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(),
- (PlatformUsersNotificationTargetConfig) target.getConfiguration(), new PageLink(recipientsPreviewSize));
+ (PlatformUsersNotificationTargetConfig) target.getConfiguration(), new PageLink(recipientsPreviewSize, 0, null,
+ new SortOrder("createdTime", SortOrder.Direction.DESC)));
recipientsCount = (int) recipients.getTotalElements();
recipientsPart = recipients.getData().stream().map(r -> (NotificationRecipient) r).collect(Collectors.toList());
} else {
@@ -240,7 +307,15 @@ public class NotificationController extends BaseController {
firstRecipient.putIfAbsent(targetType, !recipientsPart.isEmpty() ? recipientsPart.get(0) : null);
for (NotificationRecipient recipient : recipientsPart) {
if (recipientsPreview.size() < recipientsPreviewSize) {
- recipientsPreview.add(recipient.getTitle());
+ String title = recipient.getTitle();
+ if (recipient instanceof SlackConversation) {
+ title = ((SlackConversation) recipient).getPointer() + title;
+ } else if (recipient instanceof User) {
+ if (!title.equals(recipient.getEmail())) {
+ title += " (" + recipient.getEmail() + ")";
+ }
+ }
+ recipientsPreview.add(title);
} else {
break;
}
@@ -270,6 +345,9 @@ public class NotificationController extends BaseController {
return preview;
}
+ @ApiOperation(value = "Get notification request by id (getNotificationRequestById)",
+ notes = "Fetches notification request info by request id." +
+ SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/notification/request/{id}")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationRequestInfo getNotificationRequestById(@PathVariable UUID id) throws ThingsboardException {
@@ -277,12 +355,21 @@ public class NotificationController extends BaseController {
return checkEntityId(notificationRequestId, notificationRequestService::findNotificationRequestInfoById, Operation.READ);
}
+ @ApiOperation(value = "Get notification requests (getNotificationRequests)",
+ notes = "Returns the page of notification requests submitted by users of this tenant or sysadmins." + NEW_LINE +
+ PAGE_DATA_PARAMETERS +
+ SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/notification/requests")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
- public PageData getNotificationRequests(@RequestParam int pageSize,
+ public PageData getNotificationRequests(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
+ @RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
+ @ApiParam(value = "Case-insensitive 'substring' filed based on the used template name")
@RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION)
@RequestParam(required = false) String sortProperty,
+ @ApiParam(value = SORT_ORDER_DESCRIPTION)
@RequestParam(required = false) String sortOrder,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
// generic permission
@@ -290,6 +377,11 @@ public class NotificationController extends BaseController {
return notificationRequestService.findNotificationRequestsInfosByTenantIdAndOriginatorType(user.getTenantId(), EntityType.USER, pageLink);
}
+ @ApiOperation(value = "Delete notification request (deleteNotificationRequest)",
+ notes = "Deletes notification request by its id." + NEW_LINE +
+ "If the request has status `SENT` - all sent notifications for this request will be deleted. " +
+ "If it is `SCHEDULED`, the request will be cancelled." +
+ SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@DeleteMapping("/notification/request/{id}")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public void deleteNotificationRequest(@PathVariable UUID id) throws Exception {
@@ -299,6 +391,21 @@ public class NotificationController extends BaseController {
}
+ @ApiOperation(value = "Save notification settings (saveNotificationSettings)",
+ notes = "Saves notification settings for this tenant or sysadmin.\n" +
+ "`deliveryMethodsConfigs` of the settings must be specified." + NEW_LINE +
+ "Here is an example of the notification settings with Slack configuration:\n" +
+ MARKDOWN_CODE_BLOCK_START +
+ "{\n" +
+ " \"deliveryMethodsConfigs\": {\n" +
+ " \"SLACK\": {\n" +
+ " \"method\": \"SLACK\",\n" +
+ " \"botToken\": \"xoxb-....\"\n" +
+ " }\n" +
+ " }\n" +
+ "}" +
+ MARKDOWN_CODE_BLOCK_END +
+ SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PostMapping("/notification/settings")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationSettings saveNotificationSettings(@RequestBody @Valid NotificationSettings notificationSettings,
@@ -309,6 +416,9 @@ public class NotificationController extends BaseController {
return notificationSettings;
}
+ @ApiOperation(value = "Get notification settings (getNotificationSettings)",
+ notes = "Retrieves notification settings for this tenant or sysadmin." +
+ SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/notification/settings")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationSettings getNotificationSettings(@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
@@ -317,6 +427,9 @@ public class NotificationController extends BaseController {
return notificationSettingsService.findNotificationSettings(tenantId);
}
+ @ApiOperation(value = "Get available delivery methods (getAvailableDeliveryMethods)",
+ notes = "Returns the list of delivery methods that are properly configured and are allowed to be used for sending notifications." +
+ SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/notification/deliveryMethods")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public Set getAvailableDeliveryMethods(@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java
index 75e2b538a6..ccf2124113 100644
--- a/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/NotificationRuleController.java
@@ -15,6 +15,8 @@
*/
package org.thingsboard.server.controller;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -44,6 +46,15 @@ import org.thingsboard.server.service.security.permission.Operation;
import javax.validation.Valid;
import java.util.UUID;
+import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END;
+import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_START;
+import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE;
+import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
+import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.service.security.permission.Resource.NOTIFICATION;
@RestController
@@ -55,6 +66,53 @@ public class NotificationRuleController extends BaseController {
private final NotificationRuleService notificationRuleService;
+ @ApiOperation(value = "Save notification rule (saveNotificationRule)",
+ notes = "Creates or updates notification rule. " + NEW_LINE +
+ "Mandatory properties are `name`, `templateId` (of a template with `notificationType` matching to rule's `triggerType`), " +
+ "`triggerType`, `triggerConfig` and `recipientConfig`. Additionally, you may specify rule `description` " +
+ "inside of `additionalConfig`." + NEW_LINE +
+ "Trigger type of the rule cannot be changed. " +
+ "Available trigger types for tenant: `ENTITY_ACTION`, `ALARM`, `ALARM_COMMENT`, `ALARM_ASSIGNMENT`, " +
+ "`DEVICE_ACTIVITY`, `RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT`.\n" +
+ "For sysadmin, there are following trigger types available: `ENTITIES_LIMIT`, `API_USAGE_LIMIT`, " +
+ "`NEW_PLATFORM_VERSION`." + NEW_LINE +
+ "Here is an example of notification rule to send notification when a " +
+ "device, asset or customer is created or deleted:\n" +
+ MARKDOWN_CODE_BLOCK_START +
+ "{\n" +
+ " \"name\": \"Entity action\",\n" +
+ " \"templateId\": {\n" +
+ " \"entityType\": \"NOTIFICATION_TEMPLATE\",\n" +
+ " \"id\": \"32117320-d785-11ed-a06c-21dd57dd88ca\"\n" +
+ " },\n" +
+ " \"triggerType\": \"ENTITY_ACTION\",\n" +
+ " \"triggerConfig\": {\n" +
+ " \"entityTypes\": [\n" +
+ " \"CUSTOMER\",\n" +
+ " \"DEVICE\",\n" +
+ " \"ASSET\"\n" +
+ " ],\n" +
+ " \"created\": true,\n" +
+ " \"updated\": false,\n" +
+ " \"deleted\": true,\n" +
+ " \"triggerType\": \"ENTITY_ACTION\"\n" +
+ " },\n" +
+ " \"recipientsConfig\": {\n" +
+ " \"targets\": [\n" +
+ " \"320f2930-d785-11ed-a06c-21dd57dd88ca\"\n" +
+ " ],\n" +
+ " \"triggerType\": \"ENTITY_ACTION\"\n" +
+ " },\n" +
+ " \"additionalConfig\": {\n" +
+ " \"description\": \"Send notification to tenant admins or customer users when a device, asset or customer is created\"\n" +
+ " },\n" +
+ " \"templateName\": \"Entity action notification\",\n" +
+ " \"deliveryMethods\": [\n" +
+ " \"WEB\"\n" +
+ " ]\n" +
+ "}" +
+ MARKDOWN_CODE_BLOCK_END +
+ SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PostMapping("/rule")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationRule saveNotificationRule(@RequestBody @Valid NotificationRule notificationRule,
@@ -74,6 +132,11 @@ public class NotificationRuleController extends BaseController {
return notificationRule;
}
+ @ApiOperation(value = "Get notification rule by id (getNotificationRuleById)",
+ notes = "Fetches notification rule info by rule's id.\n" +
+ "In addition to regular notification rule fields, " +
+ "there are `templateName` and `deliveryMethods` in the response." +
+ SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/rule/{id}")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationRuleInfo getNotificationRuleById(@PathVariable UUID id) throws ThingsboardException {
@@ -81,12 +144,21 @@ public class NotificationRuleController extends BaseController {
return checkEntityId(notificationRuleId, notificationRuleService::findNotificationRuleInfoById, Operation.READ);
}
+ @ApiOperation(value = "Get notification rules (getNotificationRules)",
+ notes = "Returns the page of notification rules." + NEW_LINE +
+ PAGE_DATA_PARAMETERS +
+ SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/rules")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
- public PageData getNotificationRules(@RequestParam int pageSize,
+ public PageData getNotificationRules(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
+ @RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
+ @ApiParam(value = "Case-insensitive 'substring' filter based on rule's name")
@RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION)
@RequestParam(required = false) String sortProperty,
+ @ApiParam(value = SORT_ORDER_DESCRIPTION)
@RequestParam(required = false) String sortOrder,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
// generic permission
@@ -94,6 +166,10 @@ public class NotificationRuleController extends BaseController {
return notificationRuleService.findNotificationRulesInfosByTenantId(user.getTenantId(), pageLink);
}
+ @ApiOperation(value = "Delete notification rule (deleteNotificationRule)",
+ notes = "Deletes notification rule by id.\n" +
+ "Cancels all related scheduled notification requests (e.g. due to escalation table)" +
+ SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@DeleteMapping("/rule/{id}")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public void deleteNotificationRule(@PathVariable UUID id,
diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java
index 6374b65710..a4c829796b 100644
--- a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java
@@ -16,6 +16,7 @@
package org.thingsboard.server.controller;
import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
@@ -58,6 +59,14 @@ import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
+import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END;
+import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_START;
+import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE;
+import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
+import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.service.security.permission.Resource.NOTIFICATION;
@@ -71,7 +80,28 @@ public class NotificationTargetController extends BaseController {
private final NotificationTargetService notificationTargetService;
@ApiOperation(value = "Save notification target (saveNotificationTarget)",
- notes = "Create or update notification target." +
+ notes = "Creates or updates notification target." + NEW_LINE +
+ "Available `configuration` types are `PLATFORM_USERS` and `SLACK`.\n" +
+ "For `PLATFORM_USERS` the `usersFilter` must be specified. " +
+ "For tenant, there are following users filter types available: " +
+ "`USER_LIST`, `CUSTOMER_USERS`, `TENANT_ADMINISTRATORS`, `ALL_USERS`, " +
+ "`ORIGINATOR_ENTITY_OWNER_USERS`, `AFFECTED_USER`.\n" +
+ "For sysadmin: `TENANT_ADMINISTRATORS`, `AFFECTED_TENANT_ADMINISTRATORS`, " +
+ "`SYSTEM_ADMINISTRATORS`, `ALL_USERS`." + NEW_LINE +
+ "Here is an example of tenant-level notification target to send notification to customer's users:\n" +
+ MARKDOWN_CODE_BLOCK_START +
+ "{\n" +
+ " \"name\": \"Users of Customer A\",\n" +
+ " \"configuration\": {\n" +
+ " \"type\": \"PLATFORM_USERS\",\n" +
+ " \"usersFilter\": {\n" +
+ " \"type\": \"CUSTOMER_USERS\",\n" +
+ " \"customerId\": \"32499a20-d785-11ed-a06c-21dd57dd88ca\"\n" +
+ " },\n" +
+ " \"description\": \"Users of Customer A\"\n" +
+ " }\n" +
+ "}" +
+ MARKDOWN_CODE_BLOCK_END +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PostMapping("/target")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@@ -89,7 +119,7 @@ public class NotificationTargetController extends BaseController {
}
@ApiOperation(value = "Get notification target by id (getNotificationTargetById)",
- notes = "Fetch saved notification target by id." +
+ notes = "Fetches notification target by id." +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/target/{id}")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@@ -99,12 +129,14 @@ public class NotificationTargetController extends BaseController {
}
@ApiOperation(value = "Get recipients for notification target config (getRecipientsForNotificationTargetConfig)",
- notes = "Get the list (page) of recipients (users) for such notification target configuration." +
+ notes = "Returns the page of recipients for such notification target configuration." +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PostMapping("/target/recipients")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public PageData getRecipientsForNotificationTargetConfig(@RequestBody NotificationTarget notificationTarget,
+ @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
// generic permission
@@ -119,9 +151,13 @@ public class NotificationTargetController extends BaseController {
return notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), (PlatformUsersNotificationTargetConfig) notificationTarget.getConfiguration(), pageLink);
}
+ @ApiOperation(value = "Get notification targets by ids (getNotificationTargetsByIds)",
+ notes = "Returns the list of notification targets found by provided ids." +
+ SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping(value = "/targets", params = {"ids"})
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
- public List getNotificationTargetsByIds(@RequestParam("ids") UUID[] ids,
+ public List getNotificationTargetsByIds(@ApiParam(value = "Comma-separated list of uuids representing targets ids", required = true)
+ @RequestParam("ids") UUID[] ids,
@AuthenticationPrincipal SecurityUser user) {
// generic permission
List targetsIds = Arrays.stream(ids).map(NotificationTargetId::new).collect(Collectors.toList());
@@ -129,14 +165,20 @@ public class NotificationTargetController extends BaseController {
}
@ApiOperation(value = "Get notification targets (getNotificationTargets)",
- notes = "Fetch the page of notification targets owned by sysadmin or tenant." +
+ notes = "Returns the page of notification targets owned by sysadmin or tenant." + NEW_LINE +
+ PAGE_DATA_PARAMETERS +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/targets")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
- public PageData getNotificationTargets(@RequestParam int pageSize,
+ public PageData getNotificationTargets(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
+ @RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
+ @ApiParam(value = "Case-insensitive 'substring' filed based on the target's name")
@RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION)
@RequestParam(required = false) String sortProperty,
+ @ApiParam(value = SORT_ORDER_DESCRIPTION)
@RequestParam(required = false) String sortOrder,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
// generic permission
@@ -144,6 +186,10 @@ public class NotificationTargetController extends BaseController {
return notificationTargetService.findNotificationTargetsByTenantId(user.getTenantId(), pageLink);
}
+ @ApiOperation(value = "Get notification targets by supported notification type (getNotificationTargetsBySupportedNotificationType)",
+ notes = "Returns the page of notification targets filtered by notification type that they can be used for." + NEW_LINE +
+ PAGE_DATA_PARAMETERS +
+ SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping(value = "/targets", params = "notificationType")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public PageData getNotificationTargetsBySupportedNotificationType(@RequestParam int pageSize,
@@ -158,7 +204,7 @@ public class NotificationTargetController extends BaseController {
}
@ApiOperation(value = "Delete notification target by id (deleteNotificationTargetById)",
- notes = "Delete notification target by its id.\n\n" +
+ notes = "Deletes notification target by its id." + NEW_LINE +
"This target cannot be referenced by existing scheduled notification requests or any notification rules." +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@DeleteMapping("/target/{id}")
diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java
index 751b139bd3..ab171efacf 100644
--- a/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java
@@ -16,6 +16,7 @@
package org.thingsboard.server.controller;
import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -36,9 +37,9 @@ import org.thingsboard.server.common.data.notification.NotificationDeliveryMetho
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
+import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversationType;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
-import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
@@ -51,6 +52,14 @@ import javax.validation.Valid;
import java.util.List;
import java.util.UUID;
+import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END;
+import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_START;
+import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE;
+import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
+import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
+import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.service.security.permission.Resource.NOTIFICATION;
@@ -65,19 +74,44 @@ public class NotificationTemplateController extends BaseController {
private final SlackService slackService;
@ApiOperation(value = "Save notification template (saveNotificationTemplate)",
- notes = "Create or update notification template.\n\n" +
- "Example:\n" +
- "```\n{\n \"name\": \"Hello to all my users\",\n" +
- " \"notificationType\": \"Message from administrator\",\n" +
+ notes = "Creates or updates notification template." + NEW_LINE +
+ "Here is an example of template to send notification via Web, SMS and Slack:\n" +
+ MARKDOWN_CODE_BLOCK_START +
+ "{\n" +
+ " \"name\": \"Greetings\",\n" +
+ " \"notificationType\": \"GENERAL\",\n" +
" \"configuration\": {\n" +
- " \"defaultTextTemplate\": \"Hello everyone\", # required if any of the templates' bodies is not set\n" +
- " \"templates\": {\n" +
- " \"PUSH\": {\n \"method\": \"PUSH\",\n \"body\": null # defaultTextTemplate will be used if body is not set\n },\n" +
- " \"SMS\": {\n \"method\": \"SMS\",\n \"body\": null\n },\n" +
- " \"EMAIL\": {\n \"method\": \"EMAIL\",\n \"body\": \"Non-default value for email notification: Hello everyone\",\n \"subject\": \"Message from administrator\"\n },\n" +
- " \"SLACK\": {\n \"method\": \"SLACK\",\n \"body\": null,\n \"conversationType\": \"PUBLIC_CHANNEL\",\n \"conversationId\": \"U02LD7BJOU2\" # received from listSlackConversations API method\n }\n" +
+ " \"deliveryMethodsTemplates\": {\n" +
+ " \"WEB\": {\n" +
+ " \"enabled\": true,\n" +
+ " \"subject\": \"Greetings\",\n" +
+ " \"body\": \"Hi there, ${recipientTitle}\",\n" +
+ " \"additionalConfig\": {\n" +
+ " \"icon\": {\n" +
+ " \"enabled\": true,\n" +
+ " \"icon\": \"back_hand\",\n" +
+ " \"color\": \"#757575\"\n" +
+ " },\n" +
+ " \"actionButtonConfig\": {\n" +
+ " \"enabled\": false\n" +
+ " }\n" +
+ " },\n" +
+ " \"method\": \"WEB\"\n" +
+ " },\n" +
+ " \"SMS\": {\n" +
+ " \"enabled\": true,\n" +
+ " \"body\": \"Hi there, ${recipientTitle}\",\n" +
+ " \"method\": \"SMS\"\n" +
+ " },\n" +
+ " \"SLACK\": {\n" +
+ " \"enabled\": true,\n" +
+ " \"body\": \"Hi there, @${recipientTitle}\",\n" +
+ " \"method\": \"SLACK\"\n" +
+ " }\n" +
" }\n" +
- " }\n}\n```" +
+ " }\n" +
+ "}" +
+ MARKDOWN_CODE_BLOCK_END +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PostMapping("/template")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@@ -88,7 +122,7 @@ public class NotificationTemplateController extends BaseController {
}
@ApiOperation(value = "Get notification template by id (getNotificationTemplateById)",
- notes = "Fetch notification template by id." +
+ notes = "Fetches notification template by id." +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/template/{id}")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@@ -98,15 +132,22 @@ public class NotificationTemplateController extends BaseController {
}
@ApiOperation(value = "Get notification templates (getNotificationTemplates)",
- notes = "Fetch the page of notification templates owned by sysadmin or tenant." +
+ notes = "Returns the page of notification templates owned by sysadmin or tenant." + NEW_LINE +
+ PAGE_DATA_PARAMETERS +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/templates")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
- public PageData getNotificationTemplates(@RequestParam int pageSize,
+ public PageData getNotificationTemplates(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
+ @RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
+ @ApiParam(value = "Case-insensitive 'substring' filter based on template's name and notification type")
@RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION)
@RequestParam(required = false) String sortProperty,
+ @ApiParam(value = SORT_ORDER_DESCRIPTION)
@RequestParam(required = false) String sortOrder,
+ @ApiParam(value = "Comma-separated list of notification types to filter the templates")
@RequestParam(required = false) NotificationType[] notificationTypes,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
// generic permission
@@ -119,7 +160,7 @@ public class NotificationTemplateController extends BaseController {
}
@ApiOperation(value = "Delete notification template by id (deleteNotificationTemplateById",
- notes = "Delete notification template by its id.\n\n" +
+ notes = "Deletes notification template by its id." + NEW_LINE +
"This template cannot be referenced by existing scheduled notification requests or any notification rules." +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@DeleteMapping("/template/{id}")
@@ -131,12 +172,12 @@ public class NotificationTemplateController extends BaseController {
}
@ApiOperation(value = "List Slack conversations (listSlackConversations)",
- notes = "List available Slack conversations by type to use in notification template.\n\n" +
- "Slack must be configured in notification settings." +
+ notes = "List available Slack conversations by type." +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@GetMapping("/slack/conversations")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public List listSlackConversations(@RequestParam SlackConversationType type,
+ @ApiParam(value = "Slack bot token. If absent - system Slack settings will be used")
@RequestParam(required = false) String token,
@AuthenticationPrincipal SecurityUser user) {
// generic permission
diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java
index 28f7f44e5f..464dc528a5 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java
@@ -44,10 +44,10 @@ import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
+import org.thingsboard.server.exception.ToErrorResponseEntity;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.rpc.RemoveRpcActorMsg;
import org.thingsboard.server.service.security.permission.Operation;
-import org.thingsboard.server.exception.ToErrorResponseEntity;
import javax.annotation.Nullable;
import java.util.UUID;
diff --git a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java
index 19195c7681..e586f079ca 100644
--- a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java
@@ -18,31 +18,60 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.info.BuildProperties;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
+import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.server.common.data.DashboardInfo;
+import org.thingsboard.server.common.data.SystemParams;
+import org.thingsboard.server.common.data.exception.ThingsboardException;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.PageLink;
+import org.thingsboard.server.common.data.settings.UserSettings;
+import org.thingsboard.server.common.data.settings.UserSettingsType;
import org.thingsboard.server.queue.util.TbCoreComponent;
+import org.thingsboard.server.service.security.model.SecurityUser;
+import org.thingsboard.server.service.security.model.UserPrincipal;
+import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService;
import springfox.documentation.annotations.ApiIgnore;
import javax.annotation.PostConstruct;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
@ApiIgnore
@RestController
@TbCoreComponent
@RequestMapping("/api")
@Slf4j
-public class SystemInfoController {
+public class SystemInfoController extends BaseController {
+
+ @Value("${security.user_token_access_enabled}")
+ private boolean userTokenAccessEnabled;
+
+ @Value("${tbel.enabled:true}")
+ private boolean tbelEnabled;
+
+ @Value("${state.persistToTelemetry:false}")
+ private boolean persistToTelemetry;
+
+ @Value("${ui.dashboard.max_datapoints_limit}")
+ private long maxDatapointsLimit;
@Autowired(required = false)
private BuildProperties buildProperties;
+ @Autowired
+ private EntitiesVersionControlService versionControlService;
+
@PostConstruct
public void init() {
JsonNode info = buildInfoObject();
@@ -56,6 +85,62 @@ public class SystemInfoController {
return buildInfoObject();
}
+ @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/system/params", method = RequestMethod.GET)
+ @ResponseBody
+ public SystemParams getSystemParams() throws ThingsboardException {
+ SystemParams systemParams = new SystemParams();
+ SecurityUser currentUser = getCurrentUser();
+ TenantId tenantId = currentUser.getTenantId();
+ CustomerId customerId = currentUser.getCustomerId();
+ if (currentUser.isSystemAdmin() || currentUser.isTenantAdmin()) {
+ systemParams.setUserTokenAccessEnabled(userTokenAccessEnabled);
+ } else {
+ systemParams.setUserTokenAccessEnabled(false);
+ }
+ boolean forceFullscreen = isForceFullscreen(currentUser);
+ if (forceFullscreen && (currentUser.isTenantAdmin() || currentUser.isCustomerUser())) {
+ PageLink pageLink = new PageLink(100);
+ List dashboards;
+ if (currentUser.isTenantAdmin()) {
+ dashboards = dashboardService.findDashboardsByTenantId(tenantId, pageLink).getData();
+ } else {
+ dashboards = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).getData();
+ }
+ systemParams.setAllowedDashboardIds(dashboards.stream().map(d -> d.getId().getId().toString()).collect(Collectors.toList()));
+ } else {
+ systemParams.setAllowedDashboardIds(Collections.emptyList());
+ }
+ systemParams.setEdgesSupportEnabled(edgesEnabled);
+ if (currentUser.isTenantAdmin()) {
+ systemParams.setHasRepository(versionControlService.getVersionControlSettings(tenantId) != null);
+ systemParams.setTbelEnabled(tbelEnabled);
+ } else {
+ systemParams.setHasRepository(false);
+ systemParams.setTbelEnabled(false);
+ }
+ if (currentUser.isTenantAdmin() || currentUser.isCustomerUser()) {
+ systemParams.setPersistDeviceStateToTelemetry(persistToTelemetry);
+ } else {
+ systemParams.setPersistDeviceStateToTelemetry(false);
+ }
+ UserSettings userSettings = userSettingsService.findUserSettings(currentUser.getTenantId(), currentUser.getId(), UserSettingsType.GENERAL);
+ ObjectNode userSettingsNode = userSettings == null ? JacksonUtil.newObjectNode() : (ObjectNode) userSettings.getSettings();
+ if (!userSettingsNode.has("openedMenuSections")) {
+ userSettingsNode.set("openedMenuSections", JacksonUtil.newArrayNode());
+ }
+ systemParams.setUserSettings(userSettingsNode);
+ systemParams.setMaxDatapointsLimit(maxDatapointsLimit);
+ return systemParams;
+ }
+
+ private boolean isForceFullscreen(SecurityUser currentUser) {
+ return UserPrincipal.Type.PUBLIC_ID.equals(currentUser.getUserPrincipal().getType()) ||
+ (currentUser.getAdditionalInfo() != null &&
+ currentUser.getAdditionalInfo().has("defaultDashboardFullscreen") &&
+ currentUser.getAdditionalInfo().get("defaultDashboardFullscreen").booleanValue());
+ }
+
private JsonNode buildInfoObject() {
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode infoObject = objectMapper.createObjectNode();
diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
index 15c0e46836..a7fe973ec0 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
@@ -21,7 +21,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
@@ -40,7 +39,6 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.tenant.profile.TbTenantProfileService;
-import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
diff --git a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java
index 39b352633c..4a5bcb3d81 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java
@@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
+import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettings;
import org.thingsboard.server.common.data.security.model.mfa.account.EmailTwoFaAccountConfig;
import org.thingsboard.server.common.data.security.model.mfa.account.SmsTwoFaAccountConfig;
@@ -39,7 +40,6 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService;
import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails;
-import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.system.SystemSecurityService;
diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java
index cedb2d6726..f79f5ee782 100644
--- a/application/src/main/java/org/thingsboard/server/controller/UserController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java
@@ -38,7 +38,6 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.MailService;
-import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.UserEmailInfo;
@@ -59,14 +58,13 @@ import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
-import org.thingsboard.server.common.data.settings.LastVisitedDashboardInfo;
+import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent;
+import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.common.data.settings.UserDashboardAction;
import org.thingsboard.server.common.data.settings.UserDashboardsInfo;
import org.thingsboard.server.common.data.settings.UserSettings;
-import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent;
-import org.thingsboard.server.common.data.security.model.JwtPair;
-import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.common.data.settings.UserSettingsType;
+import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.user.TbUserService;
import org.thingsboard.server.service.query.EntityQueryService;
diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
index c3f1e44d12..ea13de3779 100644
--- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
+++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
@@ -35,7 +35,6 @@ import org.thingsboard.server.service.install.migrate.EntitiesMigrateService;
import org.thingsboard.server.service.install.migrate.TsLatestMigrateService;
import org.thingsboard.server.service.install.update.CacheCleanupService;
import org.thingsboard.server.service.install.update.DataUpdateService;
-import org.thingsboard.server.service.install.update.DefaultDataUpdateService;
import static org.thingsboard.server.service.install.update.DefaultDataUpdateService.getEnv;
@@ -53,6 +52,9 @@ public class ThingsboardInstallService {
@Value("${install.load_demo:false}")
private Boolean loadDemo;
+ @Value("${state.persistToTelemetry:false}")
+ private boolean persistToTelemetry;
+
@Autowired
private EntityDatabaseSchemaService entityDatabaseSchemaService;
@@ -247,6 +249,8 @@ public class ThingsboardInstallService {
case "3.4.4":
log.info("Upgrading ThingsBoard from version 3.4.4 to 3.5.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.4.4");
+ entityDatabaseSchemaService.createOrUpdateViewsAndFunctions();
+ entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry);
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
if (!getEnv("SKIP_DEFAULT_NOTIFICATION_CONFIGS_CREATION", false)) {
@@ -272,6 +276,9 @@ public class ThingsboardInstallService {
entityDatabaseSchemaService.createDatabaseSchema();
+ entityDatabaseSchemaService.createOrUpdateViewsAndFunctions();
+ entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry);
+
log.info("Installing DataBase schema for timeseries...");
if (noSqlKeyspaceService != null) {
diff --git a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
index 619df15e14..05e78e4060 100644
--- a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
+++ b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
@@ -42,6 +42,7 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.notification.trigger.EntitiesLimitTrigger;
+import org.thingsboard.server.common.msg.notification.trigger.EntityActionTrigger;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.queue.notification.NotificationRuleProcessor;
@@ -90,11 +91,11 @@ public class EntityActionService {
case ALARM_CLEAR:
msgType = DataConstants.ALARM_CLEAR;
break;
- case ALARM_ASSIGN:
- msgType = DataConstants.ALARM_ASSIGN;
+ case ALARM_ASSIGNED:
+ msgType = DataConstants.ALARM_ASSIGNED;
break;
- case ALARM_UNASSIGN:
- msgType = DataConstants.ALARM_UNASSIGN;
+ case ALARM_UNASSIGNED:
+ msgType = DataConstants.ALARM_UNASSIGNED;
break;
case ALARM_DELETE:
msgType = DataConstants.ALARM_DELETE;
@@ -190,12 +191,6 @@ public class EntityActionService {
AlarmComment comment = extractParameter(AlarmComment.class, 0, additionalInfo);
metaData.putValue("comment", json.writeValueAsString(comment));
}
- if (actionType == ActionType.ADDED && !tenantId.isSysTenantId()) {
- notificationRuleProcessor.process(EntitiesLimitTrigger.builder()
- .tenantId(tenantId)
- .entityType(entityId.getEntityType())
- .build());
- }
ObjectNode entityNode;
if (entity != null) {
entityNode = json.valueToTree(entity);
@@ -242,12 +237,28 @@ public class EntityActionService {
entityNode = json.valueToTree(extractParameter(EntityRelation.class, 0, additionalInfo));
}
}
- TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode));
+
if (tenantId == null || tenantId.isNullUid()) {
if (entity instanceof HasTenantId) {
tenantId = ((HasTenantId) entity).getTenantId();
}
}
+ if (tenantId != null && !tenantId.isSysTenantId()) {
+ if (actionType == ActionType.ADDED) {
+ notificationRuleProcessor.process(EntitiesLimitTrigger.builder()
+ .tenantId(tenantId)
+ .entityType(entityId.getEntityType())
+ .build());
+ }
+ notificationRuleProcessor.process(EntityActionTrigger.builder()
+ .tenantId(tenantId)
+ .entityId(entityId)
+ .entity(entity)
+ .actionType(actionType)
+ .user(user)
+ .build());
+ }
+ TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode));
tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null);
} catch (Exception e) {
log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e);
diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java
index db8247bb93..1215bef0bf 100644
--- a/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java
+++ b/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.apiusage;
+import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
@@ -61,20 +62,25 @@ public abstract class BaseApiUsageState {
long currentValue = get(key);
long currentHourlyValue = getHourly(key);
- long newValue;
- long newHourlyValue;
+ StatsCalculationResult result;
if (key.isCounter()) {
- newValue = currentValue + value;
- newHourlyValue = currentHourlyValue + value;
+ result = StatsCalculationResult.builder()
+ .newValue(currentValue + value).valueChanged(true)
+ .newHourlyValue(currentHourlyValue + value).hourlyValueChanged(true)
+ .build();
} else {
Long newGaugeValue = calculateGauge(key, value, serviceId);
- newValue = newGaugeValue != null ? newGaugeValue : currentValue;
- newHourlyValue = newGaugeValue != null ? Math.max(newGaugeValue, currentHourlyValue) : currentHourlyValue;
+ long newValue = newGaugeValue != null ? newGaugeValue : currentValue;
+ long newHourlyValue = newGaugeValue != null ? Math.max(newGaugeValue, currentHourlyValue) : currentHourlyValue;
+ result = StatsCalculationResult.builder()
+ .newValue(newValue).valueChanged(newValue != currentValue || !currentCycleValues.containsKey(key))
+ .newHourlyValue(newHourlyValue).hourlyValueChanged(newHourlyValue != currentHourlyValue || !currentHourValues.containsKey(key))
+ .build();
}
- set(key, newValue);
- setHourly(key, newHourlyValue);
- return StatsCalculationResult.of(newValue, newHourlyValue);
+ set(key, result.getNewValue());
+ setHourly(key, result.getNewHourlyValue());
+ return result;
}
private Long calculateGauge(ApiUsageRecordKey key, long value, String serviceId) {
@@ -188,10 +194,13 @@ public abstract class BaseApiUsageState {
return getApiUsageState().getEntityId();
}
- @Data(staticConstructor = "of")
+ @Data
+ @Builder
public static class StatsCalculationResult {
private final long newValue;
+ private final boolean valueChanged;
private final long newHourlyValue;
+ private final boolean hourlyValueChanged;
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
index 9fdb1d0b8e..9e17641c69 100644
--- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
+++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
@@ -48,7 +48,6 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
-import org.thingsboard.server.queue.notification.NotificationRuleProcessor;
import org.thingsboard.server.common.msg.notification.trigger.ApiUsageLimitTrigger;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
@@ -62,6 +61,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceM
import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionService;
+import org.thingsboard.server.queue.notification.NotificationRuleProcessor;
import org.thingsboard.server.service.apiusage.BaseApiUsageState.StatsCalculationResult;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.mail.MailExecutorService;
@@ -192,11 +192,14 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
ApiUsageRecordKey recordKey = ApiUsageRecordKey.valueOf(statsItem.getKey());
StatsCalculationResult calculationResult = usageState.calculate(recordKey, statsItem.getValue(), serviceId);
- long newValue = calculationResult.getNewValue();
- long newHourlyValue = calculationResult.getNewHourlyValue();
-
- updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.getApiCountKey(), newValue)));
- updatedEntries.add(new BasicTsKvEntry(newHourTs, new LongDataEntry(recordKey.getApiCountKey() + HOURLY, newHourlyValue)));
+ if (calculationResult.isValueChanged()) {
+ long newValue = calculationResult.getNewValue();
+ updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.getApiCountKey(), newValue)));
+ }
+ if (calculationResult.isHourlyValueChanged()) {
+ long newHourlyValue = calculationResult.getNewHourlyValue();
+ updatedEntries.add(new BasicTsKvEntry(newHourTs, new LongDataEntry(recordKey.getApiCountKey() + HOURLY, newHourlyValue)));
+ }
if (recordKey.getApiFeature() != null) {
apiFeatures.add(recordKey.getApiFeature());
}
diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
index 732d8ce409..b425694e81 100644
--- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
+++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
@@ -155,6 +155,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
scannedComponent.setName(ruleNodeAnnotation.name());
scannedComponent.setScope(ruleNodeAnnotation.scope());
+ scannedComponent.setClusteringMode(ruleNodeAnnotation.clusteringMode());
NodeDefinition nodeDefinition = prepareNodeDefinition(ruleNodeAnnotation);
ObjectNode configurationDescriptor = mapper.createObjectNode();
JsonNode node = mapper.valueToTree(nodeDefinition);
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
index f8321c39f2..474c7ba233 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
@@ -40,7 +40,6 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.EdgeEventStorageSettings;
import org.thingsboard.server.service.edge.rpc.constructor.EdgeMsgConstructor;
-import org.thingsboard.server.service.edge.rpc.processor.settings.AdminSettingsEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.alarm.AlarmEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.asset.AssetEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.asset.AssetProfileEdgeProcessor;
@@ -54,6 +53,7 @@ import org.thingsboard.server.service.edge.rpc.processor.ota.OtaPackageEdgeProce
import org.thingsboard.server.service.edge.rpc.processor.queue.QueueEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.rule.RuleChainEdgeProcessor;
+import org.thingsboard.server.service.edge.rpc.processor.settings.AdminSettingsEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.telemetry.TelemetryEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.user.UserEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.widget.WidgetBundleEdgeProcessor;
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java
index 9dd65eef60..2c4a14635d 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallService.java
@@ -67,7 +67,9 @@ public class DefaultEdgeInstallService implements EdgeInstallService {
dockerInstallInstructions = dockerInstallInstructions.replace("${LOCALHOST_WARNING}", "");
dockerInstallInstructions = dockerInstallInstructions.replace("${BASE_URL}", baseUrl);
}
- dockerInstallInstructions = dockerInstallInstructions.replace("${TB_EDGE_VERSION}", appVersion + "EDGE");
+ String edgeVersion = appVersion + "EDGE";
+ edgeVersion = edgeVersion.replace("-SNAPSHOT", "");
+ dockerInstallInstructions = dockerInstallInstructions.replace("${TB_EDGE_VERSION}", edgeVersion);
dockerInstallInstructions = dockerInstallInstructions.replace("${CLOUD_ROUTING_KEY}", edge.getRoutingKey());
dockerInstallInstructions = dockerInstallInstructions.replace("${CLOUD_ROUTING_SECRET}", edge.getSecret());
dockerInstallInstructions = dockerInstallInstructions.replace("${CLOUD_RPC_PORT}", Integer.toString(rpcPort));
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AdminSettingsMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AdminSettingsMsgConstructor.java
index 099811c45e..79754b867c 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AdminSettingsMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AdminSettingsMsgConstructor.java
@@ -16,8 +16,8 @@
package org.thingsboard.server.service.edge.rpc.constructor;
import org.springframework.stereotype.Component;
-import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent;
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java
index 9b4f649336..69a83da0a0 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.service.edge.rpc.constructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.DeviceId;
@@ -25,7 +26,6 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entityview.EntityViewService;
-import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BasePageableEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BasePageableEdgeEventFetcher.java
index c44d90264a..0ec5878695 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BasePageableEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BasePageableEdgeEventFetcher.java
@@ -21,7 +21,6 @@ import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
-import org.thingsboard.server.common.data.page.SortOrder;
import java.util.ArrayList;
import java.util.List;
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantWidgetsBundlesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantWidgetsBundlesEdgeEventFetcher.java
index 5e136f664b..ffddb65315 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantWidgetsBundlesEdgeEventFetcher.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantWidgetsBundlesEdgeEventFetcher.java
@@ -16,8 +16,6 @@
package org.thingsboard.server.service.edge.rpc.fetch;
import lombok.extern.slf4j.Slf4j;
-import org.thingsboard.server.common.data.edge.Edge;
-import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java
index b86b8f3a26..2fb7320219 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java
@@ -18,7 +18,6 @@ package org.thingsboard.server.service.edge.rpc.processor.asset;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
-import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
index fe4f761f81..0a75404fb4 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
@@ -309,10 +309,10 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
return EdgeEventActionType.ALARM_ACK;
case ALARM_CLEAR:
return EdgeEventActionType.ALARM_CLEAR;
- case ALARM_ASSIGN:
- return EdgeEventActionType.ALARM_ASSIGN;
- case ALARM_UNASSIGN:
- return EdgeEventActionType.ALARM_UNASSIGN;
+ case ALARM_ASSIGNED:
+ return EdgeEventActionType.ALARM_ASSIGNED;
+ case ALARM_UNASSIGNED:
+ return EdgeEventActionType.ALARM_UNASSIGNED;
case DELETED:
return EdgeEventActionType.DELETED;
case RELATION_ADD_OR_UPDATE:
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java
index 6f86632ffd..6dfc81c747 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmCommentService.java
@@ -27,7 +27,6 @@ import org.thingsboard.server.common.data.alarm.AlarmCommentType;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
-import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
index 36e8c9f188..caf65d39ec 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
@@ -23,6 +23,7 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmAssignee;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentType;
@@ -35,7 +36,6 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
-import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import java.util.List;
@@ -175,7 +175,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
} catch (ThingsboardException e) {
log.error("Failed to save alarm comment", e);
}
- notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_ASSIGN, user);
+ notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_ASSIGNED, user);
} else {
throw new ThingsboardException("Alarm was already assigned to this user!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
@@ -203,7 +203,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
} catch (ThingsboardException e) {
log.error("Failed to save alarm comment", e);
}
- notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_UNASSIGN, user);
+ notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_UNASSIGNED, user);
} else {
throw new ThingsboardException("Alarm was already unassigned!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java
index 6997ee8d98..63e11aeb7a 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java
@@ -19,17 +19,13 @@ import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cluster.TbClusterService;
-import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.common.data.page.PageLink;
-import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
-import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.scheduler.SchedulerComponent;
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java
index 795ed1f145..1c71bc85a3 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultTbUserSettingsService.java
@@ -43,7 +43,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
-import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
diff --git a/application/src/main/java/org/thingsboard/server/service/executors/SharedEventLoopGroupService.java b/application/src/main/java/org/thingsboard/server/service/executors/SharedEventLoopGroupService.java
index 3687da1795..38b563472e 100644
--- a/application/src/main/java/org/thingsboard/server/service/executors/SharedEventLoopGroupService.java
+++ b/application/src/main/java/org/thingsboard/server/service/executors/SharedEventLoopGroupService.java
@@ -15,7 +15,6 @@
*/
package org.thingsboard.server.service.executors;
-import com.google.common.util.concurrent.MoreExecutors;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import lombok.Getter;
@@ -23,7 +22,6 @@ import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
-import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Component
diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
index f1183bea8b..4952b486db 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
@@ -21,6 +21,7 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.Getter;
+import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -110,6 +111,8 @@ import java.util.List;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
@Service
@Profile("install")
@@ -682,18 +685,29 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
}
@Override
+ @SneakyThrows
public void createDefaultNotificationConfigs() {
log.info("Creating default notification configs for system admin");
- if (notificationTargetService.findNotificationTargetsByTenantId(TenantId.SYS_TENANT_ID, new PageLink(1)).getTotalElements() == 0) {
+ if (notificationTargetService.countNotificationTargetsByTenantId(TenantId.SYS_TENANT_ID) == 0) {
notificationSettingsService.createDefaultNotificationConfigs(TenantId.SYS_TENANT_ID);
}
PageDataIterable tenants = new PageDataIterable<>(tenantService::findTenantsIds, 500);
+ ExecutorService executor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors(), 4));
log.info("Creating default notification configs for all tenants");
+ AtomicInteger count = new AtomicInteger();
for (TenantId tenantId : tenants) {
- if (notificationTargetService.findNotificationTargetsByTenantId(tenantId, new PageLink(1)).getTotalElements() == 0) {
- notificationSettingsService.createDefaultNotificationConfigs(tenantId);
- }
+ executor.submit(() -> {
+ if (notificationTargetService.countNotificationTargetsByTenantId(tenantId) == 0) {
+ notificationSettingsService.createDefaultNotificationConfigs(tenantId);
+ int n = count.incrementAndGet();
+ if (n % 500 == 0) {
+ log.info("{} tenants processed", n);
+ }
+ }
+ });
}
+ executor.shutdown();
+ executor.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java
index 8d040d365e..35370d0854 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java
@@ -16,4 +16,9 @@
package org.thingsboard.server.service.install;
public interface EntityDatabaseSchemaService extends DatabaseSchemaService {
+
+ void createOrUpdateDeviceInfoView(boolean activityStateInTelemetry);
+
+ void createOrUpdateViewsAndFunctions() throws Exception;
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
index f5f212beb2..504b058879 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
@@ -34,12 +34,12 @@ import org.thingsboard.server.common.data.queue.ProcessingStrategyType;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.queue.SubmitStrategy;
import org.thingsboard.server.common.data.queue.SubmitStrategyType;
+import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.dao.asset.AssetDao;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
-import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.sql.tenant.TenantRepository;
import org.thingsboard.server.dao.tenant.TenantService;
diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java
index 60513765a5..b9a62e3dfa 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java
@@ -27,6 +27,7 @@ public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaSer
public static final String SCHEMA_ENTITIES_SQL = "schema-entities.sql";
public static final String SCHEMA_ENTITIES_IDX_SQL = "schema-entities-idx.sql";
public static final String SCHEMA_ENTITIES_IDX_PSQL_ADDON_SQL = "schema-entities-idx-psql-addon.sql";
+ public static final String SCHEMA_VIEWS_AND_FUNCTIONS_SQL = "schema-views-and-functions.sql";
public SqlEntityDatabaseSchemaService() {
super(SCHEMA_ENTITIES_SQL, SCHEMA_ENTITIES_IDX_SQL);
@@ -39,4 +40,16 @@ public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaSer
executeQueryFromFile(SCHEMA_ENTITIES_IDX_PSQL_ADDON_SQL);
}
+ @Override
+ public void createOrUpdateDeviceInfoView(boolean activityStateInTelemetry) {
+ String sourceViewName = activityStateInTelemetry ? "device_info_active_ts_view" : "device_info_active_attribute_view";
+ executeQuery("DROP VIEW IF EXISTS device_info_view CASCADE;");
+ executeQuery("CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM " + sourceViewName + ";");
+ }
+
+ @Override
+ public void createOrUpdateViewsAndFunctions() throws Exception {
+ log.info("Installing SQL DataBase schema views and functions: " + SCHEMA_VIEWS_AND_FUNCTIONS_SQL);
+ executeQueryFromFile(SCHEMA_VIEWS_AND_FUNCTIONS_SQL);
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumn.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumn.java
index 1f897e253d..80a938aef1 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumn.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumn.java
@@ -21,7 +21,6 @@ import org.thingsboard.server.common.data.UUIDConverter;
import java.sql.PreparedStatement;
import java.sql.SQLException;
-import java.sql.Types;
import java.util.regex.Pattern;
@Data
diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java b/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java
index f12b4b3c87..ae47a94c1a 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java
@@ -16,8 +16,6 @@
package org.thingsboard.server.service.install.update;
import lombok.extern.slf4j.Slf4j;
-import org.thingsboard.server.common.data.SearchTextBased;
-import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
index 55eae83af4..f3fd18141a 100644
--- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
+++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
@@ -364,6 +364,11 @@ public class DefaultMailService implements MailService {
mailSender.testConnection();
}
+ @Override
+ public boolean isConfigured(TenantId tenantId) {
+ return mailSender != null;
+ }
+
private String toEnabledValueLabel(ApiFeature apiFeature) {
switch (apiFeature) {
case DB:
diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
index 91bc0863db..a8e3caad1f 100644
--- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
+++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
@@ -39,7 +39,6 @@ import org.thingsboard.server.common.data.notification.NotificationRequestConfig
import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.NotificationStatus;
-import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
@@ -294,24 +293,6 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
return onNotificationUpdate(recipient.getTenantId(), recipient.getId(), update);
}
- @Override
- public void sendBasicNotification(TenantId tenantId, UserId recipientId, String subject, String text) {
- Notification notification = Notification.builder()
- .recipientId(recipientId)
- .type(NotificationType.GENERAL)
- .subject(subject)
- .text(text)
- .status(NotificationStatus.SENT)
- .build();
- notification = notificationService.saveNotification(TenantId.SYS_TENANT_ID, notification);
-
- NotificationUpdate update = NotificationUpdate.builder()
- .created(true)
- .notification(notification)
- .build();
- onNotificationUpdate(tenantId, recipientId, update);
- }
-
@Override
public void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId) {
boolean updated = notificationService.markNotificationAsRead(tenantId, recipientId, notificationId);
diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java
index 25ff1dd50e..27a9cabe43 100644
--- a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java
+++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java
@@ -128,6 +128,7 @@ public class NotificationProcessingContext {
private Map createTemplateContextForRecipient(NotificationRecipient recipient) {
return Map.of(
+ "recipientTitle", recipient.getTitle(),
"recipientEmail", Strings.nullToEmpty(recipient.getEmail()),
"recipientFirstName", Strings.nullToEmpty(recipient.getFirstName()),
"recipientLastName", Strings.nullToEmpty(recipient.getLastName())
diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java
index f5bbc57954..8b3c9551c2 100644
--- a/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java
+++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java
@@ -49,10 +49,8 @@ public class EmailNotificationChannel implements NotificationChannel triggerProcessors = new EnumMap<>(NotificationRuleTriggerType.class);
+ @PostConstruct
+ private void init() {
+ sentNotifications = cacheManager.getCache(CacheConstants.SENT_NOTIFICATIONS_CACHE);
+ if (sentNotifications == null) {
+ throw new IllegalStateException("Sent notifications cache is not set up");
+ }
+ }
+
@Override
public void process(NotificationRuleTrigger trigger) {
NotificationRuleTriggerType triggerType = trigger.getType();
@@ -126,6 +142,9 @@ public class DefaultNotificationRuleProcessor implements NotificationRuleProcess
log.debug("[{}] Rate limit for notification requests per rule was exceeded (rule '{}')", rule.getTenantId(), rule.getName());
return;
}
+ if (trigger.getType().isDeduplicate() && alreadySent(rule.getId(), trigger)) {
+ return;
+ }
NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig);
rule.getRecipientsConfig().getTargetsTable().forEach((delay, targets) -> {
@@ -175,6 +194,23 @@ public class DefaultNotificationRuleProcessor implements NotificationRuleProcess
return triggerProcessors.get(triggerConfig.getTriggerType()).constructNotificationInfo(trigger);
}
+ private boolean alreadySent(NotificationRuleId ruleId, NotificationRuleTrigger trigger) {
+ String key = ruleId + "_" + trigger.getOriginatorEntityId();
+ SentNotification sent = sentNotifications.get(key, SentNotification.class);
+ boolean alreadySent;
+ if (sent != null && sent.getTrigger().equals(trigger)) {
+ alreadySent = true;
+ log.debug("Notification for {} trigger was already sent, ignoring", trigger.getType());
+ // updating cache anyway so that the value is not removed by ttl
+ } else {
+ alreadySent = false;
+ sent = new SentNotification(trigger);
+ }
+ log.trace("[{}] Putting to sentNotifications cache: {}", ruleId, trigger);
+ sentNotifications.put(key, sent);
+ return alreadySent;
+ }
+
@EventListener(ComponentLifecycleMsg.class)
public void onNotificationRuleDeleted(ComponentLifecycleMsg componentLifecycleMsg) {
if (componentLifecycleMsg.getEvent() != ComponentLifecycleEvent.DELETED ||
@@ -209,4 +245,11 @@ public class DefaultNotificationRuleProcessor implements NotificationRuleProcess
RuleEngineMsgTrigger.msgTypeToTriggerType = ruleEngineMsgTypeToTriggerType;
}
+ @Data
+ private static class SentNotification implements Serializable {
+ private static final long serialVersionUID = 38973480405095422L;
+
+ private final NotificationRuleTrigger trigger;
+ }
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java
index 54c53a54d2..ed72f6be58 100644
--- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java
@@ -32,19 +32,20 @@ import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigg
import java.util.Set;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
+import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
@Service
public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor {
@Override
public boolean matchesFilter(RuleEngineMsgTrigger trigger, AlarmAssignmentNotificationRuleTriggerConfig triggerConfig) {
- Action action = trigger.getMsg().getType().equals(DataConstants.ALARM_ASSIGN) ? Action.ASSIGNED : Action.UNASSIGNED;
+ Action action = trigger.getMsg().getType().equals(DataConstants.ALARM_ASSIGNED) ? Action.ASSIGNED : Action.UNASSIGNED;
if (!triggerConfig.getNotifyOn().contains(action)) {
return false;
}
Alarm alarm = JacksonUtil.fromString(trigger.getMsg().getData(), Alarm.class);
- return (isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) &&
- (isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity())) &&
+ return emptyOrContains(triggerConfig.getAlarmTypes(), alarm.getType()) &&
+ emptyOrContains(triggerConfig.getAlarmSeverities(), alarm.getSeverity()) &&
(isEmpty(triggerConfig.getAlarmStatuses()) || AlarmStatusFilter.from(triggerConfig.getAlarmStatuses()).matches(alarm));
}
@@ -53,7 +54,7 @@ public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificatio
AlarmInfo alarmInfo = JacksonUtil.fromString(trigger.getMsg().getData(), AlarmInfo.class);
AlarmAssignee assignee = alarmInfo.getAssignee();
return AlarmAssignmentNotificationInfo.builder()
- .action(trigger.getMsg().getType().equals(DataConstants.ALARM_ASSIGN) ? "assigned" : "unassigned")
+ .action(trigger.getMsg().getType().equals(DataConstants.ALARM_ASSIGNED) ? "assigned" : "unassigned")
.assigneeFirstName(assignee != null ? assignee.getFirstName() : null)
.assigneeLastName(assignee != null ? assignee.getLastName() : null)
.assigneeEmail(assignee != null ? assignee.getEmail() : null)
@@ -78,7 +79,7 @@ public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificatio
@Override
public Set getSupportedMsgTypes() {
- return Set.of(DataConstants.ALARM_ASSIGN, DataConstants.ALARM_UNASSIGN);
+ return Set.of(DataConstants.ALARM_ASSIGNED, DataConstants.ALARM_UNASSIGNED);
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java
index f10e395a78..cf71718f83 100644
--- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java
@@ -33,6 +33,7 @@ import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigg
import java.util.Set;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
+import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
@Service
public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor {
@@ -53,8 +54,8 @@ public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRu
}
}
Alarm alarm = JacksonUtil.fromString(msg.getData(), Alarm.class);
- return (isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) &&
- (isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity())) &&
+ return emptyOrContains(triggerConfig.getAlarmTypes(), alarm.getType()) &&
+ emptyOrContains(triggerConfig.getAlarmSeverities(), alarm.getSeverity()) &&
(isEmpty(triggerConfig.getAlarmStatuses()) || AlarmStatusFilter.from(triggerConfig.getAlarmStatuses()).matches(alarm));
}
@@ -66,9 +67,9 @@ public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRu
return AlarmCommentNotificationInfo.builder()
.comment(comment.getComment().get("text").asText())
.action(msg.getType().equals(DataConstants.COMMENT_CREATED) ? "added" : "updated")
- .userEmail(trigger.getMsg().getMetaData().getValue("userEmail"))
- .userFirstName(trigger.getMsg().getMetaData().getValue("userFirstName"))
- .userLastName(trigger.getMsg().getMetaData().getValue("userLastName"))
+ .userEmail(msg.getMetaData().getValue("userEmail"))
+ .userFirstName(msg.getMetaData().getValue("userFirstName"))
+ .userLastName(msg.getMetaData().getValue("userLastName"))
.alarmId(alarmInfo.getUuidId())
.alarmType(alarmInfo.getType())
.alarmOriginator(alarmInfo.getOriginator())
diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java
index c8ebf73304..c32ed26854 100644
--- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.service.notification.rule.trigger;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo;
@@ -25,11 +26,10 @@ import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotific
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.ClearRule;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
-import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.msg.notification.trigger.AlarmTrigger;
-import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
+import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
@Service
public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor {
@@ -85,11 +85,11 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor {
+public class EntityActionTriggerProcessor implements NotificationRuleTriggerProcessor {
@Override
- public boolean matchesFilter(RuleEngineMsgTrigger trigger, EntityActionNotificationRuleTriggerConfig triggerConfig) {
- String msgType = trigger.getMsg().getType();
- if (msgType.equals(DataConstants.ENTITY_CREATED)) {
- if (!triggerConfig.isCreated()) {
- return false;
- }
- } else if (msgType.equals(DataConstants.ENTITY_UPDATED)) {
- if (!triggerConfig.isUpdated()) {
- return false;
- }
- } else if (msgType.equals(DataConstants.ENTITY_DELETED)) {
- if (!triggerConfig.isDeleted()) {
- return false;
- }
- } else {
- return false;
- }
- return isEmpty(triggerConfig.getEntityTypes()) || triggerConfig.getEntityTypes().contains(getEntityType(trigger.getMsg()));
+ public boolean matchesFilter(EntityActionTrigger trigger, EntityActionNotificationRuleTriggerConfig triggerConfig) {
+ return ((trigger.getActionType() == ActionType.ADDED && triggerConfig.isCreated())
+ || (trigger.getActionType() == ActionType.UPDATED && triggerConfig.isUpdated())
+ || (trigger.getActionType() == ActionType.DELETED && triggerConfig.isDeleted()))
+ && emptyOrContains(triggerConfig.getEntityTypes(), trigger.getEntityId().getEntityType());
}
@Override
- public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) {
- TbMsg msg = trigger.getMsg();
- String msgType = msg.getType();
- ActionType actionType = msgType.equals(DataConstants.ENTITY_CREATED) ? ActionType.ADDED :
- msgType.equals(DataConstants.ENTITY_UPDATED) ? ActionType.UPDATED :
- msgType.equals(DataConstants.ENTITY_DELETED) ? ActionType.DELETED : null;
+ public RuleOriginatedNotificationInfo constructNotificationInfo(EntityActionTrigger trigger) {
return EntityActionNotificationInfo.builder()
- .entityId(msg.getOriginator())
- .entityName(msg.getMetaData().getValue("entityName"))
- .actionType(actionType)
- .userId(UUID.fromString(msg.getMetaData().getValue("userId")))
- .userEmail(trigger.getMsg().getMetaData().getValue("userEmail"))
- .userFirstName(trigger.getMsg().getMetaData().getValue("userFirstName"))
- .userLastName(trigger.getMsg().getMetaData().getValue("userLastName"))
- .entityCustomerId(msg.getCustomerId())
+ .entityId(trigger.getEntityId())
+ .entityName(trigger.getEntity().getName())
+ .actionType(trigger.getActionType())
+ .userId(trigger.getUser().getUuidId())
+ .userTitle(trigger.getUser().getTitle())
+ .userEmail(trigger.getUser().getEmail())
+ .userFirstName(trigger.getUser().getFirstName())
+ .userLastName(trigger.getUser().getLastName())
+ .entityCustomerId(trigger.getEntity() instanceof HasCustomerId ?
+ ((HasCustomerId) trigger.getEntity()).getCustomerId() :
+ trigger.getUser().getCustomerId())
.build();
}
- private static EntityType getEntityType(TbMsg msg) {
- return Optional.ofNullable(msg.getMetaData().getValue("entityType"))
- .map(EntityType::valueOf).orElse(null);
- }
-
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.ENTITY_ACTION;
}
- @Override
- public Set getSupportedMsgTypes() {
- return Set.of(DataConstants.ENTITY_CREATED, DataConstants.ENTITY_UPDATED, DataConstants.ENTITY_DELETED);
- }
-
}
diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java
index cb44e7b3ef..639378ad72 100644
--- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java
@@ -18,26 +18,18 @@ package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.UpdateMessage;
-import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.info.NewPlatformVersionNotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger;
-import org.thingsboard.server.common.msg.queue.ServiceType;
-import org.thingsboard.server.queue.discovery.PartitionService;
@Service
@RequiredArgsConstructor
public class NewPlatformVersionTriggerProcessor implements NotificationRuleTriggerProcessor {
- private final PartitionService partitionService;
-
@Override
public boolean matchesFilter(NewPlatformVersionTrigger trigger, NewPlatformVersionNotificationRuleTriggerConfig triggerConfig) {
- if (!partitionService.isMyPartition(ServiceType.TB_CORE, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID)) {
- return false;
- }
return trigger.getUpdateInfo().isUpdateAvailable();
}
diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NotificationRuleTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NotificationRuleTriggerProcessor.java
index 4d5228e32c..fe7fb82ee5 100644
--- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NotificationRuleTriggerProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NotificationRuleTriggerProcessor.java
@@ -16,9 +16,9 @@
package org.thingsboard.server.service.notification.rule.trigger;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
-import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
+import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger;
public interface NotificationRuleTriggerProcessor {
diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java
index a8b0b014a0..6e37db83ef 100644
--- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java
@@ -26,8 +26,8 @@ import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotifi
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
-import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.notification.trigger.RuleEngineComponentLifecycleEventTrigger;
+import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.queue.discovery.PartitionService;
import java.io.PrintWriter;
diff --git a/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java b/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java
index 457e89b3fa..6ab61426a8 100644
--- a/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java
+++ b/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java
@@ -30,7 +30,6 @@ import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
@@ -55,10 +54,8 @@ import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.Optional;
-import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
diff --git a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java
index d0cd58aa90..89b4002223 100644
--- a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java
+++ b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java
@@ -29,6 +29,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.async.DeferredResult;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.KvUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
@@ -56,7 +57,6 @@ import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
-import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.security.AccessValidator;
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
index 4d4d2414ff..76631aaa95 100644
--- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
+++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
@@ -410,6 +410,7 @@ public class DefaultTbClusterService implements TbClusterService {
if (entityType.equals(EntityType.TENANT)
|| entityType.equals(EntityType.TENANT_PROFILE)
|| entityType.equals(EntityType.DEVICE_PROFILE)
+ || (entityType.equals(EntityType.ASSET) && msg.getEvent() == ComponentLifecycleEvent.UPDATED)
|| entityType.equals(EntityType.ASSET_PROFILE)
|| entityType.equals(EntityType.API_USAGE_STATE)
|| (entityType.equals(EntityType.DEVICE) && msg.getEvent() == ComponentLifecycleEvent.UPDATED)
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
index 6a6612c581..2bd0043046 100644
--- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
+++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
@@ -35,7 +35,6 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
-import org.thingsboard.server.queue.notification.NotificationRuleProcessor;
import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
@@ -64,6 +63,7 @@ import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
+import org.thingsboard.server.queue.notification.NotificationRuleProcessor;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
index 1e5060a95e..f8f6a7d25f 100644
--- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
+++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
@@ -37,7 +37,6 @@ import org.thingsboard.server.common.msg.queue.TbMsgCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.stats.StatsFactory;
-import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos;
@@ -50,6 +49,7 @@ import org.thingsboard.server.queue.discovery.QueueKey;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
+import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.queue.util.TbRuleEngineComponent;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java
index 4c4f76c2b9..400586235e 100644
--- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java
+++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java
@@ -18,13 +18,13 @@ package org.thingsboard.server.service.queue;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
-import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
+import org.thingsboard.server.common.data.exception.TenantNotFoundException;
import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.queue.discovery.TenantRoutingInfo;
import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
-import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
@Slf4j
@Service
@@ -46,7 +46,7 @@ public class DefaultTenantRoutingInfoService implements TenantRoutingInfoService
if (tenantProfile != null) {
return new TenantRoutingInfo(tenantId, tenantProfile.isIsolatedTbRuleEngine());
} else {
- throw new RuntimeException("Tenant not found!");
+ throw new TenantNotFoundException(tenantId);
}
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgProfilerInfo.java b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgProfilerInfo.java
index 4f408dd02a..3e69bb52bf 100644
--- a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgProfilerInfo.java
+++ b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgProfilerInfo.java
@@ -17,7 +17,6 @@ package org.thingsboard.server.service.queue;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.RuleNodeId;
-import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
import java.util.AbstractMap;
import java.util.Map;
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerStats.java b/application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerStats.java
index 62b04abba1..77e805eb62 100644
--- a/application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerStats.java
+++ b/application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerStats.java
@@ -15,19 +15,20 @@
*/
package org.thingsboard.server.service.queue;
-import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.Timer;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
+import org.thingsboard.server.common.stats.StatsCounter;
+import org.thingsboard.server.common.stats.StatsFactory;
+import org.thingsboard.server.common.stats.StatsType;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
-import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingResult;
-import org.thingsboard.server.common.stats.StatsCounter;
-import org.thingsboard.server.common.stats.StatsType;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
index 15471e25e1..2d517a2213 100644
--- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
+++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
@@ -34,7 +34,6 @@ import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
-import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
@@ -48,6 +47,7 @@ import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.TbPackCallback;
import org.thingsboard.server.service.queue.TbPackProcessingContext;
+import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import javax.annotation.PreDestroy;
import java.util.List;
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingResult.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingResult.java
index 0c39c1d506..3aa4be1363 100644
--- a/application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingResult.java
+++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingResult.java
@@ -16,7 +16,6 @@
package org.thingsboard.server.service.queue.processing;
import lombok.Getter;
-import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineSubmitStrategyFactory.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineSubmitStrategyFactory.java
index 07fe652fcc..4421f70ae3 100644
--- a/application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineSubmitStrategyFactory.java
+++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineSubmitStrategyFactory.java
@@ -18,7 +18,6 @@ package org.thingsboard.server.service.queue.processing;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.queue.SubmitStrategy;
-import org.thingsboard.server.queue.settings.TbRuleEngineQueueSubmitStrategyConfiguration;
@Component
@Slf4j
diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java
index 47d70d3330..1fef6acbc0 100644
--- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java
+++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java
@@ -19,12 +19,13 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
+import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest;
+import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcResponse;
+import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.id.RpcId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.rpc.RpcError;
-import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest;
-import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcResponse;
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
@@ -35,7 +36,6 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.util.TbRuleEngineComponent;
-import org.thingsboard.server.cluster.TbClusterService;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponseActorMsg.java b/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponseActorMsg.java
index 6f4d2af36b..2a3e30adcf 100644
--- a/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponseActorMsg.java
+++ b/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponseActorMsg.java
@@ -18,10 +18,10 @@ package org.thingsboard.server.service.rpc;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
-import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
@ToString
diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java
index 1cd4cb198f..ad40176cef 100644
--- a/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java
+++ b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java
@@ -20,6 +20,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.RpcId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -31,7 +32,6 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.rpc.RpcService;
import org.thingsboard.server.queue.util.TbCoreComponent;
-import org.thingsboard.server.cluster.TbClusterService;
@TbCoreComponent
@Service
diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java b/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java
index a0f6418d27..64c8887edb 100644
--- a/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java
+++ b/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java
@@ -18,10 +18,10 @@ package org.thingsboard.server.service.rpc;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
-import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
/**
diff --git a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
index 17af86dbf6..9bb0a40a78 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
@@ -27,7 +27,6 @@ import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.Customer;
-import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.OtaPackageInfo;
@@ -76,11 +75,11 @@ import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
import org.thingsboard.server.dao.user.UserService;
+import org.thingsboard.server.exception.ToErrorResponseEntity;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.AccessControlService;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
-import org.thingsboard.server.exception.ToErrorResponseEntity;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java
index d38138afdd..9f33466793 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java
@@ -16,7 +16,6 @@
package org.thingsboard.server.service.security.auth;
import io.jsonwebtoken.Claims;
-import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/BackupCodeTwoFaProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/BackupCodeTwoFaProvider.java
index cb718462bf..4a9c6c4e14 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/BackupCodeTwoFaProvider.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/BackupCodeTwoFaProvider.java
@@ -18,12 +18,12 @@ package org.thingsboard.server.service.security.auth.mfa.provider.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
-import org.thingsboard.server.common.data.util.CollectionsUtil;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.security.model.mfa.account.BackupCodeTwoFaAccountConfig;
import org.thingsboard.server.common.data.security.model.mfa.provider.BackupCodeTwoFaProviderConfig;
import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType;
+import org.thingsboard.server.common.data.util.CollectionsUtil;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager;
import org.thingsboard.server.service.security.auth.mfa.provider.TwoFaProvider;
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/TotpTwoFaProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/TotpTwoFaProvider.java
index 0115b1acd9..28a04d28f8 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/TotpTwoFaProvider.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/TotpTwoFaProvider.java
@@ -24,10 +24,10 @@ import org.jboss.aerogear.security.otp.api.Base32;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
import org.thingsboard.server.common.data.User;
-import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType;
-import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.common.data.security.model.mfa.account.TotpTwoFaAccountConfig;
import org.thingsboard.server.common.data.security.model.mfa.provider.TotpTwoFaProviderConfig;
+import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType;
+import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.mfa.provider.TwoFaProvider;
import org.thingsboard.server.service.security.model.SecurityUser;
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java
index 5807265c6c..429e185b3e 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java
@@ -16,6 +16,7 @@
package org.thingsboard.server.service.security.auth.oauth2;
import org.springframework.util.SerializationUtils;
+
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java
index 78725bc659..4251c4bad4 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java
@@ -16,7 +16,6 @@
package org.thingsboard.server.service.security.auth.oauth2;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
@@ -27,7 +26,6 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.system.SystemSecurityService;
-import org.thingsboard.server.utils.MiscUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
index 36c6c61471..edfc826050 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
@@ -30,9 +30,9 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
+import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.queue.util.TbCoreComponent;
-import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java
index 90722d3c08..e8fd8e4882 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java
@@ -24,9 +24,9 @@ import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.service.security.auth.MfaAuthenticationToken;
import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager;
-import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordEmailRequest.java b/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordEmailRequest.java
index cf3e722bbc..93f9541986 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordEmailRequest.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordEmailRequest.java
@@ -15,7 +15,6 @@
*/
package org.thingsboard.server.service.security.model;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java
index cde766443d..a222a7e83a 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java
@@ -34,10 +34,10 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
-import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/DefaultAccessControlService.java b/application/src/main/java/org/thingsboard/server/service/security/permission/DefaultAccessControlService.java
index 301fb986c9..de733d8f55 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/permission/DefaultAccessControlService.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/permission/DefaultAccessControlService.java
@@ -25,9 +25,9 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.service.security.model.SecurityUser;
-import java.util.*;
-
-import static org.thingsboard.server.dao.service.Validator.validateId;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
@Service
@Slf4j
diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java b/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java
index a48f8ab3d0..b2e795753c 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/permission/PermissionChecker.java
@@ -15,10 +15,8 @@
*/
package org.thingsboard.server.service.security.permission;
-import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.EntityId;
-import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.Arrays;
diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
index 921f05b0d1..3f6ea7ae3b 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
@@ -18,7 +18,6 @@ package org.thingsboard.server.service.security.permission;
import org.thingsboard.server.common.data.EntityType;
import java.util.Collections;
-import java.util.Optional;
import java.util.Set;
public enum Resource {
diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java
index aa57d1ee45..64550c438e 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java
@@ -22,8 +22,8 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.model.SecuritySettings;
-import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettings;
+import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.service.security.model.SecurityUser;
import javax.servlet.http.HttpServletRequest;
diff --git a/application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java b/application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java
index 85c33f31f5..46c992e2d8 100644
--- a/application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java
+++ b/application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java
@@ -15,22 +15,16 @@
*/
package org.thingsboard.server.service.session;
-import com.google.protobuf.InvalidProtocolBufferException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cache.annotation.CachePut;
-import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry;
import org.thingsboard.server.queue.util.TbCoreComponent;
-import java.io.Serializable;
import java.util.Collections;
-import static org.thingsboard.server.common.data.CacheConstants.SESSIONS_CACHE;
-
/**
* Created by ashvayka on 29.10.18.
*/
diff --git a/application/src/main/java/org/thingsboard/server/service/session/SessionCaffeineCache.java b/application/src/main/java/org/thingsboard/server/service/session/SessionCaffeineCache.java
index bc984bbc35..3daa7396a2 100644
--- a/application/src/main/java/org/thingsboard/server/service/session/SessionCaffeineCache.java
+++ b/application/src/main/java/org/thingsboard/server/service/session/SessionCaffeineCache.java
@@ -18,9 +18,9 @@ package org.thingsboard.server.service.session;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
+import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.id.DeviceId;
-import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.gen.transport.TransportProtos;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
diff --git a/application/src/main/java/org/thingsboard/server/service/session/SessionRedisCache.java b/application/src/main/java/org/thingsboard/server/service/session/SessionRedisCache.java
index 60e3f45d2d..5d154532c2 100644
--- a/application/src/main/java/org/thingsboard/server/service/session/SessionRedisCache.java
+++ b/application/src/main/java/org/thingsboard/server/service/session/SessionRedisCache.java
@@ -18,15 +18,14 @@ package org.thingsboard.server.service.session;
import com.google.protobuf.InvalidProtocolBufferException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
-import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CacheSpecsMap;
+import org.thingsboard.server.cache.RedisTbTransactionalCache;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.cache.TbRedisSerializer;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.id.DeviceId;
-import org.thingsboard.server.cache.RedisTbTransactionalCache;
import org.thingsboard.server.gen.transport.TransportProtos;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
diff --git a/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java b/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java
index a36b12f0fc..ee54a02620 100644
--- a/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java
+++ b/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java
@@ -28,7 +28,6 @@ import com.slack.api.methods.response.conversations.ConversationsListResponse;
import com.slack.api.methods.response.users.UsersListResponse;
import com.slack.api.model.ConversationType;
import lombok.RequiredArgsConstructor;
-import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.server.common.data.id.TenantId;
@@ -44,8 +43,6 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
-import static org.apache.commons.lang3.StringUtils.isNotEmpty;
-
@Service
@RequiredArgsConstructor
public class DefaultSlackService implements SlackService {
@@ -81,15 +78,11 @@ public class DefaultSlackService implements SlackService {
.filter(user -> !user.isDeleted() && !user.isStranger() && !user.isBot())
.map(user -> {
SlackConversation conversation = new SlackConversation();
+ conversation.setType(conversationType);
conversation.setId(user.getId());
- conversation.setShortName(user.getName());
+ conversation.setName(user.getName());
conversation.setWholeName(user.getProfile() != null ? user.getProfile().getRealNameNormalized() : user.getRealName());
conversation.setEmail(user.getProfile() != null ? user.getProfile().getEmail() : null);
- String title = "@" + conversation.getShortName();
- if (isNotEmpty(conversation.getWholeName()) && !conversation.getWholeName().equals(conversation.getShortName())) {
- title += " (" + conversation.getWholeName() + ")";
- }
- conversation.setTitle(title);
return conversation;
})
.collect(Collectors.toList());
@@ -107,10 +100,10 @@ public class DefaultSlackService implements SlackService {
.filter(channel -> !channel.isArchived())
.map(channel -> {
SlackConversation conversation = new SlackConversation();
+ conversation.setType(conversationType);
conversation.setId(channel.getId());
- conversation.setShortName(channel.getName());
+ conversation.setName(channel.getName());
conversation.setWholeName(channel.getNameNormalized());
- conversation.setTitle("#" + channel.getName());
return conversation;
})
.collect(Collectors.toList());
@@ -118,14 +111,6 @@ public class DefaultSlackService implements SlackService {
});
}
- @Override
- public SlackConversation findConversation(TenantId tenantId, String token, SlackConversationType conversationType, String namePattern) {
- List conversations = listConversations(tenantId, token, conversationType);
- return conversations.stream()
- .filter(conversation -> StringUtils.containsIgnoreCase(conversation.getTitle(), namePattern))
- .findFirst().orElse(null);
- }
-
@Override
public String getToken(TenantId tenantId) {
NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);
diff --git a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java
index 7cd4998b5d..0c54e75432 100644
--- a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java
+++ b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java
@@ -19,20 +19,20 @@ import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.NestedRuntimeException;
import org.springframework.stereotype.Service;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.sms.SmsSender;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
-import org.thingsboard.server.common.data.id.CustomerId;
-import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration;
-import org.thingsboard.server.common.data.sms.config.TestSmsRequest;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
+import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration;
+import org.thingsboard.server.common.data.sms.config.TestSmsRequest;
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
import org.thingsboard.server.dao.settings.AdminSettingsService;
-import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import javax.annotation.PostConstruct;
diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
index 005cc6ac4c..e33e501fbc 100644
--- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
+++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
@@ -24,12 +24,14 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.Getter;
+import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
+import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors;
@@ -40,6 +42,7 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceIdInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
+import org.thingsboard.server.common.data.exception.TenantNotFoundException;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -85,10 +88,10 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
@@ -112,6 +115,7 @@ import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
@Service
@TbCoreComponent
@Slf4j
+@RequiredArgsConstructor
public class DefaultDeviceStateService extends AbstractPartitionBasedService implements DeviceStateService {
public static final String ACTIVITY_STATE = "active";
@@ -146,13 +150,11 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService deviceStates = new ConcurrentHashMap<>();
- public DefaultDeviceStateService(TenantService tenantService, DeviceService deviceService,
- AttributesService attributesService, TimeseriesService tsService,
- TbClusterService clusterService, PartitionService partitionService,
- TbServiceInfoProvider serviceInfoProvider,
- EntityQueryRepository entityQueryRepository,
- DbTypeInfoComponent dbTypeInfoComponent,
- TbApiUsageReportClient apiUsageReportClient) {
- this.tenantService = tenantService;
- this.deviceService = deviceService;
- this.attributesService = attributesService;
- this.tsService = tsService;
- this.clusterService = clusterService;
- this.partitionService = partitionService;
- this.serviceInfoProvider = serviceInfoProvider;
- this.entityQueryRepository = entityQueryRepository;
- this.dbTypeInfoComponent = dbTypeInfoComponent;
- this.apiUsageReportClient = apiUsageReportClient;
- }
-
@PostConstruct
public void init() {
super.init();
deviceStateExecutor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(
Math.max(4, Runtime.getRuntime().availableProcessors()), "device-state"));
- scheduledExecutor.scheduleAtFixedRate(this::checkStates, new Random().nextInt(defaultStateCheckIntervalInSec), defaultStateCheckIntervalInSec, TimeUnit.SECONDS);
+ scheduledExecutor.scheduleWithFixedDelay(this::checkStates, new Random().nextInt(defaultStateCheckIntervalInSec), defaultStateCheckIntervalInSec, TimeUnit.SECONDS);
+ scheduledExecutor.scheduleWithFixedDelay(this::reportActivityStats, defaultActivityStatsIntervalInSec, defaultActivityStatsIntervalInSec, TimeUnit.SECONDS);
}
@PreDestroy
@@ -293,7 +281,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService> devicesActivity = new HashMap<>();
partitionedEntities.forEach((tpi, deviceIds) -> {
log.debug("Calculating state updates. tpi {} for {} devices", tpi.getFullTopicName(), deviceIds.size());
+ Set idsFromRemovedTenant = new HashSet<>();
for (DeviceId deviceId : deviceIds) {
DeviceStateData stateData;
try {
@@ -452,28 +440,44 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService tenantDevicesActivity = devicesActivity.computeIfAbsent(stateData.getTenantId(),
- tenantId -> Pair.of(new AtomicInteger(), new AtomicInteger()));
- if (stateData.getState().isActive()) {
- tenantDevicesActivity.getLeft().incrementAndGet();
- } else {
- tenantDevicesActivity.getRight().incrementAndGet();
+ if (e instanceof TenantNotFoundException) {
+ idsFromRemovedTenant.add(deviceId);
+ } else {
+ log.warn("[{}] Failed to update inactivity state [{}]", deviceId, e.getMessage());
+ }
}
}
+ deviceIds.removeAll(idsFromRemovedTenant);
});
- devicesActivity.forEach((tenantId, tenantDevicesActivity) -> {
+ } catch (Throwable t) {
+ log.warn("Failed to check devices states", t);
+ }
+ }
+
+ void reportActivityStats() {
+ try{
+ Map> stats = new HashMap<>();
+ for (DeviceStateData stateData : deviceStates.values()) {
+ Pair tenantDevicesActivity = stats.computeIfAbsent(stateData.getTenantId(),
+ tenantId -> Pair.of(new AtomicInteger(), new AtomicInteger()));
+ if (stateData.getState().isActive()) {
+ tenantDevicesActivity.getLeft().incrementAndGet();
+ } else {
+ tenantDevicesActivity.getRight().incrementAndGet();
+ }
+ }
+
+ stats.forEach((tenantId, tenantDevicesActivity) -> {
int active = tenantDevicesActivity.getLeft().get();
int inactive = tenantDevicesActivity.getRight().get();
apiUsageReportClient.report(tenantId, null, ApiUsageRecordKey.ACTIVE_DEVICES, active);
apiUsageReportClient.report(tenantId, null, ApiUsageRecordKey.INACTIVE_DEVICES, inactive);
if (active > 0) {
- log.info("[{}] Active devices: {}, inactive devices: {}", tenantId, active, inactive);
+ log.debug("[{}] Active devices: {}, inactive devices: {}", tenantId, active, inactive);
}
});
} catch (Throwable t) {
- log.warn("Failed to check devices states", t);
+ log.warn("Failed to report activity states", t);
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java
index 3c77f2b2c0..412df1ae91 100644
--- a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java
+++ b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java
@@ -16,12 +16,11 @@
package org.thingsboard.server.service.state;
import org.springframework.context.ApplicationListener;
-import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
-import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.common.msg.queue.TbCallback;
+import org.thingsboard.server.gen.transport.TransportProtos;
+import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
/**
* Created by ashvayka on 01.05.18.
diff --git a/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java b/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java
index 0612284b75..1b5bfd56d1 100644
--- a/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java
+++ b/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java
@@ -19,7 +19,6 @@ import com.google.common.util.concurrent.FutureCallback;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
-import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -28,7 +27,6 @@ import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.dao.asset.AssetService;
-import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.util.TbRuleEngineComponent;
import org.thingsboard.server.service.queue.TbRuleEngineConsumerStats;
diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
index 1db4348828..736a251dff 100644
--- a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
+++ b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
@@ -16,7 +16,6 @@
package org.thingsboard.server.service.subscription;
import org.springframework.context.ApplicationListener;
-import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -24,7 +23,6 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.queue.TbCallback;
-import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate;
diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java
index 084e73ec37..3f11679849 100644
--- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java
+++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java
@@ -29,8 +29,8 @@ import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
-import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.WebSocketService;
+import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.ArrayList;
diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java
index d6b3052613..7eba28bccd 100644
--- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java
+++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java
@@ -38,8 +38,8 @@ import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
-import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.WebSocketService;
+import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
index ccba16fbd8..c3ff54e0a5 100644
--- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
+++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
@@ -20,9 +20,7 @@ import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.alarm.Alarm;
-import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
-import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.Aggregation;
diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java
index 516eb20684..7685c8fbed 100644
--- a/application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java
+++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java
@@ -15,9 +15,9 @@
*/
package org.thingsboard.server.service.subscription;
+import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
-import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
index 914c8a7c55..8905b1b3ad 100644
--- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
+++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
@@ -16,8 +16,6 @@
package org.thingsboard.server.service.subscription;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.common.data.alarm.Alarm;
-import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java
index 793c25d625..822bd82e00 100644
--- a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java
+++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java
@@ -26,9 +26,9 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.relation.EntityRelation;
-import org.thingsboard.server.common.data.util.ThrowingRunnable;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.common.data.sync.ie.EntityImportResult;
+import org.thingsboard.server.common.data.util.ThrowingRunnable;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.queue.util.TbCoreComponent;
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java
index 618729a9f0..b408aadd68 100644
--- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java
+++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java
@@ -16,8 +16,6 @@
package org.thingsboard.server.service.sync.ie.exporting.impl;
import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import com.google.common.collect.Lists;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
@@ -27,12 +25,9 @@ import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
-import org.thingsboard.common.util.RegexUtils;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
-import java.util.UUID;
@Service
@TbCoreComponent
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/RuleChainExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/RuleChainExportService.java
index 775224cc63..83a0be5be2 100644
--- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/RuleChainExportService.java
+++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/RuleChainExportService.java
@@ -15,10 +15,8 @@
*/
package org.thingsboard.server.service.sync.ie.exporting.impl;
-import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
-import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.rule.RuleChain;
@@ -27,12 +25,10 @@ import org.thingsboard.server.common.data.sync.ie.RuleChainExportData;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
-import org.thingsboard.common.util.RegexUtils;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
-import java.util.UUID;
@Service
@TbCoreComponent
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceProfileImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceProfileImportService.java
index efa305f907..3cc0310079 100644
--- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceProfileImportService.java
+++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceProfileImportService.java
@@ -21,7 +21,6 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
-import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java
index 0e08ee9c48..a63600619a 100644
--- a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java
+++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java
@@ -22,6 +22,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
@@ -32,7 +33,6 @@ import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity;
-import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
@@ -101,6 +101,7 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import static com.google.common.util.concurrent.Futures.transform;
+import static org.thingsboard.server.common.data.sync.vc.VcUtils.checkBranchName;
@Service
@TbCoreComponent
@@ -138,6 +139,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
@SuppressWarnings("UnstableApiUsage")
@Override
public ListenableFuture saveEntitiesVersion(User user, VersionCreateRequest request) throws Exception {
+ checkBranchName(request.getBranch());
var pendingCommit = gitServiceQueue.prepareCommit(user, request);
DonAsynchron.withCallback(pendingCommit, commit -> {
cachePut(commit.getTxId(), new VersionCreationResult());
@@ -504,6 +506,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
@Override
public ListenableFuture saveVersionControlSettings(TenantId tenantId, RepositorySettings versionControlSettings) {
+ checkBranchName(versionControlSettings.getDefaultBranch());
var restoredSettings = this.repositorySettingsService.restore(tenantId, versionControlSettings);
try {
var future = gitServiceQueue.initRepository(tenantId, restoredSettings);
@@ -525,6 +528,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
@Override
public ListenableFuture checkVersionControlAccess(TenantId tenantId, RepositorySettings settings) throws ThingsboardException {
+ checkBranchName(settings.getDefaultBranch());
settings = this.repositorySettingsService.restore(tenantId, settings);
try {
return gitServiceQueue.testRepository(tenantId, settings);
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java
index 785dd50a92..feff708983 100644
--- a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java
+++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java
@@ -26,7 +26,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
-import org.thingsboard.server.common.data.util.CollectionsUtil;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType;
@@ -46,6 +45,7 @@ import org.thingsboard.server.common.data.sync.vc.RepositorySettings;
import org.thingsboard.server.common.data.sync.vc.VersionCreationResult;
import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo;
import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateRequest;
+import org.thingsboard.server.common.data.util.CollectionsUtil;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.CommitRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.EntitiesContentRequestMsg;
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCaffeineCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCaffeineCache.java
index 0c874f63a6..a870c4109b 100644
--- a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCaffeineCache.java
+++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCaffeineCache.java
@@ -20,8 +20,6 @@ import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
-import org.thingsboard.server.common.data.id.DeviceId;
-import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.UUID;
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/TbAutoCommitSettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/TbAutoCommitSettingsService.java
index edc9b84451..655acbb551 100644
--- a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/TbAutoCommitSettingsService.java
+++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/TbAutoCommitSettingsService.java
@@ -17,7 +17,6 @@ package org.thingsboard.server.service.sync.vc.autocommit;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings;
-import org.thingsboard.server.common.data.sync.vc.RepositorySettings;
public interface TbAutoCommitSettingsService {
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java
index 8bbb8e2498..20c29aea10 100644
--- a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java
+++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java
@@ -22,10 +22,10 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation;
-import org.thingsboard.server.common.data.util.ThrowingRunnable;
import org.thingsboard.server.common.data.sync.ie.EntityImportResult;
import org.thingsboard.server.common.data.sync.ie.EntityImportSettings;
import org.thingsboard.server.common.data.sync.vc.EntityTypeLoadResult;
+import org.thingsboard.server.common.data.util.ThrowingRunnable;
import java.util.ArrayList;
import java.util.Collection;
diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/DefaultTbRepositorySettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/DefaultTbRepositorySettingsService.java
index 656e484e67..cd8247d03e 100644
--- a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/DefaultTbRepositorySettingsService.java
+++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/DefaultTbRepositorySettingsService.java
@@ -18,8 +18,8 @@ package org.thingsboard.server.service.sync.vc.repository;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.common.data.sync.vc.RepositorySettings;
import org.thingsboard.server.common.data.sync.vc.RepositoryAuthMethod;
+import org.thingsboard.server.common.data.sync.vc.RepositorySettings;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.TbAbstractVersionControlSettingsService;
diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java
index 32efaa6346..145f43fa3f 100644
--- a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java
+++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java
@@ -20,7 +20,6 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Lazy;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.id.EntityId;
diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
index 2bb93d3dad..fec9d10a18 100644
--- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
+++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
@@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmModificationRequest;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
+import org.thingsboard.server.common.data.alarm.AlarmQueryV2;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
@@ -189,6 +190,16 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return alarmService.findCustomerAlarms(tenantId, customerId, query);
}
+ @Override
+ public ListenableFuture> findAlarmsV2(TenantId tenantId, AlarmQueryV2 query) {
+ return alarmService.findAlarmsV2(tenantId, query);
+ }
+
+ @Override
+ public ListenableFuture> findCustomerAlarmsV2(TenantId tenantId, CustomerId customerId, AlarmQueryV2 query) {
+ return alarmService.findCustomerAlarmsV2(tenantId, customerId, query);
+ }
+
@Override
public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus, String assigneeId) {
return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus, assigneeId);
diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
index b5bb4f054f..3f5e52796a 100644
--- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
+++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
@@ -24,7 +24,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
-import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
@@ -41,11 +40,10 @@ import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult;
import org.thingsboard.server.common.msg.queue.TbCallback;
-import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
-import org.thingsboard.server.queue.discovery.PartitionService;
+import org.thingsboard.server.dao.util.KvUtils;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
@@ -137,6 +135,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
checkInternalEntity(entityId);
boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null;
if (sysTenant || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) {
+ KvUtils.validate(ts);
if (saveLatest) {
saveAndNotifyInternal(tenantId, entityId, ts, ttl, getCallback(tenantId, customerId, sysTenant, callback));
} else {
diff --git a/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java
index 0cf251da1d..a2afe6b433 100644
--- a/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java
+++ b/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java
@@ -18,27 +18,25 @@ package org.thingsboard.server.service.transport;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
-import org.springframework.context.event.EventListener;
-import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.stats.MessagesStats;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsType;
+import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
+import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueResponseTemplate;
import org.thingsboard.server.queue.common.DefaultTbQueueResponseTemplate;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
-import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
-import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.TbCoreComponent;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
-import java.util.concurrent.*;
+import java.util.concurrent.ExecutorService;
/**
* Created by ashvayka on 05.10.18.
diff --git a/application/src/main/java/org/thingsboard/server/service/transport/TransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/TransportApiService.java
index 4247867e99..4d26d174f2 100644
--- a/application/src/main/java/org/thingsboard/server/service/transport/TransportApiService.java
+++ b/application/src/main/java/org/thingsboard/server/service/transport/TransportApiService.java
@@ -15,10 +15,10 @@
*/
package org.thingsboard.server.service.transport;
-import org.thingsboard.server.queue.TbQueueHandler;
-import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
+import org.thingsboard.server.queue.TbQueueHandler;
+import org.thingsboard.server.queue.common.TbProtoQueueMsg;
/**
* Created by ashvayka on 05.10.18.
diff --git a/application/src/main/java/org/thingsboard/server/service/transport/msg/TransportToDeviceActorMsgWrapper.java b/application/src/main/java/org/thingsboard/server/service/transport/msg/TransportToDeviceActorMsgWrapper.java
index 33b2b2a751..a976f4ed52 100644
--- a/application/src/main/java/org/thingsboard/server/service/transport/msg/TransportToDeviceActorMsgWrapper.java
+++ b/application/src/main/java/org/thingsboard/server/service/transport/msg/TransportToDeviceActorMsgWrapper.java
@@ -22,8 +22,8 @@ import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
import org.thingsboard.server.common.msg.aware.TenantAwareMsg;
-import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
import org.thingsboard.server.common.msg.queue.TbCallback;
+import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
import java.io.Serializable;
import java.util.UUID;
diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java
index 2855ddf8fd..01295bda32 100644
--- a/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java
+++ b/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java
@@ -17,19 +17,10 @@ package org.thingsboard.server.service.ttl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.queue.discovery.PartitionService;
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.SQLWarning;
-import java.sql.Statement;
-
@Slf4j
@RequiredArgsConstructor
diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
index ea059fcb2d..5154829ea6 100644
--- a/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
+++ b/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
@@ -21,11 +21,11 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.alarm.Alarm;
-import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
+import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.msg.queue.ServiceType;
@@ -61,46 +61,48 @@ public class AlarmsCleanUpService {
@Scheduled(initialDelayString = "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.alarms.checking_interval})}", fixedDelayString = "${sql.ttl.alarms.checking_interval}")
public void cleanUp() {
- PageLink tenantsBatchRequest = new PageLink(10_000, 0);
- PageLink removalBatchRequest = new PageLink(removalBatchSize, 0 );
- PageData tenantsIds;
- do {
- tenantsIds = tenantService.findTenantsIds(tenantsBatchRequest);
- for (TenantId tenantId : tenantsIds.getData()) {
- if (!partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) {
- continue;
- }
-
- Optional tenantProfileConfiguration = tenantProfileCache.get(tenantId).getProfileConfiguration();
- if (tenantProfileConfiguration.isEmpty() || tenantProfileConfiguration.get().getAlarmsTtlDays() == 0) {
- continue;
- }
+ PageDataIterable tenants = new PageDataIterable<>(tenantService::findTenantsIds, 10_000);
+ for (TenantId tenantId : tenants) {
+ try {
+ cleanUp(tenantId);
+ } catch (Exception e) {
+ log.warn("Failed to clean up alarms by ttl for tenant {}", tenantId, e);
+ }
+ }
+ }
- long ttl = TimeUnit.DAYS.toMillis(tenantProfileConfiguration.get().getAlarmsTtlDays());
- long expirationTime = System.currentTimeMillis() - ttl;
+ private void cleanUp(TenantId tenantId) {
+ if (!partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) {
+ return;
+ }
- long totalRemoved = 0;
- while (true) {
- PageData toRemove = alarmDao.findAlarmsIdsByEndTsBeforeAndTenantId(expirationTime, tenantId, removalBatchRequest);
- toRemove.getData().forEach(alarmId -> {
- relationService.deleteEntityRelations(tenantId, alarmId);
- Alarm alarm = alarmService.deleteAlarm(tenantId, alarmId).getAlarm();
- entityActionService.pushEntityActionToRuleEngine(alarm.getOriginator(), alarm, tenantId, null, ActionType.ALARM_DELETE, null);
- });
+ Optional tenantProfileConfiguration = tenantProfileCache.get(tenantId).getProfileConfiguration();
+ if (tenantProfileConfiguration.isEmpty() || tenantProfileConfiguration.get().getAlarmsTtlDays() == 0) {
+ return;
+ }
- totalRemoved += toRemove.getTotalElements();
- if (!toRemove.hasNext()) {
- break;
- }
- }
+ long ttl = TimeUnit.DAYS.toMillis(tenantProfileConfiguration.get().getAlarmsTtlDays());
+ long expirationTime = System.currentTimeMillis() - ttl;
- if (totalRemoved > 0) {
- log.info("Removed {} outdated alarm(s) for tenant {} older than {}", totalRemoved, tenantId, new Date(expirationTime));
+ PageLink removalBatchRequest = new PageLink(removalBatchSize, 0);
+ long totalRemoved = 0;
+ while (true) {
+ PageData toRemove = alarmDao.findAlarmsIdsByEndTsBeforeAndTenantId(expirationTime, tenantId, removalBatchRequest);
+ for (AlarmId alarmId : toRemove.getData()) {
+ relationService.deleteEntityRelations(tenantId, alarmId);
+ Alarm alarm = alarmService.delAlarm(tenantId, alarmId).getAlarm();
+ if (alarm != null) {
+ entityActionService.pushEntityActionToRuleEngine(alarm.getOriginator(), alarm, tenantId, null, ActionType.ALARM_DELETE, null);
+ totalRemoved++;
}
}
-
- tenantsBatchRequest = tenantsBatchRequest.nextPageLink();
- } while (tenantsIds.hasNext());
+ if (!toRemove.hasNext()) {
+ break;
+ }
+ }
+ if (totalRemoved > 0) {
+ log.info("Removed {} outdated alarm(s) for tenant {} older than {}", totalRemoved, tenantId, new Date(expirationTime));
+ }
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java
index 77efc6555a..0f6bbcf83a 100644
--- a/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java
+++ b/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java
@@ -21,7 +21,6 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.queue.discovery.PartitionService;
-import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.concurrent.TimeUnit;
diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java
index 25e2e8d289..86b69dbb07 100644
--- a/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java
+++ b/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java
@@ -22,7 +22,6 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.util.TbCoreComponent;
-import org.thingsboard.server.service.ttl.AbstractCleanUpService;
@TbCoreComponent
@Slf4j
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketMsgEndpoint.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketMsgEndpoint.java
index dd54b01580..bc45639210 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketMsgEndpoint.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketMsgEndpoint.java
@@ -16,7 +16,6 @@
package org.thingsboard.server.service.ws;
import org.springframework.web.socket.CloseStatus;
-import org.thingsboard.server.service.ws.WebSocketSessionRef;
import java.io.IOException;
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java
index a31c6a0ca0..1ebd6101f0 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java
@@ -18,8 +18,6 @@ package org.thingsboard.server.service.ws;
import org.springframework.web.socket.CloseStatus;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
-import org.thingsboard.server.service.ws.SessionEvent;
-import org.thingsboard.server.service.ws.WebSocketSessionRef;
/**
* Created by ashvayka on 27.03.18.
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java
index 2b27166106..e62ebf1606 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java
@@ -18,8 +18,8 @@ package org.thingsboard.server.service.ws.notification;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.notification.cmd.MarkAllNotificationsAsReadCmd;
import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd;
-import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd;
import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd;
+import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.UnsubscribeCmd;
public interface NotificationCommandsHandler {
diff --git a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryPluginCmdsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryPluginCmdsWrapper.java
index 4daf2a939b..5b2c12afcd 100644
--- a/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryPluginCmdsWrapper.java
+++ b/application/src/main/java/org/thingsboard/server/service/ws/telemetry/cmd/TelemetryPluginCmdsWrapper.java
@@ -21,10 +21,10 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v1.GetHistoryCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUnsubscribeCmd;
-import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUnsubscribeCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmDataUnsubscribeCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
+import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUnsubscribeCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
diff --git a/application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java b/application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java
index ed898ae999..6a222870b0 100644
--- a/application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java
@@ -20,7 +20,6 @@ import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
-import org.thingsboard.server.queue.util.TbCoreComponent;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
import java.lang.reflect.Field;
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 1bc8c19a82..cc2b075cf1 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -132,7 +132,7 @@ security:
githubMapper:
emailUrl: "${SECURITY_OAUTH2_GITHUB_MAPPER_EMAIL_URL_KEY:https://api.github.com/user/emails}"
java_cacerts:
- path: "${SECURITY_JAVA_CACERTS_PATH:${java.home}${file.separator}lib${file.separator}security${file.separator}cacerts}"
+ path: "${SECURITY_JAVA_CACERTS_PATH:${java.home}/lib/security/cacerts}"
password: "${SECURITY_JAVA_CACERTS_PASSWORD:changeit}"
# Usage statistics parameters
@@ -144,7 +144,11 @@ usage:
interval: "${USAGE_STATS_REPORT_INTERVAL:10}"
check:
cycle: "${USAGE_STATS_CHECK_CYCLE:60000}"
+ # In milliseconds. Default value is 3 minutes
gauge_report_interval: "${USAGE_STATS_GAUGE_REPORT_INTERVAL:180000}"
+ devices:
+ # In seconds, default value is 1 minutes. When changing, in cluster mode, make sure usage.stats.gauge_report_interval is set to x2-x3 of this value
+ report_interval: "${DEVICES_STATS_REPORT_INTERVAL:60}"
# UI parameters
ui:
@@ -155,7 +159,7 @@ ui:
# Help parameters
help:
# Base url for UI help assets
- base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.4.4}"
+ base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.5}"
database:
ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records
@@ -445,6 +449,9 @@ cache:
notificationSettings:
timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_SETTINGS_TTL:10}"
maxSize: "${CACHE_SPECS_NOTIFICATION_SETTINGS_MAX_SIZE:1000}"
+ sentNotifications:
+ timeToLiveInMinutes: "${CACHE_SPECS_SENT_NOTIFICATIONS_TTL:1440}"
+ maxSize: "${CACHE_SPECS_SENT_NOTIFICATIONS_MAX_SIZE:10000}"
attributes:
timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}"
maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}"
@@ -645,6 +652,10 @@ state:
# Should be greater then transport.sessions.report_timeout
defaultInactivityTimeoutInSec: "${DEFAULT_INACTIVITY_TIMEOUT:600}"
defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:60}"
+ # Controls whether we store device 'active' flag in attributes (default) or telemetry.
+ # If you device to change this parameter, you should re-create the device info view as one of the following:
+ # If 'persistToTelemetry' is changed from 'false' to 'true': 'CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM device_info_active_ts_view;'
+ # If 'persistToTelemetry' is changed from 'true' to 'false': 'CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM device_info_active_attribute_view;'
persistToTelemetry: "${PERSIST_STATE_TO_TELEMETRY:false}"
tbel:
diff --git a/application/src/test/java/org/thingsboard/server/actors/stats/StatsActorTest.java b/application/src/test/java/org/thingsboard/server/actors/stats/StatsActorTest.java
index 0ae9227077..ff247e72a0 100644
--- a/application/src/test/java/org/thingsboard/server/actors/stats/StatsActorTest.java
+++ b/application/src/test/java/org/thingsboard/server/actors/stats/StatsActorTest.java
@@ -18,7 +18,6 @@ package org.thingsboard.server.actors.stats;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.actors.ActorSystemContext;
-import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.event.Event;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.event.EventService;
diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractInMemoryStorageTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractInMemoryStorageTest.java
index 89e5098853..b107d99396 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractInMemoryStorageTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractInMemoryStorageTest.java
@@ -16,9 +16,6 @@
package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j;
-import org.junit.After;
-import org.junit.Before;
-import org.thingsboard.server.queue.memory.InMemoryStorage;
@Slf4j
public abstract class AbstractInMemoryStorageTest {
diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
index b3025a2d64..6c42c7604d 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
@@ -18,10 +18,7 @@ package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j;
import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean;
-import org.thingsboard.server.actors.service.ActorService;
-import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName;
@@ -41,7 +38,6 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.model.ModelConstants;
-import org.thingsboard.server.service.session.DeviceSessionCacheService;
import java.util.ArrayList;
import java.util.List;
diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
index f49e6671c9..c7580867e0 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
@@ -74,6 +74,7 @@ import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration;
@@ -103,16 +104,20 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
+import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
+import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
+import org.thingsboard.server.service.entitiy.tenant.profile.TbTenantProfileService;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest;
import org.thingsboard.server.service.security.auth.rest.LoginRequest;
import java.io.IOException;
import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -123,6 +128,7 @@ import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -202,6 +208,9 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
@Autowired
private TenantProfileService tenantProfileService;
+ @Autowired
+ private TbTenantProfileService tbTenantProfileService;
+
@Autowired
public TimeseriesService tsService;
@@ -903,18 +912,27 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
throw new AssertionError("Unexpected status " + mvcResult.getResponse().getStatus());
}
- protected T getFieldValue(Object target, String fieldName) throws Exception {
+ protected static T getFieldValue(Object target, String fieldName) throws Exception {
Field field = target.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return (T) field.get(target);
}
- protected void setStaticFieldValue(Class> targetCls, String fieldName, Object value) throws Exception {
+ protected static void setStaticFieldValue(Class> targetCls, String fieldName, Object value) throws Exception {
Field field = targetCls.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(null, value);
}
+ protected static void setStaticFinalFieldValue(Class> targetCls, String fieldName, Object value) throws Exception {
+ Field field = targetCls.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ Field modifiers = Field.class.getDeclaredField("modifiers");
+ modifiers.setAccessible(true);
+ modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+ field.set(null, value);
+ }
+
protected int getDeviceActorSubscriptionCount(DeviceId deviceId, FeatureType featureType) {
DeviceActorMessageProcessor processor = getDeviceActorProcessor(deviceId);
Map subscriptions = (Map) ReflectionTestUtils.getField(processor, getMapName(featureType));
@@ -951,4 +969,13 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
return (DeviceActorMessageProcessor) ReflectionTestUtils.getField(actor, "processor");
}
+ protected void updateDefaultTenantProfile(Consumer updater) throws ThingsboardException {
+ TenantProfile tenantProfile = tenantProfileService.findDefaultTenantProfile(TenantId.SYS_TENANT_ID);
+ TenantProfileData profileData = tenantProfile.getProfileData();
+ DefaultTenantProfileConfiguration profileConfiguration = (DefaultTenantProfileConfiguration) profileData.getConfiguration();
+ updater.accept(profileConfiguration);
+ tenantProfile.setProfileData(profileData);
+ tbTenantProfileService.save(TenantId.SYS_TENANT_ID, tenantProfile, null);
+ }
+
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java
index b8c091de05..98446b025d 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java
@@ -36,7 +36,6 @@ import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentInfo;
import org.thingsboard.server.common.data.alarm.AlarmCommentType;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
-import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.page.PageData;
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
index 7cf6d7109a..930c4881f9 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
@@ -29,7 +29,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
@@ -180,7 +179,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class);
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
- tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
+ tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGNED);
alarm = updatedAlarm;
alarm.setAssigneeId(null);
@@ -192,7 +191,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class);
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
- tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_UNASSIGN);
+ tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_UNASSIGNED);
}
@Test
@@ -414,7 +413,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
- tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
+ tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGNED);
}
@Test
@@ -445,7 +444,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
- tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
+ tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGNED);
logout();
@@ -462,7 +461,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
- tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_ASSIGN);
+ tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_ASSIGNED);
}
@Test
@@ -480,7 +479,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
- tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
+ tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGNED);
beforeAssignmentTs = System.currentTimeMillis();
Thread.sleep(2);
@@ -491,7 +490,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
- tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_UNASSIGN);
+ tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_UNASSIGNED);
}
@Test
@@ -509,7 +508,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
- tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
+ tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGNED);
logout();
loginCustomerUser();
@@ -525,7 +524,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
- tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_UNASSIGN);
+ tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_UNASSIGNED);
}
@Test
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java
index 6ffc216dd3..42bfb236df 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java
@@ -38,6 +38,7 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
@@ -72,7 +73,6 @@ import org.thingsboard.server.service.gateway_device.GatewayNotificationsService
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
@@ -1287,8 +1287,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
savedDevice.getId().getId().toString(), savedEdge.getId().getId().toString(), savedEdge.getName());
testNotificationUpdateGatewayNever();
- pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/devices?",
- PAGE_DATA_DEVICE_TYPE_REF, new PageLink(100));
+ PageData pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/devices?",
+ new TypeReference<>() {}, new PageLink(100));
Assert.assertEquals(1, pageData.getData().size());
@@ -1303,7 +1303,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
testNotificationUpdateGatewayNever();
pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/devices?",
- PAGE_DATA_DEVICE_TYPE_REF, new PageLink(100));
+ new TypeReference<>() {}, new PageLink(100));
Assert.assertEquals(0, pageData.getData().size());
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java
index b875a638a5..c591bb6a16 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java
@@ -49,10 +49,10 @@ import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
-import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@TestPropertySource(properties = {
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java
index f5d8303a80..ed25395b3b 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java
@@ -55,7 +55,6 @@ import org.thingsboard.server.common.data.security.Authority;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseRpcControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseRpcControllerTest.java
index fb2f52129e..486ba32c4e 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseRpcControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseRpcControllerTest.java
@@ -24,7 +24,9 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.test.web.servlet.MvcResult;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.common.data.*;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.rpc.Rpc;
import org.thingsboard.server.common.data.rpc.RpcStatus;
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseTelemetryControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseTelemetryControllerTest.java
index 50ad731a79..0676ccf83c 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseTelemetryControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseTelemetryControllerTest.java
@@ -16,7 +16,6 @@
package org.thingsboard.server.controller;
import org.junit.Test;
-import org.stringtemplate.v4.ST;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest;
import org.thingsboard.server.common.data.security.DeviceCredentials;
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
index 2d93e16f4f..5340101e6f 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
@@ -32,8 +32,8 @@ import org.springframework.http.HttpHeaders;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.server.common.data.Customer;
-import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Dashboard;
+import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
diff --git a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
index 5d66342dca..ede6c69fd4 100644
--- a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
+++ b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
@@ -15,9 +15,9 @@
*/
package org.thingsboard.server.controller;
-import lombok.Getter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
diff --git a/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java b/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java
index 536f7751c1..8c8074c35d 100644
--- a/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java
@@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettings;
import org.thingsboard.server.common.data.security.model.mfa.account.EmailTwoFaAccountConfig;
import org.thingsboard.server.common.data.security.model.mfa.account.SmsTwoFaAccountConfig;
@@ -51,7 +52,6 @@ import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService;
import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager;
import org.thingsboard.server.service.security.auth.rest.LoginRequest;
-import org.thingsboard.server.common.data.security.model.JwtPair;
import java.time.Duration;
import java.util.Arrays;
diff --git a/application/src/test/java/org/thingsboard/server/controller/sql/EntityViewControllerSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/sql/EntityViewControllerSqlTest.java
index 7498eec6bc..6e13b83334 100644
--- a/application/src/test/java/org/thingsboard/server/controller/sql/EntityViewControllerSqlTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/sql/EntityViewControllerSqlTest.java
@@ -15,14 +15,9 @@
*/
package org.thingsboard.server.controller.sql;
-import org.junit.Assert;
-import org.junit.Test;
-import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.controller.BaseEntityViewControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
-import java.util.Arrays;
-
/**
* Created by Victor Basanets on 8/27/2017.
*/
diff --git a/application/src/test/java/org/thingsboard/server/controller/sql/TwoFactorAuthConfigSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/sql/TwoFactorAuthConfigSqlTest.java
index 2261665690..7082268b69 100644
--- a/application/src/test/java/org/thingsboard/server/controller/sql/TwoFactorAuthConfigSqlTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/sql/TwoFactorAuthConfigSqlTest.java
@@ -15,8 +15,8 @@
*/
package org.thingsboard.server.controller.sql;
-import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.controller.TwoFactorAuthConfigTest;
+import org.thingsboard.server.dao.service.DaoSqlTest;
@DaoSqlTest
public class TwoFactorAuthConfigSqlTest extends TwoFactorAuthConfigTest {
diff --git a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java
index f4370d615e..42a682bd9c 100644
--- a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java
+++ b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java
@@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest;
@@ -96,7 +97,6 @@ import java.util.TreeMap;
import java.util.UUID;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
@TestPropertySource(properties = {
"edges.enabled=true",
@@ -322,9 +322,9 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
UUID deviceUUID = new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB());
Device device = doGet("/api/device/" + deviceUUID, Device.class);
Assert.assertNotNull(device);
- List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?",
- new TypeReference>() {}, new PageLink(100)).getData();
- Assert.assertTrue(edgeDevices.contains(device));
+ List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?",
+ new TypeReference>() {}, new PageLink(100)).getData();
+ Assert.assertTrue(edgeDevices.stream().map(DeviceInfo::getId).anyMatch(id -> id.equals(device.getId())));
testAutoGeneratedCodeByProtobuf(deviceUpdateMsg);
}
@@ -487,10 +487,10 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
}
protected Device findDeviceByName(String deviceName) throws Exception {
- List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?",
- new TypeReference>() {
+ List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?",
+ new TypeReference>() {
}, new PageLink(100)).getData();
- Optional foundDevice = edgeDevices.stream().filter(d -> d.getName().equals(deviceName)).findAny();
+ Optional foundDevice = edgeDevices.stream().filter(d -> d.getName().equals(deviceName)).findAny();
Assert.assertTrue(foundDevice.isPresent());
Device device = foundDevice.get();
Assert.assertEquals(deviceName, device.getName());
diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java
index 904a6246ab..1fbd483aa4 100644
--- a/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java
+++ b/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java
@@ -30,6 +30,7 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.StringUtils;
@@ -378,18 +379,18 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
sendAttributesRequestAndVerify(device, DataConstants.SHARED_SCOPE, "{\"key2\":\"value2\"}",
"key2", "value2");
- doDelete("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/" + DataConstants.SERVER_SCOPE, "keys","key1, inactivityTimeout");
+ doDelete("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/" + DataConstants.SERVER_SCOPE, "keys", "key1, inactivityTimeout");
doDelete("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/" + DataConstants.SHARED_SCOPE, "keys", "key2");
}
@Test
public void testSendDeleteDeviceOnEdgeToCloud() throws Exception {
- Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge();
+ Device savedDevice = saveDeviceOnCloudAndVerifyDeliveryToEdge();
UplinkMsg.Builder upLinkMsgBuilder = UplinkMsg.newBuilder();
DeviceUpdateMsg.Builder deviceDeleteMsgBuilder = DeviceUpdateMsg.newBuilder();
deviceDeleteMsgBuilder.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE);
- deviceDeleteMsgBuilder.setIdMSB(device.getId().getId().getMostSignificantBits());
- deviceDeleteMsgBuilder.setIdLSB(device.getId().getId().getLeastSignificantBits());
+ deviceDeleteMsgBuilder.setIdMSB(savedDevice.getId().getId().getMostSignificantBits());
+ deviceDeleteMsgBuilder.setIdLSB(savedDevice.getId().getId().getLeastSignificantBits());
testAutoGeneratedCodeByProtobuf(deviceDeleteMsgBuilder);
upLinkMsgBuilder.addDeviceUpdateMsg(deviceDeleteMsgBuilder.build());
@@ -398,12 +399,12 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(upLinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
- device = doGet("/api/device/" + device.getUuidId(), Device.class);
- Assert.assertNotNull(device);
- List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?",
- new TypeReference>() {
+ DeviceInfo deviceInfo = doGet("/api/device/info/" + savedDevice.getUuidId(), DeviceInfo.class);
+ Assert.assertNotNull(deviceInfo);
+ List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?",
+ new TypeReference>() {
}, new PageLink(100)).getData();
- Assert.assertFalse(edgeDevices.contains(device));
+ Assert.assertFalse(edgeDevices.contains(deviceInfo));
}
@Test
@@ -455,7 +456,8 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
Assert.assertEquals(timeseriesValue, timeseries.get(timeseriesKey).get(0).get("value"));
String attributeValuesUrl = "/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/attributes/" + DataConstants.SERVER_SCOPE;
- List