diff --git a/application/src/main/data/json/system/widget_types/update_server_string_attribute.json b/application/src/main/data/json/system/widget_types/update_server_string_attribute.json index 193300cb06..d8c33bffcc 100644 --- a/application/src/main/data/json/system/widget_types/update_server_string_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_server_string_attribute.json @@ -11,7 +11,7 @@ "resources": [], "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n settings.isRequired = utils.defaultValue(settings.isRequired, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n \n var validators = [$scope.validators.minLength(settings.minLength),\n $scope.validators.maxLength(settings.maxLength)];\n \n if (settings.isRequired) {\n validators.push($scope.validators.required);\n }\n \n $scope.attributeUpdateFormGroup = $scope.fb.group({\n currentValue: [undefined, validators]\n });\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n \n var value = $scope.attributeUpdateFormGroup.get('currentValue').value;\n \n if (!$scope.attributeUpdateFormGroup.get('currentValue').value.length) {\n value = null;\n }\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SERVER_SCOPE',\n [\n {\n key: $scope.currentKey,\n value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}", + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n settings.isRequired = utils.defaultValue(settings.isRequired, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n \n var validators = [];\n if (utils.isDefinedAndNotNull(settings.minLength)) {\n validators.push($scope.validators.minLength(settings.minLength));\n }\n if (utils.isDefinedAndNotNull(settings.maxLength)) {\n validators.push($scope.validators.maxLength(settings.maxLength));\n }\n if (settings.isRequired) {\n validators.push($scope.validators.required);\n }\n \n $scope.attributeUpdateFormGroup = $scope.fb.group({\n currentValue: [undefined, validators]\n });\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n \n var value = $scope.attributeUpdateFormGroup.get('currentValue').value;\n \n if (!$scope.attributeUpdateFormGroup.get('currentValue').value.length) {\n value = null;\n }\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SERVER_SCOPE',\n [\n {\n key: $scope.currentKey,\n value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "", "dataKeySettingsSchema": "{}\n", "settingsDirective": "tb-update-string-attribute-widget-settings", diff --git a/application/src/main/data/json/system/widget_types/update_shared_string_attribute.json b/application/src/main/data/json/system/widget_types/update_shared_string_attribute.json index 4c5333ec59..7578838066 100644 --- a/application/src/main/data/json/system/widget_types/update_shared_string_attribute.json +++ b/application/src/main/data/json/system/widget_types/update_shared_string_attribute.json @@ -11,7 +11,7 @@ "resources": [], "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n settings.isRequired = utils.defaultValue(settings.isRequired, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\n \n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n \n var validators = [$scope.validators.minLength(settings.minLength),\n $scope.validators.maxLength(settings.maxLength)];\n \n if (settings.isRequired) {\n validators.push($scope.validators.required);\n }\n \n $scope.attributeUpdateFormGroup = $scope.fb.group({\n currentValue: [undefined, validators]\n });\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === 'DEVICE') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n \n $scope.entityDetected = true;\n }\n } else {\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n var value = $scope.attributeUpdateFormGroup.get('currentValue').value;\n \n if (!$scope.attributeUpdateFormGroup.get('currentValue').value.length) {\n value = null;\n }\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SHARED_SCOPE',\n [\n {\n key: $scope.currentKey,\n value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}", + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n settings.isRequired = utils.defaultValue(settings.isRequired, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\n \n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-attribute-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n \n var validators = [];\n if (utils.isDefinedAndNotNull(settings.minLength)) {\n validators.push($scope.validators.minLength(settings.minLength));\n }\n if (utils.isDefinedAndNotNull(settings.maxLength)) {\n validators.push($scope.validators.maxLength(settings.maxLength));\n }\n if (settings.isRequired) {\n validators.push($scope.validators.required);\n }\n \n $scope.attributeUpdateFormGroup = $scope.fb.group({\n currentValue: [undefined, validators]\n });\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === 'DEVICE') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n \n $scope.entityDetected = true;\n }\n } else {\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n var value = $scope.attributeUpdateFormGroup.get('currentValue').value;\n \n if (!$scope.attributeUpdateFormGroup.get('currentValue').value.length) {\n value = null;\n }\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SHARED_SCOPE',\n [\n {\n key: $scope.currentKey,\n value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "", "dataKeySettingsSchema": "{}\n", "settingsDirective": "tb-update-string-attribute-widget-settings", diff --git a/application/src/main/data/json/system/widget_types/update_string_timeseries.json b/application/src/main/data/json/system/widget_types/update_string_timeseries.json index 43a96d6f69..d54224ef36 100644 --- a/application/src/main/data/json/system/widget_types/update_string_timeseries.json +++ b/application/src/main/data/json/system/widget_types/update_string_timeseries.json @@ -11,7 +11,7 @@ "resources": [], "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", - "controllerScript": "let $scope;\nlet settings;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.minLength(settings.minLength),\n $scope.validators.maxLength(settings.maxLength)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n );\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}\n", + "controllerScript": "let $scope;\nlet settings;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n \n var validators = [];\n if (utils.isDefinedAndNotNull(settings.minLength)) {\n validators.push($scope.validators.minLength(settings.minLength));\n }\n if (utils.isDefinedAndNotNull(settings.maxLength)) {\n validators.push($scope.validators.maxLength(settings.maxLength));\n }\n if (settings.isRequired) {\n validators.push($scope.validators.required);\n }\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, validators]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n );\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n\n}\n", "settingsSchema": "", "dataKeySettingsSchema": "{}\n", "settingsDirective": "tb-update-string-attribute-widget-settings", diff --git a/application/src/test/java/org/thingsboard/server/edge/NotificationEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/NotificationEdgeTest.java index 890ec00913..6d4ebb3676 100644 --- a/application/src/test/java/org/thingsboard/server/edge/NotificationEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/NotificationEdgeTest.java @@ -18,7 +18,6 @@ package org.thingsboard.server.edge; import com.google.protobuf.AbstractMessage; import org.junit.Assert; import org.junit.Test; -import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; diff --git a/application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java index c25410b562..beae36718d 100644 --- a/application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java @@ -59,12 +59,7 @@ public class OAuth2EdgeTest extends AbstractEdgeTest { oAuth2Info.setEnabled(false); oAuth2Info.setEdgeEnabled(false); doPost("/api/oauth2/config", oAuth2Info, OAuth2Info.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof OAuth2UpdateMsg); - oAuth2UpdateMsg = (OAuth2UpdateMsg) latestMessage; - result = JacksonUtil.fromString(oAuth2UpdateMsg.getEntity(), OAuth2Info.class, true); - Assert.assertEquals(oAuth2Info, result); + Assert.assertFalse(edgeImitator.waitForMessages(5)); edgeImitator.ignoreType(OAuth2UpdateMsg.class); loginTenantAdmin(); diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/CacheSpecsMap.java b/common/cache/src/main/java/org/thingsboard/server/cache/CacheSpecsMap.java index 583a871fe1..abca4624e6 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/CacheSpecsMap.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/CacheSpecsMap.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.cache; +import jakarta.annotation.PostConstruct; import lombok.Data; import lombok.Getter; import org.springframework.beans.factory.annotation.Value; @@ -22,7 +23,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.thingsboard.server.common.data.CacheConstants; -import jakarta.annotation.PostConstruct; import java.util.Map; @Configuration diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java index abfbb398c9..8e7bc86d53 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java @@ -29,7 +29,6 @@ import org.springframework.data.redis.core.types.Expiration; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.thingsboard.server.common.data.FstStatsService; -import redis.clients.jedis.Connection; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.util.JedisClusterCRC16; @@ -40,6 +39,8 @@ import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.function.Supplier; @Slf4j public abstract class RedisTbTransactionalCache implements TbTransactionalCache { @@ -57,6 +58,7 @@ public abstract class RedisTbTransactionalCache valueSerializer; private final Expiration evictExpiration; private final Expiration cacheTtl; + private final boolean cacheEnabled; public RedisTbTransactionalCache(String cacheName, CacheSpecsMap cacheSpecsMap, @@ -73,10 +75,19 @@ public abstract class RedisTbTransactionalCache Expiration.from(t, TimeUnit.MINUTES)) .orElseGet(Expiration::persistent); + this.cacheEnabled = Optional.ofNullable(cacheSpecsMap) + .map(CacheSpecsMap::getSpecs) + .map(x -> x.get(cacheName)) + .map(CacheSpecs::getMaxSize) + .map(size -> size > 0) + .orElse(false); } @Override public TbCacheValueWrapper get(K key) { + if (!cacheEnabled) { + return null; + } try (var connection = connectionFactory.getConnection()) { byte[] rawKey = getRawKey(key); byte[] rawValue = connection.get(rawKey); @@ -98,6 +109,9 @@ public abstract class RedisTbTransactionalCache keys) { + if (!cacheEnabled) { + return; + } //Redis expects at least 1 key to delete. Otherwise - ERR wrong number of arguments for 'del' command if (keys.isEmpty()) { return; @@ -130,6 +153,9 @@ public abstract class RedisTbTransactionalCache(this, connection); } + @Override + public R getAndPutInTransaction(K key, Supplier dbCall, Function cacheValueToResult, Function dbValueToCacheValue, boolean cacheNullValue) { + if (!cacheEnabled) { + return dbCall.get(); + } + return TbTransactionalCache.super.getAndPutInTransaction(key, dbCall, cacheValueToResult, dbValueToCacheValue, cacheNullValue); + } + private RedisConnection getConnection(byte[] rawKey) { if (!connectionFactory.isRedisClusterAware()) { return connectionFactory.getConnection(); @@ -214,6 +248,9 @@ public abstract class RedisTbTransactionalCache dbCall, boolean cacheNullValue) { + return getAndPutInTransaction(key, dbCall, Function.identity(), Function.identity(), cacheNullValue); + } + + default R getAndPutInTransaction(K key, Supplier dbCall, Function cacheValueToResult, Function dbValueToCacheValue, boolean cacheNullValue) { TbCacheValueWrapper cacheValueWrapper = get(key); if (cacheValueWrapper != null) { - return cacheValueWrapper.get(); + V cacheValue = cacheValueWrapper.get(); + return cacheValue != null ? cacheValueToResult.apply(cacheValue) : null; } var cacheTransaction = newTransactionForKey(key); try { - V dbValue = dbCall.get(); + R dbValue = dbCall.get(); if (dbValue != null || cacheNullValue) { - cacheTransaction.putIfAbsent(key, dbValue); + cacheTransaction.putIfAbsent(key, dbValueToCacheValue.apply(dbValue)); cacheTransaction.commit(); return dbValue; } else { @@ -94,27 +99,4 @@ public interface TbTransactionalCache R getAndPutInTransaction(K key, Supplier dbCall, Function cacheValueToResult, Function dbValueToCacheValue, boolean cacheNullValue) { - TbCacheValueWrapper cacheValueWrapper = get(key); - if (cacheValueWrapper != null) { - var cacheValue = cacheValueWrapper.get(); - return cacheValue == null ? null : cacheValueToResult.apply(cacheValue); - } - var cacheTransaction = newTransactionForKey(key); - try { - R dbValue = dbCall.get(); - if (dbValue != null || cacheNullValue) { - cacheTransaction.putIfAbsent(key, dbValueToCacheValue.apply(dbValue)); - cacheTransaction.commit(); - return dbValue; - } else { - cacheTransaction.rollback(); - return null; - } - } catch (Throwable e) { - cacheTransaction.rollback(); - throw e; - } - } - } diff --git a/common/cache/src/test/java/org/thingsboard/server/cache/CacheSpecsMapTest.java b/common/cache/src/test/java/org/thingsboard/server/cache/CacheSpecsMapTest.java index aec5e01f39..eb83cc2821 100644 --- a/common/cache/src/test/java/org/thingsboard/server/cache/CacheSpecsMapTest.java +++ b/common/cache/src/test/java/org/thingsboard/server/cache/CacheSpecsMapTest.java @@ -62,4 +62,5 @@ public class CacheSpecsMapTest { public void givenCacheConfig_whenCacheManagerReady_thenVerifyNonExistedCaches() { assertThat(cacheManager.getCache("rainbows_and_unicorns")).isNull(); } -} \ No newline at end of file + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java index c39c603a9e..4cf3d5a790 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Data; +import lombok.ToString; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; @@ -53,9 +54,12 @@ public class AlarmCreateOrUpdateActiveRequest implements AlarmModificationReques private long startTs; @Schema(description = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522") private long endTs; + + @ToString.Exclude @NoXss @Schema(description = "JSON object with alarm details") private JsonNode details; + @Valid @Schema(description = "JSON object with propagation details") private AlarmPropagationInfo propagation; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java index 8eb418d6dc..1556e5fa73 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Data; +import lombok.ToString; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; @@ -47,9 +48,12 @@ public class AlarmUpdateRequest implements AlarmModificationRequest { private long startTs; @Schema(description = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522") private long endTs; + + @ToString.Exclude @NoXss @Schema(description = "JSON object with alarm details") private JsonNode details; + @Valid @Schema(description = "JSON object with propagation details") private AlarmPropagationInfo propagation; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index b0bf2cc666..133eb70521 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -342,9 +342,12 @@ public class JpaAlarmDao extends JpaAbstractDao implements A @Override public AlarmApiCallResult createOrUpdateActiveAlarm(AlarmCreateOrUpdateActiveRequest request, boolean alarmCreationEnabled) { + UUID tenantUUID = request.getTenantId().getId(); + log.debug("[{}] createOrUpdateActiveAlarm [{}] {}", tenantUUID, alarmCreationEnabled, request); + AlarmPropagationInfo ap = getSafePropagationInfo(request.getPropagation()); return toAlarmApiResult(alarmRepository.createOrUpdateActiveAlarm( - request.getTenantId().getId(), + tenantUUID, request.getCustomerId() != null ? request.getCustomerId().getId() : CustomerId.NULL_UUID, request.getEdgeAlarmId() != null ? request.getEdgeAlarmId().getId() : UUID.randomUUID(), System.currentTimeMillis(), @@ -364,10 +367,14 @@ public class JpaAlarmDao extends JpaAbstractDao implements A @Override public AlarmApiCallResult updateAlarm(AlarmUpdateRequest request) { + UUID tenantUUID = request.getTenantId().getId(); + UUID alarmUUID = request.getAlarmId().getId(); + log.debug("[{}][{}] updateAlarm {}", tenantUUID, alarmUUID, request); + AlarmPropagationInfo ap = getSafePropagationInfo(request.getPropagation()); return toAlarmApiResult(alarmRepository.updateAlarm( - request.getTenantId().getId(), - request.getAlarmId().getId(), + tenantUUID, + alarmUUID, request.getSeverity().name(), request.getStartTs(), request.getEndTs(), getDetailsAsString(request.getDetails()), @@ -380,11 +387,13 @@ public class JpaAlarmDao extends JpaAbstractDao implements A @Override public AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId id, long ackTs) { + log.debug("[{}][{}] acknowledgeAlarm [{}]", tenantId, id, ackTs); return toAlarmApiResult(alarmRepository.acknowledgeAlarm(tenantId.getId(), id.getId(), ackTs)); } @Override public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId id, long clearTs, JsonNode details) { + log.debug("[{}][{}] clearAlarm [{}]", tenantId, id, clearTs); return toAlarmApiResult(alarmRepository.clearAlarm(tenantId.getId(), id.getId(), clearTs, details != null ? getDetailsAsString(details) : null)); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/cache/RedisTbTransactionalCacheTest.java b/dao/src/test/java/org/thingsboard/server/dao/cache/RedisTbTransactionalCacheTest.java new file mode 100644 index 0000000000..12b6f5d7d1 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/cache/RedisTbTransactionalCacheTest.java @@ -0,0 +1,82 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.cache; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.thingsboard.server.cache.CacheSpecsMap; +import org.thingsboard.server.cache.RedisSslCredentials; +import org.thingsboard.server.cache.TBRedisCacheConfiguration; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.dao.relation.RelationCacheKey; +import org.thingsboard.server.dao.relation.RelationRedisCache; + +import java.util.List; +import java.util.UUID; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = {RelationRedisCache.class, CacheSpecsMap.class, TBRedisCacheConfiguration.class}) +@TestPropertySource(properties = { + "cache.type=redis", + "cache.specs.relations.timeToLiveInMinutes=1440", + "cache.specs.relations.maxSize=0", +}) +@Slf4j +public class RedisTbTransactionalCacheTest { + + @MockBean + private RelationRedisCache relationRedisCache; + @MockBean + private RedisConnectionFactory connectionFactory; + @MockBean + private RedisConnection redisConnection; + @MockBean + private RedisSslCredentials redisSslCredentials; + + @Test + public void testNoOpWhenCacheDisabled() { + when(connectionFactory.getConnection()).thenReturn(redisConnection); + + relationRedisCache.put(createRelationCacheKey(), null); + relationRedisCache.putIfAbsent(createRelationCacheKey(), null); + relationRedisCache.evict(createRelationCacheKey()); + relationRedisCache.evict(List.of(createRelationCacheKey())); + relationRedisCache.getAndPutInTransaction(createRelationCacheKey(), null, false); + relationRedisCache.getAndPutInTransaction(createRelationCacheKey(), null, null, null, false); + relationRedisCache.getOrFetchFromDB(createRelationCacheKey(), null, false, false); + + verify(connectionFactory, never()).getConnection(); + verifyNoInteractions(redisConnection); + } + + private RelationCacheKey createRelationCacheKey() { + return new RelationCacheKey(new DeviceId(UUID.randomUUID()), new DeviceId(UUID.randomUUID()), null, RelationTypeGroup.COMMON); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java index 6e5c7f3030..6c3a359f73 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java @@ -58,7 +58,6 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.user.UserService; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; @@ -245,8 +244,8 @@ public class AlarmServiceTest extends AbstractServiceTest { // Check child relation PageData alarms = alarmService.findAlarmsV2(tenantId, AlarmQueryV2.builder() .affectedEntityId(childId) - .severityList(Arrays.asList(AlarmSeverity.CRITICAL)) - .statusList(Arrays.asList(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink( + .severityList(List.of(AlarmSeverity.CRITICAL)) + .statusList(List.of(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink( new TimePageLink(1, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()); @@ -257,8 +256,8 @@ public class AlarmServiceTest extends AbstractServiceTest { // Check parent relation alarms = alarmService.findAlarmsV2(tenantId, AlarmQueryV2.builder() .affectedEntityId(parentId) - .severityList(Arrays.asList(AlarmSeverity.CRITICAL)) - .statusList(Arrays.asList(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink( + .severityList(List.of(AlarmSeverity.CRITICAL)) + .statusList(List.of(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink( new TimePageLink(1, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()); @@ -272,8 +271,8 @@ public class AlarmServiceTest extends AbstractServiceTest { // Check child relation alarms = alarmService.findAlarmsV2(tenantId, AlarmQueryV2.builder() .affectedEntityId(childId) - .severityList(Arrays.asList(AlarmSeverity.CRITICAL)) - .statusList(Arrays.asList(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink( + .severityList(List.of(AlarmSeverity.CRITICAL)) + .statusList(List.of(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink( new TimePageLink(1, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()); @@ -284,8 +283,8 @@ public class AlarmServiceTest extends AbstractServiceTest { // Check parent relation alarms = alarmService.findAlarmsV2(tenantId, AlarmQueryV2.builder() .affectedEntityId(parentId) - .severityList(Arrays.asList(AlarmSeverity.CRITICAL)) - .statusList(Arrays.asList(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink( + .severityList(List.of(AlarmSeverity.CRITICAL)) + .statusList(List.of(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink( new TimePageLink(1, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()); @@ -298,8 +297,8 @@ public class AlarmServiceTest extends AbstractServiceTest { alarms = alarmService.findAlarmsV2(tenantId, AlarmQueryV2.builder() .affectedEntityId(childId) - .severityList(Arrays.asList(AlarmSeverity.CRITICAL)) - .statusList(Arrays.asList(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.ACK)).pageLink( + .severityList(List.of(AlarmSeverity.CRITICAL)) + .statusList(List.of(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.ACK)).pageLink( new TimePageLink(1, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()); @@ -310,8 +309,8 @@ public class AlarmServiceTest extends AbstractServiceTest { // Check not existing relation alarms = alarmService.findAlarmsV2(tenantId, AlarmQueryV2.builder() .affectedEntityId(childId) - .severityList(Arrays.asList(AlarmSeverity.CRITICAL)) - .statusList(Arrays.asList(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink( + .severityList(List.of(AlarmSeverity.CRITICAL)) + .statusList(List.of(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink( new TimePageLink(1, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()); @@ -323,8 +322,8 @@ public class AlarmServiceTest extends AbstractServiceTest { alarms = alarmService.findAlarmsV2(tenantId, AlarmQueryV2.builder() .affectedEntityId(childId) - .severityList(Arrays.asList(AlarmSeverity.CRITICAL)) - .statusList(Arrays.asList(AlarmSearchStatus.CLEARED, AlarmSearchStatus.ACK)).pageLink( + .severityList(List.of(AlarmSeverity.CRITICAL)) + .statusList(List.of(AlarmSearchStatus.CLEARED, AlarmSearchStatus.ACK)).pageLink( new TimePageLink(1, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()); @@ -334,7 +333,7 @@ public class AlarmServiceTest extends AbstractServiceTest { } @Test - public void testFindAssignedAlarm() throws ExecutionException, InterruptedException { + public void testFindAssignedAlarm() { AssetId parentId = new AssetId(Uuids.timeBased()); AssetId childId = new AssetId(Uuids.timeBased()); @@ -368,7 +367,6 @@ public class AlarmServiceTest extends AbstractServiceTest { PageData alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() .assigneeId(tenantUser.getId()) - .fetchOriginator(true) .pageLink(new TimePageLink(1, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()); @@ -405,7 +403,7 @@ public class AlarmServiceTest extends AbstractServiceTest { } @Test - public void testFindCustomerAlarm() throws ExecutionException, InterruptedException { + public void testFindCustomerAlarm() { Customer customer = new Customer(); customer.setTitle("TestCustomer"); customer.setTenantId(tenantId); @@ -451,10 +449,10 @@ public class AlarmServiceTest extends AbstractServiceTest { pageLink.setStartTs(0L); pageLink.setEndTs(System.currentTimeMillis()); pageLink.setSearchPropagatedAlarms(true); - pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); - pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); + pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); + pageLink.setStatusList(List.of(AlarmSearchStatus.ACTIVE)); - PageData tenantAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Arrays.asList(tenantDevice.getId(), customerDevice.getId())); + PageData tenantAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), List.of(tenantDevice.getId(), customerDevice.getId())); Assert.assertEquals(2, tenantAlarms.getData().size()); PageData customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(customerDevice.getId())); @@ -473,7 +471,7 @@ public class AlarmServiceTest extends AbstractServiceTest { } @Test - public void testFindPropagatedCustomerAssetAlarm() throws ExecutionException, InterruptedException { + public void testFindPropagatedCustomerAssetAlarm() { Customer customer = new Customer(); customer.setTitle("TestCustomer"); customer.setTenantId(tenantId); @@ -525,8 +523,8 @@ public class AlarmServiceTest extends AbstractServiceTest { pageLink.setStartTs(0L); pageLink.setEndTs(System.currentTimeMillis()); pageLink.setSearchPropagatedAlarms(true); - pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); - pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); + pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); + pageLink.setStatusList(List.of(AlarmSearchStatus.ACTIVE)); //TEST that propagated alarms are visible on the asset level. PageData customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(customerAsset.getId())); @@ -576,7 +574,7 @@ public class AlarmServiceTest extends AbstractServiceTest { pageLink.setStartTs(0L); pageLink.setEndTs(System.currentTimeMillis()); pageLink.setSearchPropagatedAlarms(true); - pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); + pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); pageLink.setStatusList(Collections.singletonList(AlarmSearchStatus.ACTIVE)); //TEST that propagated alarms are visible on the asset level. @@ -599,7 +597,7 @@ public class AlarmServiceTest extends AbstractServiceTest { } @Test - public void testFindHighestAlarmSeverity() throws ExecutionException, InterruptedException { + public void testFindHighestAlarmSeverity() { Customer customer = new Customer(); customer.setTitle("TestCustomer"); customer.setTenantId(tenantId); @@ -679,8 +677,8 @@ public class AlarmServiceTest extends AbstractServiceTest { pageLink.setStartTs(0L); pageLink.setEndTs(System.currentTimeMillis()); pageLink.setSearchPropagatedAlarms(false); - pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); - pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); + pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); + pageLink.setStatusList(List.of(AlarmSearchStatus.ACTIVE)); PageData alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId)); @@ -695,8 +693,8 @@ public class AlarmServiceTest extends AbstractServiceTest { pageLink.setStartTs(0L); pageLink.setEndTs(System.currentTimeMillis()); pageLink.setSearchPropagatedAlarms(false); - pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); - pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); + pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); + pageLink.setStatusList(List.of(AlarmSearchStatus.ACTIVE)); alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId)); Assert.assertNotNull(alarms.getData()); @@ -722,8 +720,8 @@ public class AlarmServiceTest extends AbstractServiceTest { pageLink.setStartTs(0L); pageLink.setEndTs(System.currentTimeMillis()); pageLink.setSearchPropagatedAlarms(true); - pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); - pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); + pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); + pageLink.setStatusList(List.of(AlarmSearchStatus.ACTIVE)); alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId)); Assert.assertNotNull(alarms.getData()); @@ -738,8 +736,8 @@ public class AlarmServiceTest extends AbstractServiceTest { pageLink.setStartTs(0L); pageLink.setEndTs(System.currentTimeMillis()); pageLink.setSearchPropagatedAlarms(true); - pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); - pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); + pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); + pageLink.setStatusList(List.of(AlarmSearchStatus.ACTIVE)); alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(parentId)); Assert.assertNotNull(alarms.getData()); @@ -748,7 +746,6 @@ public class AlarmServiceTest extends AbstractServiceTest { PageData alarmsInfoData = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(childId) - .fetchOriginator(true) .status(AlarmStatus.ACTIVE_UNACK).pageLink( new TimePageLink(10, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) @@ -759,7 +756,6 @@ public class AlarmServiceTest extends AbstractServiceTest { alarmsInfoData = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(parentId) - .fetchOriginator(true) .status(AlarmStatus.ACTIVE_UNACK).pageLink( new TimePageLink(10, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) @@ -770,7 +766,6 @@ public class AlarmServiceTest extends AbstractServiceTest { alarmsInfoData = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(parentId2) - .fetchOriginator(true) .status(AlarmStatus.ACTIVE_UNACK).pageLink( new TimePageLink(10, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) @@ -786,8 +781,8 @@ public class AlarmServiceTest extends AbstractServiceTest { pageLink.setStartTs(0L); pageLink.setEndTs(System.currentTimeMillis()); pageLink.setSearchPropagatedAlarms(true); - pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); - pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); + pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); + pageLink.setStatusList(List.of(AlarmSearchStatus.ACTIVE)); alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(parentId)); Assert.assertNotNull(alarms.getData()); @@ -803,8 +798,8 @@ public class AlarmServiceTest extends AbstractServiceTest { pageLink.setStartTs(0L); pageLink.setEndTs(System.currentTimeMillis()); pageLink.setSearchPropagatedAlarms(true); - pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); - pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); + pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); + pageLink.setStatusList(List.of(AlarmSearchStatus.ACTIVE)); alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId)); Assert.assertNotNull(alarms.getData()); @@ -813,7 +808,7 @@ public class AlarmServiceTest extends AbstractServiceTest { } @Test - public void testCountAlarmsUsingAlarmDataQuery() throws ExecutionException, InterruptedException { + public void testCountAlarmsUsingAlarmDataQuery() { AssetId childId = new AssetId(Uuids.timeBased()); long ts = System.currentTimeMillis(); @@ -829,7 +824,7 @@ public class AlarmServiceTest extends AbstractServiceTest { .startTs(0L) .endTs(System.currentTimeMillis()) .searchPropagatedAlarms(false) - .severityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)) + .severityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)) .statusList(List.of(AlarmSearchStatus.ACTIVE)) .build(); @@ -841,7 +836,7 @@ public class AlarmServiceTest extends AbstractServiceTest { .startTs(0L) .endTs(System.currentTimeMillis()) .searchPropagatedAlarms(true) - .severityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)) + .severityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)) .statusList(List.of(AlarmSearchStatus.ACTIVE)) .build(); @@ -866,7 +861,7 @@ public class AlarmServiceTest extends AbstractServiceTest { .startTs(0L) .endTs(System.currentTimeMillis()) .searchPropagatedAlarms(true) - .severityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)) + .severityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)) .statusList(List.of(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.CLEARED)) .build(); @@ -939,6 +934,6 @@ public class AlarmServiceTest extends AbstractServiceTest { ).build()); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(0, alarms.getData().size()); - } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 1db9dd2fc5..26de3ad5e5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -27,10 +27,10 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.web.client.RestClientResponseException; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec; +import org.springframework.web.reactive.function.client.WebClientResponseException; import org.springframework.web.util.UriComponentsBuilder; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbContext; @@ -132,6 +132,7 @@ public class TbHttpClient { this.webClient = WebClient.builder() .clientConnector(new ReactorClientHttpConnector(httpClient)) + .defaultHeader(HttpHeaders.CONNECTION, "close") //In previous realization this header was present! (Added for hotfix "Connection reset") .build(); } catch (SSLException e) { throw new TbNodeException(e); @@ -170,7 +171,7 @@ public class TbHttpClient { BiConsumer onFailure) { try { if (semaphore != null && !semaphore.tryAcquire(config.getReadTimeoutMs(), TimeUnit.MILLISECONDS)) { - ctx.tellFailure(msg, new RuntimeException("Timeout during waiting for reply!")); + onFailure.accept(msg, new RuntimeException("Timeout during waiting for reply!")); return; } @@ -183,10 +184,10 @@ public class TbHttpClient { .uri(uri) .headers(headers -> prepareHeaders(headers, msg)); - if (HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method) || - HttpMethod.PATCH.equals(method) || HttpMethod.DELETE.equals(method) || + if ((HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method) || + HttpMethod.PATCH.equals(method) || HttpMethod.DELETE.equals(method)) && !config.isIgnoreRequestBody()) { - request.body(BodyInserters.fromValue(getData(msg, config.isIgnoreRequestBody(), config.isParseToPlainText()))); + request.body(BodyInserters.fromValue(getData(msg, config.isParseToPlainText()))); } request @@ -236,11 +237,9 @@ public class TbHttpClient { return uri; } - private String getData(TbMsg tbMsg, boolean ignoreBody, boolean parseToPlainText) { - if (!ignoreBody && parseToPlainText) { - return JacksonUtil.toPlainText(tbMsg.getData()); - } - return tbMsg.getData(); + private Object getData(TbMsg tbMsg, boolean parseToPlainText) { + String data = tbMsg.getData(); + return parseToPlainText ? JacksonUtil.toPlainText(data) : JacksonUtil.toJsonNode(data); } private TbMsg processResponse(TbContext ctx, TbMsg origMsg, ResponseEntity response) { @@ -283,10 +282,9 @@ public class TbHttpClient { private TbMsg processException(TbMsg origMsg, Throwable e) { TbMsgMetaData metaData = origMsg.getMetaData(); metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); - if (e instanceof RestClientResponseException) { - RestClientResponseException restClientResponseException = (RestClientResponseException) e; + if (e instanceof WebClientResponseException restClientResponseException) { metaData.putValue(STATUS, restClientResponseException.getStatusText()); - metaData.putValue(STATUS_CODE, restClientResponseException.getRawStatusCode() + ""); + metaData.putValue(STATUS_CODE, restClientResponseException.getStatusCode().value() + ""); metaData.putValue(ERROR_BODY, restClientResponseException.getResponseBodyAsString()); } return TbMsg.transformMsgMetadata(origMsg, metaData); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java index 635bfac356..132261420c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java @@ -168,7 +168,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { try { assertEquals(path, request.getRequestLine().getUri(), "Request path matches"); assertTrue(request.containsHeader("Content-Type"), "Content-Type included"); - assertEquals("text/plain;charset=UTF-8", + assertEquals("application/json", request.getFirstHeader("Content-Type").getValue(), "Content-Type value"); assertTrue(request.containsHeader("Content-Length"), "Content-Length included"); assertEquals("2", diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 097c3b8d49..ae2754c0eb 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -456,6 +456,10 @@ export class UtilsService { return isDefined(value); } + public isDefinedAndNotNull(value: any): boolean { + return isDefinedAndNotNull(value); + } + public defaultValue(value: any, defaultValue: any): any { if (isDefinedAndNotNull(value)) { return value; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html index 0d8537a3a6..76b458e4e4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html @@ -160,7 +160,7 @@ {}); } - this.router.navigateByUrl(this.router.parseUrl('/notification/inbox')).then(() => {}); } trackById(index: number, item: NotificationRequest): string { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts index 0a242eba36..def776897e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts @@ -169,6 +169,6 @@ export class TimeSeriesChartWidgetComponent implements OnInit, OnDestroy, AfterV } public toggleLegendKey(legendKey: LegendKey) { - this.timeSeriesChart.toggleKey(legendKey.dataKey); + this.timeSeriesChart.toggleKey(legendKey.dataKey, legendKey.dataIndex); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index d93f529de8..c5d09cfa6e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -56,7 +56,14 @@ import { measureAxisNameSize } from '@home/components/widget/lib/chart/echarts-widget.models'; import { DateFormatProcessor, ValueSourceType } from '@shared/models/widget-settings.models'; -import { formattedDataFormDatasourceData, formatValue, isDefinedAndNotNull, isEqual, mergeDeep } from '@core/utils'; +import { + formattedDataFormDatasourceData, + formatValue, + isDefined, + isDefinedAndNotNull, + isEqual, + mergeDeep +} from '@core/utils'; import { DataKey, Datasource, DatasourceType, FormattedData, widgetType } from '@shared/models/widget.models'; import * as echarts from 'echarts/core'; import { CallbackDataParams, PiecewiseVisualMapOption } from 'echarts/types/dist/shared'; @@ -300,7 +307,7 @@ export class TbTimeSeriesChart { } } - public toggleKey(dataKey: DataKey): void { + public toggleKey(dataKey: DataKey, dataIndex?: number): void { const enable = dataKey.hidden; const dataItem = this.dataItems.find(d => d.dataKey === dataKey); if (dataItem) { @@ -320,6 +327,9 @@ export class TbTimeSeriesChart { this.timeSeriesChart.setOption(this.timeSeriesChartOptions, this.stackMode ? {notMerge: true} : {replaceMerge: mergeList}); this.updateAxes(); dataKey.hidden = !enable; + if (isDefined(dataIndex)) { + this.ctx.defaultSubscription.updateDataVisibility(dataIndex); + } if (enable) { this.timeSeriesChart.dispatchAction({ type: 'highlight', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html index 8965b462b1..e7ca744685 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html @@ -79,7 +79,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.html index d79e84d98e..e56fc1ec00 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.html @@ -18,7 +18,7 @@
-
{{ title | translate }}
+
{{ titleText | translate }}
{{ chartFillTypeTranslationMap.get(type) | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.ts index 718c4cce02..bf02937221 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.ts @@ -55,7 +55,7 @@ export class ChartFillSettingsComponent implements OnInit, ControlValueAccessor disabled: boolean; @Input() - title = 'widgets.chart.fill'; + titleText = 'widgets.chart.fill'; @Input() fillNoneTitle = 'widgets.chart.fill-type-none'; diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts index a79fa89242..bae0389e61 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts @@ -487,6 +487,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } if (!this.widgetContext.inited && this.isReady()) { this.widgetContext.inited = true; + this.widgetContext.destroyed = false; this.dashboardWidget.updateWidgetParams(); this.widgetContext.detectContainerChanges(); if (this.cafs.init) { diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 581fded240..fbda78a128 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -449,6 +449,8 @@ export class WidgetContext { labelPattern.destroy(); } this.labelPatterns.clear(); + this.width = undefined; + this.height = undefined; this.destroyed = true; } diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts index 4e33944f29..a4d7c9bab0 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -25,11 +25,17 @@ import { SimpleChanges, ViewChild } from '@angular/core'; -import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { + ControlValueAccessor, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormGroup, + ValidationErrors, + Validators +} from '@angular/forms'; import { Observable } from 'rxjs'; import { filter, map, mergeMap, share, tap } from 'rxjs/operators'; -import { Store } from '@ngrx/store'; -import { AppState } from '@app/core/core.state'; import { TranslateService } from '@ngx-translate/core'; import { EntityType } from '@shared/models/entity-type.models'; import { BaseData } from '@shared/models/base-data'; @@ -49,6 +55,11 @@ import { SubscriptSizing } from '@angular/material/form-field'; provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => EntityListComponent), multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => EntityListComponent), + multi: true } ] }) @@ -56,7 +67,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV entityListFormGroup: UntypedFormGroup; - modelValue: Array | null; + private modelValue: Array | null; @Input() entityType: EntityType; @@ -108,17 +119,16 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV private propagateChange = (v: any) => { }; - constructor(private store: Store, - public translate: TranslateService, + constructor(public translate: TranslateService, private entityService: EntityService, private fb: UntypedFormBuilder) { this.entityListFormGroup = this.fb.group({ - entities: [this.entities, this.required ? [Validators.required] : []], + entities: [this.entities], entity: [null] }); } - updateValidators() { + private updateValidators() { this.entityListFormGroup.get('entities').setValidators(this.required ? [Validators.required] : []); this.entityListFormGroup.get('entities').updateValueAndValidity(); } @@ -189,7 +199,13 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV this.dirty = true; } - reset() { + validate(): ValidationErrors | null { + return this.entityListFormGroup.valid ? null : { + entities: {valid: false} + }; + } + + private reset() { this.entities = []; this.entityListFormGroup.get('entities').setValue(this.entities); this.modelValue = null; @@ -201,7 +217,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV this.dirty = true; } - add(entity: BaseData): void { + private add(entity: BaseData): void { if (!this.modelValue || this.modelValue.indexOf(entity.id.id) === -1) { if (!this.modelValue) { this.modelValue = []; @@ -214,7 +230,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV this.clear(); } - remove(entity: BaseData) { + public remove(entity: BaseData) { let index = this.entities.indexOf(entity); if (index >= 0) { this.entities.splice(index, 1); @@ -229,11 +245,11 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV } } - displayEntityFn(entity?: BaseData): string | undefined { + public displayEntityFn(entity?: BaseData): string | undefined { return entity ? entity.name : undefined; } - fetchEntities(searchText?: string): Observable>> { + private fetchEntities(searchText?: string): Observable>> { this.searchText = searchText; return this.entityService.getEntitiesByNameFilter(this.entityType, searchText, @@ -241,14 +257,14 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV map((data) => data ? data : [])); } - onFocus() { + public onFocus() { if (this.dirty) { this.entityListFormGroup.get('entity').updateValueAndValidity({onlySelf: true, emitEvent: true}); this.dirty = false; } } - clear(value: string = '') { + private clear(value: string = '') { this.entityInput.nativeElement.value = value; this.entityListFormGroup.get('entity').patchValue(value, {emitEvent: true}); setTimeout(() => { @@ -257,8 +273,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV }, 0); } - textIsNotEmpty(text: string): boolean { + public textIsNotEmpty(text: string): boolean { return (text && text.length > 0); } - }