diff --git a/ui-ngx/src/assets/help/en_US/rulenode/common_node_script_args.md b/ui-ngx/src/assets/help/en_US/rulenode/common_node_script_args.md index ef53ae37f8..296e5b735c 100644 --- a/ui-ngx/src/assets/help/en_US/rulenode/common_node_script_args.md +++ b/ui-ngx/src/assets/help/en_US/rulenode/common_node_script_args.md @@ -1,8 +1,11 @@
{[key: string]: any} - is a Message payload key/value object.
{[key: string]: string} - is a Message metadata key/value object.
+ {[key: string]: string} - is a Message metadata key/value map, where both keys and values are strings.
string - is a string Message type. See MessageType enum for common used values.
+ string - is a string containing Message type. See MessageType enum for common used values.
data having value 40
```javascript
var msg = { temp: 42, humidity: 77 };
var metadata = { data: 40 };
-var msgType = "POST_TELEMETRY_REQUEST";
-
-return { msg: msg, metadata: metadata, msgType: msgType };
+return { msg: msg, metadata: metadata, msgType: "POST_TELEMETRY_REQUEST" };
{:copy-code}
```
@@ -108,9 +103,8 @@ if (isDecrement === 'true') {
var msg = { temperature: temperature };
var metadata = { isDecrement: isDecrement };
-var msgType = "POST_TELEMETRY_REQUEST";
-return { msg: msg, metadata: metadata, msgType: msgType };
+return { msg: msg, metadata: metadata, msgType: "POST_TELEMETRY_REQUEST" };
{:copy-code}
```
diff --git a/ui-ngx/src/assets/help/en_US/rulenode/switch_node_script_fn.md b/ui-ngx/src/assets/help/en_US/rulenode/switch_node_script_fn.md
index de08fb9020..a31eae72cf 100644
--- a/ui-ngx/src/assets/help/en_US/rulenode/switch_node_script_fn.md
+++ b/ui-ngx/src/assets/help/en_US/rulenode/switch_node_script_fn.md
@@ -5,7 +5,7 @@
*function Switch(msg, metadata, msgType): string[]*
-JavaScript function computing **an array of next Relation names** for incoming Message.
+JavaScript function computing **an array of Link names** to forward the incoming Message.
**Parameters:**
@@ -13,8 +13,9 @@ JavaScript function computing **an array of next Relation names** for incoming M
**Returns:**
-Should return an array of `string` values presenting **next Relation names** where Message should be routed.
-If returned array is empty - message will not be routed to any Node and discarded.
+Should return an array of `string` values presenting **link names** that the Rule Engine should use to further route the incoming Message.
+If the result is an empty array - message will not be routed to any Node and will be immediately
+acknowledged.
@@ -24,7 +25,7 @@ If returned array is empty - message will not be routed to any Node and discarde
Forward all messages with temperature value greater than 30 to the 'High temperature' chain,
with temperature value lower than 20 to the 'Low temperature' chain and all other messages
-to the 'Normal temperature' chain:
+to the 'Other' chain:
@@ -34,11 +35,15 @@ if (msg.temperature > 30) {
} else if (msg.temperature < 20) {
return ['Low temperature'];
} else {
- return ['Normal temperature'];
+ return ['Other'];
}
{:copy-code}
```
+Example of the rule chain configuration:
+
+
+
-
For messages with type
POST_TELEMETRY_REQUEST:
diff --git a/ui-ngx/src/assets/help/en_US/rulenode/transformation_node_script_fn.md b/ui-ngx/src/assets/help/en_US/rulenode/transformation_node_script_fn.md
index acc49c623c..49f246a361 100644
--- a/ui-ngx/src/assets/help/en_US/rulenode/transformation_node_script_fn.md
+++ b/ui-ngx/src/assets/help/en_US/rulenode/transformation_node_script_fn.md
@@ -5,7 +5,7 @@
*function Transform(msg, metadata, msgType): {msg: object, metadata: object, msgType: string}*
-JavaScript function transforming input Message payload, Metadata or Message type.
+The JavaScript function to transform input Message payload, Metadata and/or Message type to the output message.
**Parameters:**
@@ -29,11 +29,29 @@ All fields in resulting object are optional and will be taken from original mess
##### Examples
-* Change message type to `CUSTOM_REQUEST`:
+* Add sum of two fields ('a' and 'b') as a new field ('sum') of existing message:
```javascript
-return { msgType: 'CUSTOM_REQUEST' };
-{:copy-code}
+if(typeof msg.a !== "undefined" && typeof msg.b !== "undefined"){
+ msg.sum = msg.a + msg.b;
+}
+return {msg: msg};
+```
+
+* Transform value of the 'temperature' field from °F to °C:
+
+```javascript
+msg.temperature = (msg.temperature - 32) * 5 / 9;
+return {msg: msg};
+```
+
+* Replace the incoming message with the new message that contains only one field - count of properties in the original message:
+
+```javascript
+var newMsg = {
+ count: Object.keys(msg).length
+};
+return {msg: newMsg};
```
diff --git a/ui-ngx/src/assets/help/en_US/widget/action/custom_action_fn.md b/ui-ngx/src/assets/help/en_US/widget/action/custom_action_fn.md
index 7ea3417f39..2bd143842d 100644
--- a/ui-ngx/src/assets/help/en_US/widget/action/custom_action_fn.md
+++ b/ui-ngx/src/assets/help/en_US/widget/action/custom_action_fn.md
@@ -17,67 +17,65 @@ A JavaScript function performing custom action.
##### Examples
-* Display alert dialog with entity information:
-
-```javascript
-{:code-style="max-height: 300px;"}
-var title;
-var content;
-if (entityName) {
- title = entityName + ' details';
- content = 'Entity name: ' + entityName;
- if (additionalParams && additionalParams.entity) {
- var entity = additionalParams.entity;
- if (entity.id) {
- content += '
Entity type: ' + entity.id.entityType;
- }
- if (!isNaN(entity.temperature) && entity.temperature !== '') {
- content += '
Temperature: ' + entity.temperature + ' °C';
- }
- }
-} else {
- title = 'No entity information available';
- content = 'No entity information available';
-}
-
-showAlertDialog(title, content);
-
-function showAlertDialog(title, content) {
- setTimeout(function() {
- widgetContext.dialogs.alert(title, content).subscribe();
- }, 100);
-}
-{:copy-code}
-```
-
-* Delete device after confirmation:
-
-```javascript
-{:code-style="max-height: 300px;"}
-var $injector = widgetContext.$scope.$injector;
-var dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));
-var deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
-
-openDeleteDeviceDialog();
-
-function openDeleteDeviceDialog() {
- var title = 'Are you sure you want to delete the device ' + entityName + '?';
- var content = 'Be careful, after the confirmation, the device and all related data will become unrecoverable!';
- dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(
- function(result) {
- if (result) {
- deleteDevice();
- }
- }
- );
-}
-
-function deleteDevice() {
- deviceService.deleteDevice(entityId.id).subscribe(
- function() {
- widgetContext.updateAliases();
- }
- );
-}
-{:copy-code}
-```
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/action/custom_pretty_action_fn.md b/ui-ngx/src/assets/help/en_US/widget/action/custom_pretty_action_fn.md
index a4c20a7cad..e82eab1e73 100644
--- a/ui-ngx/src/assets/help/en_US/widget/action/custom_pretty_action_fn.md
+++ b/ui-ngx/src/assets/help/en_US/widget/action/custom_pretty_action_fn.md
@@ -42,7 +42,7 @@ A JavaScript function performing custom action with defined HTML template to ren
+
+
+###### Display dialog to created new user
+
+
+
+
+
+
+
+
+
+
+
+###### Display dialog to add/edit image in entity attribute
+
+
+
+
+
+
+
+
+
+
+
+###### Display dialog to clone device
+
+
+
+
+
+
+
+
+ 0) {
+ stateIndex -= 1;
+ backToPrevState(stateIndex);
+}
+openDashboardState('devices');
+
+function backToPrevState(stateIndex) {
+ widgetContext.stateController.navigatePrevState(stateIndex);
+}
+
+function openDashboardState(statedId) {
+ var currentState = widgetContext.stateController.getStateId();
+ if (currentState !== statedId) {
+ var params = {};
+ widgetContext.stateController.updateState(statedId, params, false);
+ }
+}
+{:copy-code}
+```
+
+
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_copy_access_token.md b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_copy_access_token.md
new file mode 100644
index 0000000000..975883f7f8
--- /dev/null
+++ b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_copy_access_token.md
@@ -0,0 +1,44 @@
+#### Function copy device access token to buffer
+
+```javascript
+{:code-style="max-height: 400px;"}
+var $injector = widgetContext.$scope.$injector;
+var deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
+var $translate = $injector.get(widgetContext.servicesMap.get('translate'));
+var $scope = widgetContext.$scope;
+if (entityId.id && entityId.entityType === 'DEVICE') {
+ deviceService.getDeviceCredentials(entityId.id, true).subscribe(
+ (deviceCredentials) => {
+ var credentialsId = deviceCredentials.credentialsId;
+ if (copyToClipboard(credentialsId)) {
+ $scope.showSuccessToast($translate.instant('device.accessTokenCopiedMessage'), 750, "top", "left");
+ }
+ }
+ );
+}
+
+function copyToClipboard(text) {
+ if (window.clipboardData && window.clipboardData.setData) {
+ return window.clipboardData.setData("Text", text);
+ }
+ else if (document.queryCommandSupported && document.queryCommandSupported("copy")) {
+ var textarea = document.createElement("textarea");
+ textarea.textContent = text;
+ textarea.style.position = "fixed";
+ document.body.appendChild(textarea);
+ textarea.select();
+ try {
+ return document.execCommand("copy");
+ }
+ catch (ex) {
+ console.warn("Copy to clipboard failed.", ex);
+ return false;
+ }
+ document.body.removeChild(textarea);
+ }
+}
+{:copy-code}
+```
+
+
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_delete_device_confirm.md b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_delete_device_confirm.md
new file mode 100644
index 0000000000..83ba86c348
--- /dev/null
+++ b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_delete_device_confirm.md
@@ -0,0 +1,33 @@
+#### Function delete device after confirmation
+
+```javascript
+var $injector = widgetContext.$scope.$injector;
+var dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));
+var deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
+
+openDeleteDeviceDialog();
+
+function openDeleteDeviceDialog() {
+ var title = 'Are you sure you want to delete the device ' + entityName + '?';
+ var content = 'Be careful, after the confirmation, the device and all related data will become unrecoverable!';
+ dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(
+ function(result) {
+ if (result) {
+ deleteDevice();
+ }
+ }
+ );
+}
+
+function deleteDevice() {
+ deviceService.deleteDevice(entityId.id).subscribe(
+ function() {
+ widgetContext.updateAliases();
+ }
+ );
+}
+{:copy-code}
+```
+
+
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_display_alert.md b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_display_alert.md
new file mode 100644
index 0000000000..bf6ed2b6fd
--- /dev/null
+++ b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_display_alert.md
@@ -0,0 +1,35 @@
+#### Function display alert dialog with entity information
+
+```javascript
+{:code-style="max-height: 400px;"}
+var title;
+var content;
+if (entityName) {
+ title = entityName + ' details';
+ content = 'Entity name: ' + entityName;
+ if (additionalParams && additionalParams.entity) {
+ var entity = additionalParams.entity;
+ if (entity.id) {
+ content += '
Entity type: ' + entity.id.entityType;
+ }
+ if (!isNaN(entity.temperature) && entity.temperature !== '') {
+ content += '
Temperature: ' + entity.temperature + ' °C';
+ }
+ }
+} else {
+ title = 'No entity information available';
+ content = 'No entity information available';
+}
+
+showAlertDialog(title, content);
+
+function showAlertDialog(title, content) {
+ setTimeout(function() {
+ widgetContext.dialogs.alert(title, content).subscribe();
+ }, 100);
+}
+{:copy-code}
+```
+
+
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_open_state_save_parameters.md b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_open_state_save_parameters.md
new file mode 100644
index 0000000000..18bd3f53ab
--- /dev/null
+++ b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_open_state_save_parameters.md
@@ -0,0 +1,34 @@
+#### Function open state conditionally with saving particular state parameters
+
+```javascript
+{:code-style="max-height: 400px;"}
+var entitySubType;
+var $injector = widgetContext.$scope.$injector;
+$injector.get(widgetContext.servicesMap.get('entityService')).getEntity(entityId.entityType, entityId.id)
+ .subscribe(function(data) {
+ entitySubType = data.type;
+ if (entitySubType == 'energy meter') {
+ openDashboardStates('energy_meter_details_view');
+ } else if (entitySubType == 'thermometer') {
+ openDashboardStates('thermometer_details_view');
+ }
+ });
+
+function openDashboardStates(statedId) {
+ var stateParams = widgetContext.stateController.getStateParams();
+ var params = {
+ entityId: entityId,
+ entityName: entityName
+ };
+
+ if (stateParams.city) {
+ params.city = stateParams.city;
+ }
+
+ widgetContext.stateController.openState(statedId, params, false);
+}
+{:copy-code}
+```
+
+
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_return_previous_state.md b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_return_previous_state.md
new file mode 100644
index 0000000000..77f65edbe7
--- /dev/null
+++ b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_action/custom_action_return_previous_state.md
@@ -0,0 +1,18 @@
+#### Function return to the previous state
+
+```javascript
+{:code-style="max-height: 400px;"}
+let stateIndex = widgetContext.stateController.getStateIndex();
+if (stateIndex > 0) {
+ stateIndex -= 1;
+ backToPrevState(stateIndex);
+}
+
+function backToPrevState(stateIndex) {
+ widgetContext.stateController.navigatePrevState(stateIndex);
+}
+{:copy-code}
+```
+
+
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_clone_device_html.md b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_clone_device_html.md
new file mode 100644
index 0000000000..7f93d6266b
--- /dev/null
+++ b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_clone_device_html.md
@@ -0,0 +1,46 @@
+#### HTML template of dialog to clone device
+
+```html
+{:code-style="max-height: 400px;"}
+
+{:copy-code}
+```
+
+
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_clone_device_js.md b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_clone_device_js.md
new file mode 100644
index 0000000000..2ee9160cce
--- /dev/null
+++ b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_clone_device_js.md
@@ -0,0 +1,56 @@
+#### Function displaying dialog to clone device
+
+```javascript
+{:code-style="max-height: 400px;"}
+const $injector = widgetContext.$scope.$injector;
+const customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
+const attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));
+const deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
+const rxjs = widgetContext.rxjs;
+
+openCloneDeviceDialog();
+
+function openCloneDeviceDialog() {
+ customDialog.customDialog(htmlTemplate, CloneDeviceDialogController).subscribe();
+}
+
+function CloneDeviceDialogController(instance) {
+ let vm = instance;
+ vm.deviceName = entityName;
+
+ vm.cloneDeviceFormGroup = vm.fb.group({
+ cloneName: ['', [vm.validators.required]]
+ });
+
+ vm.save = function() {
+ deviceService.getDevice(entityId.id).pipe(
+ rxjs.mergeMap((origDevice) => {
+ let cloneDevice = {
+ name: vm.cloneDeviceFormGroup.get('cloneName').value,
+ type: origDevice.type
+ };
+ return deviceService.saveDevice(cloneDevice).pipe(
+ rxjs.mergeMap((newDevice) => {
+ return attributeService.getEntityAttributes(origDevice.id, 'SERVER_SCOPE').pipe(
+ rxjs.mergeMap((origAttributes) => {
+ return attributeService.saveEntityAttributes(newDevice.id, 'SERVER_SCOPE', origAttributes);
+ })
+ );
+ })
+ );
+ })
+ ).subscribe(() => {
+ widgetContext.updateAliases();
+ vm.dialogRef.close(null);
+ });
+ };
+
+ vm.cancel = function() {
+ vm.dialogRef.close(null);
+ };
+}
+{:copy-code}
+```
+
+
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_dialog_html.md b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_dialog_html.md
new file mode 100644
index 0000000000..567e093c76
--- /dev/null
+++ b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_dialog_html.md
@@ -0,0 +1,164 @@
+#### HTML template of dialog to create a device or an asset
+
+```html
+{:code-style="max-height: 400px;"}
+
+{:copy-code}
+```
+
+
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_dialog_js.md b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_dialog_js.md
new file mode 100644
index 0000000000..5dda1b524c
--- /dev/null
+++ b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_dialog_js.md
@@ -0,0 +1,136 @@
+#### Function displaying dialog to create a device or an asset
+
+```javascript
+{:code-style="max-height: 400px;"}
+let $injector = widgetContext.$scope.$injector;
+let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
+let assetService = $injector.get(widgetContext.servicesMap.get('assetService'));
+let deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
+let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));
+let entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService'));
+
+openAddEntityDialog();
+
+function openAddEntityDialog() {
+ customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();
+}
+
+function AddEntityDialogController(instance) {
+ let vm = instance;
+
+ vm.allowedEntityTypes = ['ASSET', 'DEVICE'];
+ vm.entitySearchDirection = {
+ from: "FROM",
+ to: "TO"
+ }
+
+ vm.addEntityFormGroup = vm.fb.group({
+ entityName: ['', [vm.validators.required]],
+ entityType: ['DEVICE'],
+ entityLabel: [null],
+ type: ['', [vm.validators.required]],
+ attributes: vm.fb.group({
+ latitude: [null],
+ longitude: [null],
+ address: [null],
+ owner: [null],
+ number: [null, [vm.validators.pattern(/^-?[0-9]+$/)]],
+ booleanValue: [null]
+ }),
+ relations: vm.fb.array([])
+ });
+
+ vm.cancel = function () {
+ vm.dialogRef.close(null);
+ };
+
+ vm.relations = function () {
+ return vm.addEntityFormGroup.get('relations');
+ };
+
+ vm.addRelation = function () {
+ vm.relations().push(vm.fb.group({
+ relatedEntity: [null, [vm.validators.required]],
+ relationType: [null, [vm.validators.required]],
+ direction: [null, [vm.validators.required]]
+ }));
+ };
+
+ vm.removeRelation = function (index) {
+ vm.relations().removeAt(index);
+ vm.relations().markAsDirty();
+ };
+
+ vm.save = function () {
+ vm.addEntityFormGroup.markAsPristine();
+ saveEntityObservable().subscribe(
+ function (entity) {
+ widgetContext.rxjs.forkJoin([
+ saveAttributes(entity.id),
+ saveRelations(entity.id)
+ ]).subscribe(
+ function () {
+ widgetContext.updateAliases();
+ vm.dialogRef.close(null);
+ }
+ );
+ }
+ );
+ };
+
+ function saveEntityObservable() {
+ const formValues = vm.addEntityFormGroup.value;
+ let entity = {
+ name: formValues.entityName,
+ type: formValues.type,
+ label: formValues.entityLabel
+ };
+ if (formValues.entityType == 'ASSET') {
+ return assetService.saveAsset(entity);
+ } else if (formValues.entityType == 'DEVICE') {
+ return deviceService.saveDevice(entity);
+ }
+ }
+
+ function saveAttributes(entityId) {
+ let attributes = vm.addEntityFormGroup.get('attributes').value;
+ let attributesArray = [];
+ for (let key in attributes) {
+ if (attributes[key] !== null) {
+ attributesArray.push({key: key, value: attributes[key]});
+ }
+ }
+ if (attributesArray.length > 0) {
+ return attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributesArray);
+ }
+ return widgetContext.rxjs.of([]);
+ }
+
+ function saveRelations(entityId) {
+ let relations = vm.addEntityFormGroup.get('relations').value;
+ let tasks = [];
+ for (let i = 0; i < relations.length; i++) {
+ let relation = {
+ type: relations[i].relationType,
+ typeGroup: 'COMMON'
+ };
+ if (relations[i].direction == 'FROM') {
+ relation.to = relations[i].relatedEntity;
+ relation.from = entityId;
+ } else {
+ relation.to = entityId;
+ relation.from = relations[i].relatedEntity;
+ }
+ tasks.push(entityRelationService.saveRelation(relation));
+ }
+ if (tasks.length > 0) {
+ return widgetContext.rxjs.forkJoin(tasks);
+ }
+ return widgetContext.rxjs.of([]);
+ }
+}
+{:copy-code}
+```
+
+
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_user_html.md b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_user_html.md
new file mode 100644
index 0000000000..c523258fbc
--- /dev/null
+++ b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_user_html.md
@@ -0,0 +1,81 @@
+#### HTML template of dialog to create new user
+
+```html
+{:code-style="max-height: 400px;"}
+
+{:copy-code}
+```
+
+
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_user_js.md b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_user_js.md
new file mode 100644
index 0000000000..22738c53c1
--- /dev/null
+++ b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_create_user_js.md
@@ -0,0 +1,137 @@
+#### Function displaying dialog to create new user
+
+```javascript
+{:code-style="max-height: 400px;"}
+const $injector = widgetContext.$scope.$injector;
+const customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
+const userService = $injector.get(widgetContext.servicesMap.get('userService'));
+const $scope = widgetContext.$scope;
+const rxjs = widgetContext.rxjs;
+
+openAddUserDialog();
+
+function openAddUserDialog() {
+ customDialog.customDialog(htmlTemplate, AddUserDialogController).subscribe();
+}
+
+function AddUserDialogController(instance) {
+ let vm = instance;
+
+ vm.currentUser = widgetContext.currentUser;
+
+ vm.addEntityFormGroup = vm.fb.group({
+ email: ['', [vm.validators.required, vm.validators.pattern(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\_\-0-9]+\.)+[a-zA-Z]{2,}))$/)]],
+ firstName: [''],
+ lastName: ['', ],
+ userActivationMethod: ['', [vm.validators.required]]
+ });
+
+ vm.activationMethods = [
+ {
+ value: 'displayActivationLink',
+ name: 'Display activation link'
+ },
+ {
+ value: 'sendActivationMail',
+ name: 'Send activation email'
+ }
+ ];
+
+ vm.cancel = function() {
+ vm.dialogRef.close(null);
+ };
+
+ vm.save = function() {
+ let formObj = vm.addEntityFormGroup.getRawValue();
+ let attributes = [];
+ let sendActivationMail = false;
+ let newUser = {
+ email: formObj.email,
+ firstName: formObj.firstName,
+ lastName: formObj.lastName,
+ authority: 'TENANT_ADMIN'
+ };
+
+ if (formObj.userActivationMethod === 'sendActivationMail') {
+ sendActivationMail = true;
+ }
+
+ userService.saveUser(newUser, sendActivationMail).pipe(
+ rxjs.mergeMap((user) => {
+ let activationObs;
+ if (sendActivationMail) {
+ activationObs = rxjs.of(null);
+ } else {
+ activationObs = userService.getActivationLink(user.id.id);
+ }
+ return activationObs.pipe(
+ rxjs.mergeMap((activationLink) => {
+ return activationLink ? customDialog.customDialog(activationLinkDialogTemplate, ActivationLinkDialogController, {"activationLink": activationLink}) : rxjs.of(null);
+ })
+ );
+ })
+ ).subscribe(() => {
+ vm.dialogRef.close(null);
+ });
+ };
+}
+
+function ActivationLinkDialogController(instance) {
+ let vm = instance;
+
+ vm.activationLink = vm.data.activationLink;
+
+ vm.onActivationLinkCopied = () => {
+ $scope.showSuccessToast("User activation link has been copied to clipboard", 1200, "bottom", "left", "activationLinkDialogContent");
+ };
+
+ vm.close = () => {
+ vm.dialogRef.close(null);
+ };
+}
+
+let activationLinkDialogTemplate = ``;
+{:copy-code}
+```
+
+
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_dialog_html.md b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_dialog_html.md
new file mode 100644
index 0000000000..4e3cd0c608
--- /dev/null
+++ b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_dialog_html.md
@@ -0,0 +1,196 @@
+#### HTML template of dialog to edit a device or an asset
+
+```html
+{:code-style="max-height: 400px;"}
+
+{:copy-code}
+```
+
+
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_dialog_js.md b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_dialog_js.md
new file mode 100644
index 0000000000..b2a6a635df
--- /dev/null
+++ b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_dialog_js.md
@@ -0,0 +1,224 @@
+#### Function displaying dialog to edit a device or an asset
+
+```javascript
+{:code-style="max-height: 400px;"}
+let $injector = widgetContext.$scope.$injector;
+let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
+let entityService = $injector.get(widgetContext.servicesMap.get('entityService'));
+let assetService = $injector.get(widgetContext.servicesMap.get('assetService'));
+let deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
+let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));
+let entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService'));
+
+openEditEntityDialog();
+
+function openEditEntityDialog() {
+ customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();
+}
+
+function EditEntityDialogController(instance) {
+ let vm = instance;
+
+ vm.entityName = entityName;
+ vm.entityType = entityId.entityType;
+ vm.entitySearchDirection = {
+ from: "FROM",
+ to: "TO"
+ };
+ vm.attributes = {};
+ vm.oldRelationsData = [];
+ vm.relationsToDelete = [];
+ vm.entity = {};
+
+ vm.editEntityFormGroup = vm.fb.group({
+ entityName: ['', [vm.validators.required]],
+ entityType: [null],
+ entityLabel: [null],
+ type: ['', [vm.validators.required]],
+ attributes: vm.fb.group({
+ latitude: [null],
+ longitude: [null],
+ address: [null],
+ owner: [null],
+ number: [null, [vm.validators.pattern(/^-?[0-9]+$/)]],
+ booleanValue: [false]
+ }),
+ oldRelations: vm.fb.array([]),
+ relations: vm.fb.array([])
+ });
+
+ getEntityInfo();
+
+ vm.cancel = function() {
+ vm.dialogRef.close(null);
+ };
+
+ vm.relations = function() {
+ return vm.editEntityFormGroup.get('relations');
+ };
+
+ vm.oldRelations = function() {
+ return vm.editEntityFormGroup.get('oldRelations');
+ };
+
+ vm.addRelation = function() {
+ vm.relations().push(vm.fb.group({
+ relatedEntity: [null, [vm.validators.required]],
+ relationType: [null, [vm.validators.required]],
+ direction: [null, [vm.validators.required]]
+ }));
+ };
+
+ function addOldRelation() {
+ vm.oldRelations().push(vm.fb.group({
+ relatedEntity: [{value: null, disabled: true}, [vm.validators.required]],
+ relationType: [{value: null, disabled: true}, [vm.validators.required]],
+ direction: [{value: null, disabled: true}, [vm.validators.required]]
+ }));
+ }
+
+ vm.removeRelation = function(index) {
+ vm.relations().removeAt(index);
+ vm.relations().markAsDirty();
+ };
+
+ vm.removeOldRelation = function(index) {
+ vm.oldRelations().removeAt(index);
+ vm.relationsToDelete.push(vm.oldRelationsData[index]);
+ vm.oldRelations().markAsDirty();
+ };
+
+ vm.save = function() {
+ vm.editEntityFormGroup.markAsPristine();
+ widgetContext.rxjs.forkJoin([
+ saveAttributes(entityId),
+ saveRelations(entityId),
+ saveEntity()
+ ]).subscribe(
+ function () {
+ widgetContext.updateAliases();
+ vm.dialogRef.close(null);
+ }
+ );
+ };
+
+ function getEntityAttributes(attributes) {
+ for (var i = 0; i < attributes.length; i++) {
+ vm.attributes[attributes[i].key] = attributes[i].value;
+ }
+ }
+
+ function getEntityRelations(relations) {
+ let relationsFrom = relations[0];
+ let relationsTo = relations[1];
+ for (let i=0; i < relationsFrom.length; i++) {
+ let relation = {
+ direction: 'FROM',
+ relationType: relationsFrom[i].type,
+ relatedEntity: relationsFrom[i].to
+ };
+ vm.oldRelationsData.push(relation);
+ addOldRelation();
+ }
+ for (let i=0; i < relationsTo.length; i++) {
+ let relation = {
+ direction: 'TO',
+ relationType: relationsTo[i].type,
+ relatedEntity: relationsTo[i].from
+ };
+ vm.oldRelationsData.push(relation);
+ addOldRelation();
+ }
+ }
+
+ function getEntityInfo() {
+ widgetContext.rxjs.forkJoin([
+ entityRelationService.findInfoByFrom(entityId),
+ entityRelationService.findInfoByTo(entityId),
+ attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE'),
+ entityService.getEntity(entityId.entityType, entityId.id)
+ ]).subscribe(
+ function (data) {
+ getEntityRelations(data.slice(0,2));
+ getEntityAttributes(data[2]);
+ vm.entity = data[3];
+ vm.editEntityFormGroup.patchValue({
+ entityName: vm.entity.name,
+ entityType: vm.entityType,
+ entityLabel: vm.entity.label,
+ type: vm.entity.type,
+ attributes: vm.attributes,
+ oldRelations: vm.oldRelationsData
+ }, {emitEvent: false});
+ }
+ );
+ }
+
+ function saveEntity() {
+ const formValues = vm.editEntityFormGroup.value;
+ if (vm.entity.label !== formValues.entityLabel){
+ vm.entity.label = formValues.entityLabel;
+ if (formValues.entityType == 'ASSET') {
+ return assetService.saveAsset(vm.entity);
+ } else if (formValues.entityType == 'DEVICE') {
+ return deviceService.saveDevice(vm.entity);
+ }
+ }
+ return widgetContext.rxjs.of([]);
+ }
+
+ function saveAttributes(entityId) {
+ let attributes = vm.editEntityFormGroup.get('attributes').value;
+ let attributesArray = [];
+ for (let key in attributes) {
+ if (attributes[key] !== vm.attributes[key]) {
+ attributesArray.push({key: key, value: attributes[key]});
+ }
+ }
+ if (attributesArray.length > 0) {
+ return attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributesArray);
+ }
+ return widgetContext.rxjs.of([]);
+ }
+
+ function saveRelations(entityId) {
+ let relations = vm.editEntityFormGroup.get('relations').value;
+ let tasks = [];
+ for(let i=0; i < relations.length; i++) {
+ let relation = {
+ type: relations[i].relationType,
+ typeGroup: 'COMMON'
+ };
+ if (relations[i].direction == 'FROM') {
+ relation.to = relations[i].relatedEntity;
+ relation.from = entityId;
+ } else {
+ relation.to = entityId;
+ relation.from = relations[i].relatedEntity;
+ }
+ tasks.push(entityRelationService.saveRelation(relation));
+ }
+ for (let i=0; i < vm.relationsToDelete.length; i++) {
+ let relation = {
+ type: vm.relationsToDelete[i].relationType
+ };
+ if (vm.relationsToDelete[i].direction == 'FROM') {
+ relation.to = vm.relationsToDelete[i].relatedEntity;
+ relation.from = entityId;
+ } else {
+ relation.to = entityId;
+ relation.from = vm.relationsToDelete[i].relatedEntity;
+ }
+ tasks.push(entityRelationService.deleteRelation(relation.from, relation.type, relation.to));
+ }
+ if (tasks.length > 0) {
+ return widgetContext.rxjs.forkJoin(tasks);
+ }
+ return widgetContext.rxjs.of([]);
+ }
+}
+{:copy-code}
+```
+
+
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_image_html.md b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_image_html.md
new file mode 100644
index 0000000000..3f2eff11c6
--- /dev/null
+++ b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_image_html.md
@@ -0,0 +1,42 @@
+#### HTML template of dialog to add/edit image in entity attribute
+
+```html
+{:code-style="max-height: 400px;"}
+
+{:copy-code}
+```
+
+
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_image_js.md b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_image_js.md
new file mode 100644
index 0000000000..21acb89321
--- /dev/null
+++ b/ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_image_js.md
@@ -0,0 +1,85 @@
+#### Function displaying dialog to add/edit image in entity attribute
+
+```javascript
+{:code-style="max-height: 400px;"}
+let $injector = widgetContext.$scope.$injector;
+let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
+let assetService = $injector.get(widgetContext.servicesMap.get('assetService'));
+let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));
+let entityService = $injector.get(widgetContext.servicesMap.get('entityService'));
+
+openAddEntityDialog();
+
+function openAddEntityDialog() {
+ customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe(() => {});
+}
+
+function AddEntityDialogController(instance) {
+ let vm = instance;
+
+ vm.entityName = entityName;
+
+ vm.attributes = {};
+
+ vm.editEntity = vm.fb.group({
+ attributes: vm.fb.group({
+ image: [null]
+ })
+ });
+
+ getEntityInfo();
+
+ vm.cancel = function() {
+ vm.dialogRef.close(null);
+ };
+
+ vm.save = function() {
+ vm.loading = true;
+ saveAttributes(entityId).subscribe(
+ () => {
+ vm.dialogRef.close(null);
+ }, () =>{
+ vm.loading = false;
+ }
+ );
+ };
+
+ function getEntityAttributes(attributes) {
+ for (var i = 0; i < attributes.length; i++) {
+ vm.attributes[attributes[i].key] = attributes[i].value;
+ }
+ }
+
+ function getEntityInfo() {
+ vm.loading = true;
+ attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE').subscribe(
+ function (data) {
+ getEntityAttributes(data);
+
+ vm.editEntity.patchValue({
+ attributes: vm.attributes
+ }, {emitEvent: false});
+ vm.loading = false;
+ }
+ );
+ }
+
+ function saveAttributes(entityId) {
+ let attributes = vm.editEntity.get('attributes').value;
+ let attributesArray = [];
+ for (let key in attributes) {
+ if (attributes[key] !== vm.attributes[key]) {
+ attributesArray.push({key: key, value: attributes[key]});
+ }
+ }
+ if (attributesArray.length > 0) {
+ return attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributesArray);
+ }
+ return widgetContext.rxjs.of([]);
+ }
+}
+{:copy-code}
+```
+
+
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/config/datakey_postprocess_fn.md b/ui-ngx/src/assets/help/en_US/widget/config/datakey_postprocess_fn.md
index acab47c45e..fc4999757a 100644
--- a/ui-ngx/src/assets/help/en_US/widget/config/datakey_postprocess_fn.md
+++ b/ui-ngx/src/assets/help/en_US/widget/config/datakey_postprocess_fn.md
@@ -54,6 +54,33 @@ if (prevOrigValue) {
}
{:copy-code}
```
+* Formatting data to time format
+
+```javascript
+if (value) {
+ return moment(value).format("DD/MM/YYYY HH:mm:ss");
+}
+return '';
+{:copy-code}
+```
+
+* Creates line-breaks for 0 values, when used in line chart
+
+```javascript
+if (value === 0) {
+ return null;
+} else {
+ return value;
+}
+{:copy-code}
+```
+
+* Display data point of the HTML value card under the condition
+
+```javascript
+return value ? 'Temperature: '+value+' °C' : '';
+{:copy-code}
+```
diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/entity/cell_content_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/entity/cell_content_fn.md
index 4458177327..e2dd12e841 100644
--- a/ui-ngx/src/assets/help/en_US/widget/lib/entity/cell_content_fn.md
+++ b/ui-ngx/src/assets/help/en_US/widget/lib/entity/cell_content_fn.md
@@ -57,5 +57,87 @@ return '⬤';
+{:copy-code}
+```
+
+* Decimal value format (1196 => 1,196.0):
+
+```javascript
+var value = value / 1;
+function numberWithCommas(x) {
+ return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+}
+return value ? numberWithCommas(value.toFixed(1)) : '';
+{:copy-code}
+```
+
+* Show device status and icon this :
+
+```javascript
+{:code-style="max-height: 200px; max-width: 850px;"}
+function getIcon(value) {
+ if (value == 'QUEUED') {
+ return '' +
+ '' +
+ ' ';
+ }
+ if (value == 'UPDATED' ) {
+ return '' +
+ 'update' +
+ ' ';
+ }
+ return '';
+}
+function capitalize (s) {
+ if (typeof s !== 'string') return '';
+ return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
+}
+var status = value;
+return getIcon(status) + '' + capitalize(status) + '';
+{:copy-code}
+```
+
+* Display device attribute value on progress bar:
+
+```javascript
+{:code-style="max-height: 200px; max-width: 850px;"}
+var progress = value;
+if (value !== '') {
+ return `` +
+ `` +
+ ` `;
+}
+{:copy-code}
+```
+
diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/entity/cell_style_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/entity/cell_style_fn.md
index 692960cf4a..442fca3375 100644
--- a/ui-ngx/src/assets/help/en_US/widget/lib/entity/cell_style_fn.md
+++ b/ui-ngx/src/assets/help/en_US/widget/lib/entity/cell_style_fn.md
@@ -29,6 +29,16 @@ Should return key/value object presenting style attributes.
##### Examples
+* Set color and font-weight table cell content:
+
+```javascript
+return {
+ color:'rgb(0, 132, 214)',
+ fontWeight: 600
+}
+{:copy-code}
+```
+
* Set color depending on device temperature value:
```javascript
diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/entity/row_style_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/entity/row_style_fn.md
index f91befddce..4867cb9b38 100644
--- a/ui-ngx/src/assets/help/en_US/widget/lib/entity/row_style_fn.md
+++ b/ui-ngx/src/assets/help/en_US/widget/lib/entity/row_style_fn.md
@@ -27,6 +27,16 @@ Should return key/value object presenting style attributes.
##### Examples
+* Set color and font-weight table row:
+
+```javascript
+return {
+ color:'rgb(0, 132, 214)',
+ fontWeight: 600
+}
+{:copy-code}
+```
+
* Set row background color depending on device type:
```javascript
diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/flot/ticks_formatter_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/flot/ticks_formatter_fn.md
index 673161c58d..12de0f782c 100644
--- a/ui-ngx/src/assets/help/en_US/widget/lib/flot/ticks_formatter_fn.md
+++ b/ui-ngx/src/assets/help/en_US/widget/lib/flot/ticks_formatter_fn.md
@@ -43,6 +43,17 @@ return '';
{:copy-code}
```
+* Present ticks in decimal format (1196 => 1,196.0):
+
+```javascript
+var value = value / 1;
+function numberWithCommas(x) {
+ return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+}
+return value ? numberWithCommas(value.toFixed(1)) : '';
+{:copy-code}
+```
+
-
To present axis ticks for true / false or 1 / 0 data.
diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/flot/tooltip_value_format_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/flot/tooltip_value_format_fn.md
index 4ff02e3fab..bffbfe4673 100644
--- a/ui-ngx/src/assets/help/en_US/widget/lib/flot/tooltip_value_format_fn.md
+++ b/ui-ngx/src/assets/help/en_US/widget/lib/flot/tooltip_value_format_fn.md
@@ -36,5 +36,16 @@ return value.toFixed(2) + ' A';
{:copy-code}
```
+* Present the datapoint value in decimal format (1196 => 1,196.0):
+
+```javascript
+var value = value / 1;
+function numberWithCommas(x) {
+ return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+}
+return value ? numberWithCommas(value.toFixed(1)) : '';
+{:copy-code}
+```
+
diff --git a/ui-ngx/src/assets/help/images/rulenode/examples/filter-node.png b/ui-ngx/src/assets/help/images/rulenode/examples/filter-node.png
new file mode 100644
index 0000000000..178df7e314
Binary files /dev/null and b/ui-ngx/src/assets/help/images/rulenode/examples/filter-node.png differ
diff --git a/ui-ngx/src/assets/help/images/rulenode/examples/switch-node.png b/ui-ngx/src/assets/help/images/rulenode/examples/switch-node.png
new file mode 100644
index 0000000000..88739a72e5
Binary files /dev/null and b/ui-ngx/src/assets/help/images/rulenode/examples/switch-node.png differ