Merge pull request #14136 from thingsboard/feature/display-name
Add new entity field - displayName
This commit is contained in:
		
						commit
						1b98495342
					
				
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -11,19 +11,13 @@
 | 
			
		||||
    "resources": [],
 | 
			
		||||
    "templateHtml": "<tb-entities-table-widget \n    [ctx]=\"ctx\">\n</tb-entities-table-widget>",
 | 
			
		||||
    "templateCss": "",
 | 
			
		||||
    "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n    self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.onEditModeChanged = function() {\n    self.ctx.$scope.entitiesTableWidget.onEditModeChanged();\n}\n\nself.typeParameters = function() {\n    return {\n        maxDatasources: 1,\n        hasDataPageLink: true,\n        warnOnPageDataOverflow: false,\n        dataKeysOptional: true,\n        supportsUnitConversion: true,\n        defaultDataKeysFunction: function() {\n            return [{ name: 'name', type: 'entityField' }];\n        }\n    };\n}\n\nself.actionSources = function() {\n    return {\n        'actionCellButton': {\n            name: 'widget-action.action-cell-button',\n            multiple: true,\n            hasShowCondition: true\n        },\n        'rowClick': {\n            name: 'widget-action.row-click',\n            multiple: false\n        },\n        'rowDoubleClick': {\n            name: 'widget-action.row-double-click',\n            multiple: false\n        },\n        'cellClick': {\n            name: 'widget-action.cell-click',\n            multiple: true\n        }\n    };\n}\n\nself.onDestroy = function() {\n}\n",
 | 
			
		||||
    "settingsSchema": "",
 | 
			
		||||
    "dataKeySettingsSchema": "",
 | 
			
		||||
    "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n    self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.onEditModeChanged = function() {\n    self.ctx.$scope.entitiesTableWidget.onEditModeChanged();\n}\n\nself.typeParameters = function() {\n    return {\n        maxDatasources: 1,\n        hasDataPageLink: true,\n        warnOnPageDataOverflow: false,\n        dataKeysOptional: true,\n        supportsUnitConversion: true,\n        defaultDataKeysFunction: function() {\n            return [{ name: 'displayName', type: 'entityField' }];\n        }\n    };\n}\n\nself.actionSources = function() {\n    return {\n        'actionCellButton': {\n            name: 'widget-action.action-cell-button',\n            multiple: true,\n            hasShowCondition: true\n        },\n        'rowClick': {\n            name: 'widget-action.row-click',\n            multiple: false\n        },\n        'rowDoubleClick': {\n            name: 'widget-action.row-double-click',\n            multiple: false\n        },\n        'cellClick': {\n            name: 'widget-action.cell-click',\n            multiple: true\n        }\n    };\n}\n\nself.onDestroy = function() {\n}\n",
 | 
			
		||||
    "settingsDirective": "tb-entities-table-widget-settings",
 | 
			
		||||
    "dataKeySettingsDirective": "tb-entities-table-key-settings",
 | 
			
		||||
    "hasBasicMode": true,
 | 
			
		||||
    "basicModeDirective": "tb-entities-table-basic-config",
 | 
			
		||||
    "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"enableSelectColumnDisplay\":true,\"enableStickyHeader\":true,\"enableStickyAction\":true,\"reserveSpaceForHiddenAction\":\"true\",\"displayEntityName\":false,\"displayEntityLabel\":false,\"displayEntityType\":false,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"name\",\"useRowStyleFunction\":false,\"entitiesTitle\":\"Entities\"},\"title\":\"Entities table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Entity name\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return 'Simulated';\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Entity type\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.782057645776538,\"funcBody\":\"return 'Device';\",\"decimals\":null,\"aggregationType\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.904797781901171,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\",\"decimals\":0},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.1961430898042078,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\",\"decimals\":0},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.7678057538205878,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\",\"decimals\":2}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"displayTimewindow\":false,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"list\",\"iconColor\":null}"
 | 
			
		||||
  },
 | 
			
		||||
  "tags": [
 | 
			
		||||
    "administration",
 | 
			
		||||
    "management"
 | 
			
		||||
  ],
 | 
			
		||||
  "resources": [
 | 
			
		||||
    {
 | 
			
		||||
      "link": "/api/images/system/entities_table_system_widget_image.png",
 | 
			
		||||
@ -36,5 +30,10 @@
 | 
			
		||||
      "data": "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",
 | 
			
		||||
      "public": true
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "scada": false,
 | 
			
		||||
  "tags": [
 | 
			
		||||
    "administration",
 | 
			
		||||
    "management"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
@ -77,6 +77,7 @@ import java.util.Arrays;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import java.util.function.BiConsumer;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
			
		||||
@ -431,14 +432,17 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
 | 
			
		||||
 | 
			
		||||
        List<EntityKey> alarmFields = new ArrayList<>();
 | 
			
		||||
        alarmFields.add(new EntityKey(EntityKeyType.ALARM_FIELD, "type"));
 | 
			
		||||
        alarmFields.add(new EntityKey(EntityKeyType.ALARM_FIELD, "originatorDisplayName"));
 | 
			
		||||
 | 
			
		||||
        EntityTypeFilter assetTypeFilter = new EntityTypeFilter();
 | 
			
		||||
        assetTypeFilter.setEntityType(EntityType.ASSET);
 | 
			
		||||
        AlarmDataQuery assetAlarmQuery =  new AlarmDataQuery(assetTypeFilter, pageLink, null, null, null, alarmFields);
 | 
			
		||||
 | 
			
		||||
        PageData<AlarmData> alarmPageData = findAlarmsByQueryAndCheck(assetAlarmQuery, 10);
 | 
			
		||||
        List<String> retrievedAlarmTypes = alarmPageData.getData().stream().map(Alarm::getType).toList();
 | 
			
		||||
        List<String> retrievedAlarmTypes = alarmPageData.getData().stream().map(AlarmData::getType).toList();
 | 
			
		||||
        assertThat(retrievedAlarmTypes).containsExactlyInAnyOrderElementsOf(assetAlarmTypes);
 | 
			
		||||
        List<String> retrievedAlarmDisplayName = alarmPageData.getData().stream().map(AlarmData::getOriginatorDisplayName).toList();
 | 
			
		||||
        assertThat(retrievedAlarmDisplayName).containsExactlyInAnyOrderElementsOf(assets.stream().map(Asset::getLabel).toList());
 | 
			
		||||
 | 
			
		||||
        KeyFilter nameFilter = buildStringKeyFilter(EntityKeyType.ENTITY_FIELD, "name", StringFilterPredicate.StringOperation.STARTS_WITH, "Asset1");
 | 
			
		||||
        List<KeyFilter> keyFilters = Collections.singletonList(nameFilter);
 | 
			
		||||
@ -1068,6 +1072,119 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
 | 
			
		||||
        countByQueryAndCheck(customerEntitiesQuery, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testFindDevicesByDisplayName() throws Exception {
 | 
			
		||||
        loginTenantAdmin();
 | 
			
		||||
        int numOfDevices = 3;
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < numOfDevices; i++) {
 | 
			
		||||
            Device device = new Device();
 | 
			
		||||
            String name = "Device" + i;
 | 
			
		||||
            device.setName(name);
 | 
			
		||||
            device.setLabel("Device Label " + i);
 | 
			
		||||
            device.setType("testFindDevicesByDisplayName");
 | 
			
		||||
 | 
			
		||||
            Device savedDevice = doPost("/api/device?accessToken=" + name, device, Device.class);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        DeviceTypeFilter filter = new DeviceTypeFilter();
 | 
			
		||||
        filter.setDeviceTypes(List.of("testFindDevicesByDisplayName"));
 | 
			
		||||
        filter.setDeviceNameFilter("");
 | 
			
		||||
 | 
			
		||||
        KeyFilter displayNameFilter = getEntityFieldEqualFilter("displayName", "Device Label " + 0);
 | 
			
		||||
 | 
			
		||||
        EntityDataSortOrder sortOrder = new EntityDataSortOrder(
 | 
			
		||||
                new EntityKey(EntityKeyType.ENTITY_FIELD, "displayName"), EntityDataSortOrder.Direction.ASC
 | 
			
		||||
        );
 | 
			
		||||
        EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
 | 
			
		||||
        List<EntityKey> entityFields = List.of(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"), new EntityKey(EntityKeyType.ENTITY_FIELD, "displayName"));
 | 
			
		||||
 | 
			
		||||
        // all devices with ownerName = TEST TENANT
 | 
			
		||||
        EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, Collections.emptyList(), Collections.emptyList());
 | 
			
		||||
        checkEntitiesByQuery(query, numOfDevices, (i, entity) -> {
 | 
			
		||||
            String name = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("name", new TsValue(0, "Invalid")).getValue();
 | 
			
		||||
            String displayName = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("displayName", new TsValue(0, "Invalid")).getValue();
 | 
			
		||||
            Assert.assertEquals("Device" + i, name);
 | 
			
		||||
            Assert.assertEquals("Device Label " + i, displayName);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // all devices with ownerName = TEST TENANT
 | 
			
		||||
        EntityDataQuery displayNameFilterQuery = new EntityDataQuery(filter, pageLink, entityFields, Collections.emptyList(), List.of(displayNameFilter));
 | 
			
		||||
        checkEntitiesByQuery(displayNameFilterQuery, 1, (i, entity) -> {
 | 
			
		||||
            String name = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("name", new TsValue(0, "Invalid")).getValue();
 | 
			
		||||
            String displayName = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("displayName", new TsValue(0, "Invalid")).getValue();
 | 
			
		||||
            Assert.assertEquals("Device" + i, name);
 | 
			
		||||
            Assert.assertEquals("Device Label " + i, displayName);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testFindUsersByDisplayName() throws Exception {
 | 
			
		||||
        loginTenantAdmin();
 | 
			
		||||
 | 
			
		||||
        User userA = new User();
 | 
			
		||||
        userA.setAuthority(Authority.TENANT_ADMIN);
 | 
			
		||||
        userA.setFirstName("John");
 | 
			
		||||
        userA.setLastName("Doe");
 | 
			
		||||
        userA.setEmail("john.doe@tb.org");
 | 
			
		||||
        userA = doPost("/api/user", userA, User.class);
 | 
			
		||||
        var aId = userA.getId();
 | 
			
		||||
 | 
			
		||||
        User userB = new User();
 | 
			
		||||
        userB.setAuthority(Authority.TENANT_ADMIN);
 | 
			
		||||
        userB.setFirstName("John");
 | 
			
		||||
        userB.setEmail("john@tb.org");
 | 
			
		||||
        userB = doPost("/api/user", userB, User.class);
 | 
			
		||||
        var bId = userB.getId();
 | 
			
		||||
 | 
			
		||||
        User userC = new User();
 | 
			
		||||
        userC.setAuthority(Authority.TENANT_ADMIN);
 | 
			
		||||
        userC.setLastName("Doe");
 | 
			
		||||
        userC.setEmail("doe@tb.org");
 | 
			
		||||
        userC = doPost("/api/user", userC, User.class);
 | 
			
		||||
        var cId = userC.getId();
 | 
			
		||||
 | 
			
		||||
        User userD = new User();
 | 
			
		||||
        userD.setAuthority(Authority.TENANT_ADMIN);
 | 
			
		||||
        userD.setEmail("noname@tb.org");
 | 
			
		||||
        userD = doPost("/api/user", userD, User.class);
 | 
			
		||||
        var dId = userD.getId();
 | 
			
		||||
 | 
			
		||||
        EntityTypeFilter filter = new EntityTypeFilter();
 | 
			
		||||
        filter.setEntityType(EntityType.USER);
 | 
			
		||||
 | 
			
		||||
        EntityDataSortOrder sortOrder = new EntityDataSortOrder(
 | 
			
		||||
                new EntityKey(EntityKeyType.ENTITY_FIELD, "displayName"), EntityDataSortOrder.Direction.ASC
 | 
			
		||||
        );
 | 
			
		||||
        EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
 | 
			
		||||
        List<EntityKey> entityFields = List.of(new EntityKey(EntityKeyType.ENTITY_FIELD, "displayName"));
 | 
			
		||||
 | 
			
		||||
        EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, Collections.emptyList(), List.of(getEntityFieldEqualFilter("displayName", "John Doe")));
 | 
			
		||||
        checkEntitiesByQuery(query, 1, (i, entity) -> {
 | 
			
		||||
            Assert.assertEquals(aId, entity.getEntityId());
 | 
			
		||||
            String displayName = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("displayName", new TsValue(0, "Invalid")).getValue();
 | 
			
		||||
            Assert.assertEquals("John Doe", displayName);
 | 
			
		||||
        });
 | 
			
		||||
        query = new EntityDataQuery(filter, pageLink, entityFields, Collections.emptyList(), List.of(getEntityFieldEqualFilter("displayName", "John")));
 | 
			
		||||
        checkEntitiesByQuery(query, 1, (i, entity) -> {
 | 
			
		||||
            Assert.assertEquals(bId, entity.getEntityId());
 | 
			
		||||
            String displayName = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("displayName", new TsValue(0, "Invalid")).getValue();
 | 
			
		||||
            Assert.assertEquals("John", displayName);
 | 
			
		||||
        });
 | 
			
		||||
        query = new EntityDataQuery(filter, pageLink, entityFields, Collections.emptyList(), List.of(getEntityFieldEqualFilter("displayName", "Doe")));
 | 
			
		||||
        checkEntitiesByQuery(query, 1, (i, entity) -> {
 | 
			
		||||
            Assert.assertEquals(cId, entity.getEntityId());
 | 
			
		||||
            String displayName = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("displayName", new TsValue(0, "Invalid")).getValue();
 | 
			
		||||
            Assert.assertEquals("Doe", displayName);
 | 
			
		||||
        });
 | 
			
		||||
        query = new EntityDataQuery(filter, pageLink, entityFields, Collections.emptyList(), List.of(getEntityFieldEqualFilter("displayName", "noname@tb.org")));
 | 
			
		||||
        checkEntitiesByQuery(query, 1, (i, entity) -> {
 | 
			
		||||
            Assert.assertEquals(dId, entity.getEntityId());
 | 
			
		||||
            String displayName = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("displayName", new TsValue(0, "Invalid")).getValue();
 | 
			
		||||
            Assert.assertEquals("noname@tb.org", displayName);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testFindDevicesByOwnerNameAndOwnerType() throws Exception {
 | 
			
		||||
        loginTenantAdmin();
 | 
			
		||||
@ -1105,19 +1222,30 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
 | 
			
		||||
 | 
			
		||||
        // all devices with ownerName = TEST TENANT
 | 
			
		||||
        EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, tenantOwnerNameFilter));
 | 
			
		||||
        checkEntitiesByQuery(query, numOfDevices, TEST_TENANT_NAME, "TENANT");
 | 
			
		||||
        BiConsumer<Integer, EntityData> checkFunction = (i, entity) -> {
 | 
			
		||||
            String name = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("name", new TsValue(0, "Invalid")).getValue();
 | 
			
		||||
            String ownerName = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("ownerName", new TsValue(0, "Invalid")).getValue();
 | 
			
		||||
            String ownerType = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("ownerType", new TsValue(0, "Invalid")).getValue();
 | 
			
		||||
            String alarmActiveTime = entity.getLatest().get(EntityKeyType.ATTRIBUTE).getOrDefault("alarmActiveTime", new TsValue(0, "-1")).getValue();
 | 
			
		||||
 | 
			
		||||
            Assert.assertEquals("Device" + i, name);
 | 
			
		||||
            Assert.assertEquals(TEST_TENANT_NAME, ownerName);
 | 
			
		||||
            Assert.assertEquals("TENANT", ownerType);
 | 
			
		||||
            Assert.assertEquals("1" + i, alarmActiveTime);
 | 
			
		||||
        };
 | 
			
		||||
        checkEntitiesByQuery(query, numOfDevices, checkFunction);
 | 
			
		||||
 | 
			
		||||
        // all devices with wrong ownerName
 | 
			
		||||
        EntityDataQuery wrongTenantNameQuery = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, wrongOwnerNameFilter));
 | 
			
		||||
        checkEntitiesByQuery(wrongTenantNameQuery, 0, null, null);
 | 
			
		||||
        checkEntitiesByQuery(wrongTenantNameQuery, 0, null);
 | 
			
		||||
 | 
			
		||||
        // all devices with owner type = TENANT
 | 
			
		||||
        EntityDataQuery tenantEntitiesQuery = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, tenantOwnerTypeFilter));
 | 
			
		||||
        checkEntitiesByQuery(tenantEntitiesQuery, numOfDevices, TEST_TENANT_NAME, "TENANT");
 | 
			
		||||
        checkEntitiesByQuery(tenantEntitiesQuery, numOfDevices, checkFunction);
 | 
			
		||||
 | 
			
		||||
        // all devices with owner type = CUSTOMER
 | 
			
		||||
        EntityDataQuery customerEntitiesQuery = new EntityDataQuery(filter, pageLink, entityFields, latestValues, List.of(activeAlarmTimeFilter, customerOwnerTypeFilter));
 | 
			
		||||
        checkEntitiesByQuery(customerEntitiesQuery, 0, null, null);
 | 
			
		||||
        checkEntitiesByQuery(customerEntitiesQuery, 0, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
@ -1163,6 +1291,28 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
 | 
			
		||||
        findByQueryAndCheck(query, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void checkEntitiesByQuery(EntityDataQuery query, int expectedNumOfDevices, BiConsumer<Integer,EntityData> checkFunction) throws Exception {
 | 
			
		||||
        await()
 | 
			
		||||
                .alias("data by query")
 | 
			
		||||
                .atMost(30, TimeUnit.SECONDS)
 | 
			
		||||
                .until(() -> {
 | 
			
		||||
                    var data = findByQuery(query);
 | 
			
		||||
                    var loadedEntities = new ArrayList<>(data.getData());
 | 
			
		||||
                    return loadedEntities.size() == expectedNumOfDevices;
 | 
			
		||||
                });
 | 
			
		||||
        if (expectedNumOfDevices == 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        var data = findByQuery(query);
 | 
			
		||||
        var loadedEntities = new ArrayList<>(data.getData());
 | 
			
		||||
 | 
			
		||||
        Assert.assertEquals(expectedNumOfDevices, loadedEntities.size());
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < expectedNumOfDevices; i++) {
 | 
			
		||||
            checkFunction.accept(i, loadedEntities.get(i));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void checkEntitiesByQuery(EntityDataQuery query, int expectedNumOfDevices, String expectedOwnerName, String expectedOwnerType) throws Exception {
 | 
			
		||||
        await()
 | 
			
		||||
                .alias("data by query")
 | 
			
		||||
 | 
			
		||||
@ -38,6 +38,11 @@ public class AlarmInfo extends Alarm {
 | 
			
		||||
    @Schema(description = "Alarm originator label", example = "Thermostat label")
 | 
			
		||||
    private String originatorLabel;
 | 
			
		||||
 | 
			
		||||
    @Getter
 | 
			
		||||
    @Setter
 | 
			
		||||
    @Schema(description = "Originator display name", example = "Thermostat")
 | 
			
		||||
    private String originatorDisplayName;
 | 
			
		||||
 | 
			
		||||
    @Getter
 | 
			
		||||
    @Setter
 | 
			
		||||
    @Schema(description = "Alarm assignee")
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ import lombok.Setter;
 | 
			
		||||
import lombok.ToString;
 | 
			
		||||
import org.thingsboard.server.common.data.AttributeScope;
 | 
			
		||||
import org.thingsboard.server.common.data.EntityType;
 | 
			
		||||
import org.thingsboard.server.common.data.StringUtils;
 | 
			
		||||
import org.thingsboard.server.common.data.edqs.fields.EntityFields;
 | 
			
		||||
import org.thingsboard.server.common.data.id.CustomerId;
 | 
			
		||||
import org.thingsboard.server.common.data.permission.QueryContext;
 | 
			
		||||
@ -139,11 +140,32 @@ public abstract class BaseEntityData<T extends EntityFields> implements EntityDa
 | 
			
		||||
            case "name" -> getEntityName();
 | 
			
		||||
            case "ownerName" -> getOwnerName();
 | 
			
		||||
            case "ownerType" -> getOwnerType();
 | 
			
		||||
            case "displayName" -> getDisplayName();
 | 
			
		||||
            case "entityType" -> Optional.ofNullable(getEntityType()).map(EntityType::name).orElse("");
 | 
			
		||||
            default -> fields.getAsString(name);
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getDisplayName(){
 | 
			
		||||
        return switch (getEntityType()) {
 | 
			
		||||
            case DEVICE, ASSET -> StringUtils.isNotBlank(fields.getLabel()) ? fields.getLabel() : fields.getName();
 | 
			
		||||
            case USER -> {
 | 
			
		||||
                boolean firstNameSet = StringUtils.isNotBlank(fields.getFirstName());
 | 
			
		||||
                boolean lastNameSet = StringUtils.isNotBlank(fields.getLastName());
 | 
			
		||||
                if(firstNameSet && lastNameSet) {
 | 
			
		||||
                    yield fields.getFirstName() + " " + fields.getLastName();
 | 
			
		||||
                } else if(firstNameSet) {
 | 
			
		||||
                    yield fields.getFirstName();
 | 
			
		||||
                } else if  (lastNameSet) {
 | 
			
		||||
                    yield fields.getLastName();
 | 
			
		||||
                } else {
 | 
			
		||||
                    yield fields.getEmail();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            default -> fields.getName();
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getEntityName() {
 | 
			
		||||
        return getFields().getName();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,7 @@ package org.thingsboard.server.edqs.data;
 | 
			
		||||
import lombok.ToString;
 | 
			
		||||
import org.thingsboard.server.common.data.AttributeScope;
 | 
			
		||||
import org.thingsboard.server.common.data.EntityType;
 | 
			
		||||
import org.thingsboard.server.common.data.StringUtils;
 | 
			
		||||
import org.thingsboard.server.common.data.edqs.fields.DeviceFields;
 | 
			
		||||
import org.thingsboard.server.common.data.query.EntityKeyType;
 | 
			
		||||
import org.thingsboard.server.common.data.edqs.DataPoint;
 | 
			
		||||
 | 
			
		||||
@ -121,6 +121,7 @@ public class AlarmDataAdapter {
 | 
			
		||||
        AlarmData alarmData = new AlarmData(alarm, entityId);
 | 
			
		||||
        alarmData.setOriginatorName(originatorName);
 | 
			
		||||
        alarmData.setOriginatorLabel(originatorLabel);
 | 
			
		||||
        alarmData.setOriginatorDisplayName(StringUtils.isBlank(originatorLabel) ? originatorName : originatorLabel);
 | 
			
		||||
        if (alarm.getAssigneeId() != null) {
 | 
			
		||||
            alarmData.setAssignee(new AlarmAssignee(alarm.getAssigneeId(), assigneeFirstName, assigneeLastName, assigneeEmail));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -65,6 +65,7 @@ public class EntityKeyMapping {
 | 
			
		||||
    public static final String NAME = "name";
 | 
			
		||||
    public static final String TYPE = "type";
 | 
			
		||||
    public static final String LABEL = "label";
 | 
			
		||||
    public static final String DISPLAY_NAME = "displayName";
 | 
			
		||||
    public static final String FIRST_NAME = "firstName";
 | 
			
		||||
    public static final String LAST_NAME = "lastName";
 | 
			
		||||
    public static final String EMAIL = "email";
 | 
			
		||||
@ -83,6 +84,8 @@ public class EntityKeyMapping {
 | 
			
		||||
    public static final String SERVICE_ID = "serviceId";
 | 
			
		||||
    public static final String OWNER_NAME = "ownerName";
 | 
			
		||||
    public static final String OWNER_TYPE = "ownerType";
 | 
			
		||||
    public static final String LABELED_ENTITY_DISPLAY_NAME_SELECT_QUERY = "COALESCE(NULLIF(TRIM(e." + LABEL + "), ''), e." + NAME + ")";
 | 
			
		||||
    public static final String USER_DISPLAY_NAME_SELECT_QUERY  = "COALESCE(NULLIF(TRIM(CONCAT_WS(' ', e.first_name, e.last_name)), ''), e.email)";
 | 
			
		||||
    public static final String OWNER_NAME_SELECT_QUERY = "case when e.customer_id = '" + NULL_UUID + "' " +
 | 
			
		||||
            "then (select title from tenant where id = e.tenant_id) " +
 | 
			
		||||
            "else (select title from customer where id = e.customer_id) end";
 | 
			
		||||
@ -94,6 +97,16 @@ public class EntityKeyMapping {
 | 
			
		||||
            OWNER_NAME, OWNER_NAME_SELECT_QUERY,
 | 
			
		||||
            OWNER_TYPE, OWNER_TYPE_SELECT_QUERY
 | 
			
		||||
    );
 | 
			
		||||
    public static final Map<String, String> labeledPropertiesFunctions = Map.of(
 | 
			
		||||
            OWNER_NAME, OWNER_NAME_SELECT_QUERY,
 | 
			
		||||
            OWNER_TYPE, OWNER_TYPE_SELECT_QUERY,
 | 
			
		||||
            DISPLAY_NAME, LABELED_ENTITY_DISPLAY_NAME_SELECT_QUERY
 | 
			
		||||
    );
 | 
			
		||||
    public static final Map<String, String> userPropertiesFunctions = Map.of(
 | 
			
		||||
            OWNER_NAME, OWNER_NAME_SELECT_QUERY,
 | 
			
		||||
            OWNER_TYPE, OWNER_TYPE_SELECT_QUERY,
 | 
			
		||||
            DISPLAY_NAME, USER_DISPLAY_NAME_SELECT_QUERY
 | 
			
		||||
    );
 | 
			
		||||
    public static final Map<String, String> queueStatsPropertiesFunctions = Map.of(NAME, QUEUE_STATS_NAME_QUERY);
 | 
			
		||||
 | 
			
		||||
    public static final List<String> typedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, ADDITIONAL_INFO);
 | 
			
		||||
@ -153,20 +166,24 @@ public class EntityKeyMapping {
 | 
			
		||||
        Map<String, String> contactBasedAliases = new HashMap<>();
 | 
			
		||||
        contactBasedAliases.put(NAME, TITLE);
 | 
			
		||||
        contactBasedAliases.put(LABEL, TITLE);
 | 
			
		||||
        contactBasedAliases.put(DISPLAY_NAME, TITLE);
 | 
			
		||||
        aliases.put(EntityType.TENANT, contactBasedAliases);
 | 
			
		||||
        aliases.put(EntityType.CUSTOMER, contactBasedAliases);
 | 
			
		||||
        aliases.put(EntityType.DASHBOARD, contactBasedAliases);
 | 
			
		||||
        Map<String, String> deviceAndAssetAliases = new HashMap<>();
 | 
			
		||||
        deviceAndAssetAliases.put(TITLE, NAME);
 | 
			
		||||
        aliases.put(EntityType.DEVICE, deviceAndAssetAliases);
 | 
			
		||||
        aliases.put(EntityType.ASSET, deviceAndAssetAliases);
 | 
			
		||||
        Map<String, String> commonEntityAliases = new HashMap<>();
 | 
			
		||||
        commonEntityAliases.put(TITLE, NAME);
 | 
			
		||||
        aliases.put(EntityType.DEVICE, commonEntityAliases);
 | 
			
		||||
        aliases.put(EntityType.ASSET, commonEntityAliases);
 | 
			
		||||
        commonEntityAliases.put(DISPLAY_NAME, NAME);
 | 
			
		||||
        aliases.put(EntityType.ENTITY_VIEW, commonEntityAliases);
 | 
			
		||||
        aliases.put(EntityType.WIDGETS_BUNDLE, commonEntityAliases);
 | 
			
		||||
 | 
			
		||||
        propertiesFunctions.put(EntityType.DEVICE, ownerPropertiesFunctions);
 | 
			
		||||
        propertiesFunctions.put(EntityType.ASSET, ownerPropertiesFunctions);
 | 
			
		||||
        propertiesFunctions.put(EntityType.DEVICE, labeledPropertiesFunctions);
 | 
			
		||||
        propertiesFunctions.put(EntityType.ASSET, labeledPropertiesFunctions);
 | 
			
		||||
        propertiesFunctions.put(EntityType.ENTITY_VIEW, ownerPropertiesFunctions);
 | 
			
		||||
        propertiesFunctions.put(EntityType.USER, ownerPropertiesFunctions);
 | 
			
		||||
        propertiesFunctions.put(EntityType.USER, userPropertiesFunctions);
 | 
			
		||||
        propertiesFunctions.put(EntityType.DASHBOARD, ownerPropertiesFunctions);
 | 
			
		||||
        propertiesFunctions.put(EntityType.QUEUE_STATS, queueStatsPropertiesFunctions);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -817,6 +817,7 @@ export class EntityService {
 | 
			
		||||
    switch (entityType) {
 | 
			
		||||
      case EntityType.USER:
 | 
			
		||||
        entityFieldKeys.push(entityFields.name.keyName);
 | 
			
		||||
        entityFieldKeys.push(entityFields.displayName.keyName);
 | 
			
		||||
        entityFieldKeys.push(entityFields.email.keyName);
 | 
			
		||||
        entityFieldKeys.push(entityFields.firstName.keyName);
 | 
			
		||||
        entityFieldKeys.push(entityFields.lastName.keyName);
 | 
			
		||||
@ -846,6 +847,7 @@ export class EntityService {
 | 
			
		||||
      case EntityType.EDGE:
 | 
			
		||||
      case EntityType.ASSET:
 | 
			
		||||
        entityFieldKeys.push(entityFields.name.keyName);
 | 
			
		||||
        entityFieldKeys.push(entityFields.displayName.keyName);
 | 
			
		||||
        entityFieldKeys.push(entityFields.type.keyName);
 | 
			
		||||
        entityFieldKeys.push(entityFields.label.keyName);
 | 
			
		||||
        entityFieldKeys.push(entityFields.ownerName.keyName);
 | 
			
		||||
 | 
			
		||||
@ -60,7 +60,7 @@
 | 
			
		||||
                    #entityAutocomplete="matAutocomplete"
 | 
			
		||||
                    [displayWith]="displayEntityFn">
 | 
			
		||||
    <mat-option *ngFor="let entity of filteredEntities | async" [value]="entity">
 | 
			
		||||
      <span [innerHTML]="entity.name | highlight:searchText:true:'ig'"></span>
 | 
			
		||||
      <span [innerHTML]="displayEntityFn(entity) | highlight:searchText:true:'ig'"></span>
 | 
			
		||||
    </mat-option>
 | 
			
		||||
    <mat-option *ngIf="!(filteredEntities | async)?.length" [value]="null">
 | 
			
		||||
      <div (click)="$event.stopPropagation()">
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,7 @@ import { catchError, debounceTime, map, share, switchMap, tap } from 'rxjs/opera
 | 
			
		||||
import { Store } from '@ngrx/store';
 | 
			
		||||
import { AppState } from '@app/core/core.state';
 | 
			
		||||
import { AliasEntityType, EntityType } from '@shared/models/entity-type.models';
 | 
			
		||||
import { BaseData } from '@shared/models/base-data';
 | 
			
		||||
import { BaseData, getEntityDisplayName } from '@shared/models/base-data';
 | 
			
		||||
import { EntityId } from '@shared/models/id/entity-id';
 | 
			
		||||
import { EntityService } from '@core/http/entity.service';
 | 
			
		||||
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
 | 
			
		||||
@ -138,6 +138,10 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
 | 
			
		||||
  @coerceArray()
 | 
			
		||||
  additionalClasses: Array<string>;
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  @coerceBoolean()
 | 
			
		||||
  useEntityDisplayName = false;
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  entityChanged = new EventEmitter<BaseData<EntityId>>();
 | 
			
		||||
 | 
			
		||||
@ -395,7 +399,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  displayEntityFn(entity?: BaseData<EntityId>): string | undefined {
 | 
			
		||||
    return entity ? entity.name : undefined;
 | 
			
		||||
    return entity ? (this.useEntityDisplayName ? getEntityDisplayName(entity) : entity.name) : undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> {
 | 
			
		||||
 | 
			
		||||
@ -36,6 +36,7 @@
 | 
			
		||||
    *ngIf="modelValue.entityType"
 | 
			
		||||
    [required]="required"
 | 
			
		||||
    [entityType]="modelValue.entityType"
 | 
			
		||||
    [useEntityDisplayName]="useEntityDisplayName"
 | 
			
		||||
    formControlName="entityIds">
 | 
			
		||||
  </tb-entity-list>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -68,6 +68,9 @@ export class EntityListSelectComponent implements ControlValueAccessor, OnInit {
 | 
			
		||||
  @Input()
 | 
			
		||||
  additionEntityTypes: {[key in string]: string} = {};
 | 
			
		||||
 | 
			
		||||
  @Input({transform: booleanAttribute})
 | 
			
		||||
  useEntityDisplayName = false;
 | 
			
		||||
 | 
			
		||||
  displayEntityTypeSelect: boolean;
 | 
			
		||||
 | 
			
		||||
  private defaultEntityType: EntityType | AliasEntityType = null;
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,7 @@
 | 
			
		||||
      class="tb-chip-row-ellipsis"
 | 
			
		||||
      [removable]="!disabled"
 | 
			
		||||
      (removed)="remove(entity)">
 | 
			
		||||
      {{entity.name}}
 | 
			
		||||
      {{ displayEntityFn(entity) }}
 | 
			
		||||
      <mat-icon matChipRemove *ngIf="!disabled">close</mat-icon>
 | 
			
		||||
    </mat-chip-row>
 | 
			
		||||
    <input matInput type="text" placeholder="{{ !disabled ? placeholderText : '' }}"
 | 
			
		||||
@ -51,7 +51,7 @@
 | 
			
		||||
                    class="tb-autocomplete"
 | 
			
		||||
                    [displayWith]="displayEntityFn">
 | 
			
		||||
    <mat-option *ngFor="let entity of filteredEntities | async" [value]="entity">
 | 
			
		||||
      <span [innerHTML]="entity.name | highlight:searchText"></span>
 | 
			
		||||
      <span [innerHTML]="displayEntityFn(entity) | highlight:searchText"></span>
 | 
			
		||||
    </mat-option>
 | 
			
		||||
    <mat-option *ngIf="!(filteredEntities | async)?.length" [value]="null">
 | 
			
		||||
      <div (click)="$event.stopPropagation()">
 | 
			
		||||
 | 
			
		||||
@ -39,7 +39,7 @@ import { Observable } from 'rxjs';
 | 
			
		||||
import { filter, map, mergeMap, share, tap } from 'rxjs/operators';
 | 
			
		||||
import { TranslateService } from '@ngx-translate/core';
 | 
			
		||||
import { EntityType } from '@shared/models/entity-type.models';
 | 
			
		||||
import { BaseData } from '@shared/models/base-data';
 | 
			
		||||
import { BaseData, getEntityDisplayName } from '@shared/models/base-data';
 | 
			
		||||
import { EntityId } from '@shared/models/id/entity-id';
 | 
			
		||||
import { EntityService } from '@core/http/entity.service';
 | 
			
		||||
import { MatAutocomplete } from '@angular/material/autocomplete';
 | 
			
		||||
@ -125,6 +125,10 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, OnChan
 | 
			
		||||
  @coerceBoolean()
 | 
			
		||||
  allowCreateNew: boolean;
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  @coerceBoolean()
 | 
			
		||||
  useEntityDisplayName = false;
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  createNew = new EventEmitter<string>();
 | 
			
		||||
 | 
			
		||||
@ -277,7 +281,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, OnChan
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public displayEntityFn(entity?: BaseData<EntityId>): string | undefined {
 | 
			
		||||
    return entity ? entity.name : undefined;
 | 
			
		||||
    return entity ? (this.useEntityDisplayName ? getEntityDisplayName(entity) : entity.name) : undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> {
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,7 @@
 | 
			
		||||
    [appearance]="appearance"
 | 
			
		||||
    [required]="required"
 | 
			
		||||
    [entityType]="modelValue.entityType"
 | 
			
		||||
    [useEntityDisplayName]="useEntityDisplayName"
 | 
			
		||||
    formControlName="entityId">
 | 
			
		||||
  </tb-entity-autocomplete>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -62,6 +62,10 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte
 | 
			
		||||
  @Input()
 | 
			
		||||
  appearance: MatFormFieldAppearance = 'fill';
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  @coerceBoolean()
 | 
			
		||||
  useEntityDisplayName = false;
 | 
			
		||||
 | 
			
		||||
  displayEntityTypeSelect: boolean;
 | 
			
		||||
 | 
			
		||||
  AliasEntityType = AliasEntityType;
 | 
			
		||||
 | 
			
		||||
@ -140,6 +140,7 @@ export interface AlarmCommentInfo extends AlarmComment {
 | 
			
		||||
export interface AlarmInfo extends Alarm {
 | 
			
		||||
  originatorName: string;
 | 
			
		||||
  originatorLabel: string;
 | 
			
		||||
  originatorDisplayName?: string;
 | 
			
		||||
  assignee: AlarmAssignee;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -172,6 +173,7 @@ export const simulatedAlarm: AlarmInfo = {
 | 
			
		||||
  clearTs: 0,
 | 
			
		||||
  assignTs: 0,
 | 
			
		||||
  originatorName: 'Simulated',
 | 
			
		||||
  originatorDisplayName: 'Simulated',
 | 
			
		||||
  originatorLabel: 'Simulated',
 | 
			
		||||
  assignee: {
 | 
			
		||||
    firstName: '',
 | 
			
		||||
@ -242,6 +244,11 @@ export const alarmFields: {[fieldName: string]: AlarmField} = {
 | 
			
		||||
    value: 'originatorName',
 | 
			
		||||
    name: 'alarm.originator'
 | 
			
		||||
  },
 | 
			
		||||
  originatorDisplayName: {
 | 
			
		||||
    keyName: 'originatorDisplayName',
 | 
			
		||||
    value: 'originatorDisplayName',
 | 
			
		||||
    name: 'alarm.originator'
 | 
			
		||||
  },
 | 
			
		||||
  originatorLabel: {
 | 
			
		||||
    keyName: 'originatorLabel',
 | 
			
		||||
    value: 'originatorLabel',
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,9 @@
 | 
			
		||||
 | 
			
		||||
import { EntityId } from '@shared/models/id/entity-id';
 | 
			
		||||
import { HasUUID } from '@shared/models/id/has-uuid';
 | 
			
		||||
import { isDefinedAndNotNull } from '@core/utils';
 | 
			
		||||
import { isDefinedAndNotNull, isNotEmptyStr } from '@core/utils';
 | 
			
		||||
import { EntityType } from '@shared/models/entity-type.models';
 | 
			
		||||
import { User } from '@shared/models/user.model';
 | 
			
		||||
 | 
			
		||||
export declare type HasId = EntityId | HasUUID;
 | 
			
		||||
 | 
			
		||||
@ -49,3 +51,12 @@ export function hasIdEquals(id1: HasId, id2: HasId): boolean {
 | 
			
		||||
    return id1 === id2;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getEntityDisplayName(entity: BaseData<EntityId>): string {
 | 
			
		||||
  if (entity?.id?.entityType === EntityType.USER) {
 | 
			
		||||
    const user = entity as User;
 | 
			
		||||
    const userName = (user?.firstName ?? '') + " " + (user?.lastName ?? '');
 | 
			
		||||
    return isNotEmptyStr(userName) ? userName.trim() : entity?.name;
 | 
			
		||||
  }
 | 
			
		||||
  return isNotEmptyStr(entity?.label) ? entity.label : entity?.name;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -163,6 +163,11 @@ export const entityFields: {[fieldName: string]: EntityField} = {
 | 
			
		||||
    name: 'entity-field.label',
 | 
			
		||||
    value: 'label'
 | 
			
		||||
  },
 | 
			
		||||
  displayName: {
 | 
			
		||||
    keyName: 'displayName',
 | 
			
		||||
    name: 'entity-field.name',
 | 
			
		||||
    value: 'name'
 | 
			
		||||
  },
 | 
			
		||||
  queueName: {
 | 
			
		||||
    keyName: 'queueName',
 | 
			
		||||
    name: 'entity-field.queue-name',
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user