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