Commit 44a938d8f6a70bbbe5bf417c0906046c4971013c

Authored by Igor Kulikov
1 parent 416432e3

Propagate UI changes

@@ -57,6 +57,14 @@ export class AppComponent implements OnInit { @@ -57,6 +57,14 @@ export class AppComponent implements OnInit {
57 '0,10 0 0,1 2,12A10,10 0 0,1 12,2Z" /></svg>' 57 '0,10 0 0,1 2,12A10,10 0 0,1 12,2Z" /></svg>'
58 ) 58 )
59 ); 59 );
  60 + this.matIconRegistry.addSvgIconLiteral(
  61 + 'alpha-e-circle-outline',
  62 + this.domSanitizer.bypassSecurityTrustHtml(
  63 + '<svg viewBox="0 0 24 24"><path d="M9,7H15V9H11V11H15V13H11V15H15V17H9V7M12,2A10,10 0 0,'+
  64 + '1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 ' +
  65 + '0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" /></svg>'
  66 + )
  67 + );
60 68
61 this.storageService.testLocalStorage(); 69 this.storageService.testLocalStorage();
62 70
@@ -50,6 +50,8 @@ import { AlarmSourceListener } from '@core/http/alarm.service'; @@ -50,6 +50,8 @@ import { AlarmSourceListener } from '@core/http/alarm.service';
50 import { DatasourceListener } from '@core/api/datasource.service'; 50 import { DatasourceListener } from '@core/api/datasource.service';
51 import * as deepEqual from 'deep-equal'; 51 import * as deepEqual from 'deep-equal';
52 import { EntityId } from '@app/shared/models/id/entity-id'; 52 import { EntityId } from '@app/shared/models/id/entity-id';
  53 +import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
  54 +import { entityFields } from '@shared/models/entity.models';
53 55
54 export class WidgetSubscription implements IWidgetSubscription { 56 export class WidgetSubscription implements IWidgetSubscription {
55 57
@@ -342,6 +344,12 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -342,6 +344,12 @@ export class WidgetSubscription implements IWidgetSubscription {
342 dataKey, 344 dataKey,
343 data: [] 345 data: []
344 }; 346 };
  347 + if (dataKey.type === DataKeyType.entityField && datasource.entity) {
  348 + const propName = entityFields[dataKey.name] ? entityFields[dataKey.name].value : dataKey.name;
  349 + if (datasource.entity[propName]) {
  350 + datasourceData.data.push([Date.now(), datasource.entity[propName]]);
  351 + }
  352 + }
345 this.data.push(datasourceData); 353 this.data.push(datasourceData);
346 this.hiddenData.push({data: []}); 354 this.hiddenData.push({data: []});
347 if (this.displayLegend) { 355 if (this.displayLegend) {
@@ -682,8 +690,15 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -682,8 +690,15 @@ export class WidgetSubscription implements IWidgetSubscription {
682 }, 690 },
683 datasourceIndex: index 691 datasourceIndex: index
684 }; 692 };
  693 +
  694 + let entityFieldKey = false;
  695 +
685 for (let a = 0; a < datasource.dataKeys.length; a++) { 696 for (let a = 0; a < datasource.dataKeys.length; a++) {
686 - this.data[index + a].data = []; 697 + if (datasource.dataKeys[a].type !== DataKeyType.entityField) {
  698 + this.data[index + a].data = [];
  699 + } else {
  700 + entityFieldKey = true;
  701 + }
687 } 702 }
688 index += datasource.dataKeys.length; 703 index += datasource.dataKeys.length;
689 this.datasourceListeners.push(listener); 704 this.datasourceListeners.push(listener);
@@ -691,7 +706,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -691,7 +706,7 @@ export class WidgetSubscription implements IWidgetSubscription {
691 if (datasource.dataKeys.length) { 706 if (datasource.dataKeys.length) {
692 this.ctx.datasourceService.subscribeToDatasource(listener); 707 this.ctx.datasourceService.subscribeToDatasource(listener);
693 } 708 }
694 - if (datasource.unresolvedStateEntity || 709 + if (datasource.unresolvedStateEntity || entityFieldKey ||
695 !datasource.dataKeys.length || 710 !datasource.dataKeys.length ||
696 (datasource.type === DatasourceType.entity && !datasource.entityId) 711 (datasource.type === DatasourceType.entity && !datasource.entityId)
697 ) { 712 ) {
@@ -45,11 +45,11 @@ export class AttributeService { @@ -45,11 +45,11 @@ export class AttributeService {
45 defaultHttpOptionsFromConfig(config)); 45 defaultHttpOptionsFromConfig(config));
46 } 46 }
47 47
48 - public deleteEntityTimeseries(entityId: EntityId, timeseries: Array<AttributeData>, 48 + public deleteEntityTimeseries(entityId: EntityId, timeseries: Array<AttributeData>, deleteAllDataForKeys = false,
49 config?: RequestConfig): Observable<any> { 49 config?: RequestConfig): Observable<any> {
50 const keys = timeseries.map(attribute => attribute.key).join(','); 50 const keys = timeseries.map(attribute => attribute.key).join(',');
51 return this.http.delete(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/delete` + 51 return this.http.delete(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/delete` +
52 - `?keys=${keys}`, 52 + `?keys=${keys}&deleteAllDataForKeys=${deleteAllDataForKeys}`,
53 defaultHttpOptionsFromConfig(config)); 53 defaultHttpOptionsFromConfig(config));
54 } 54 }
55 55
@@ -93,7 +93,7 @@ export class AttributeService { @@ -93,7 +93,7 @@ export class AttributeService {
93 }); 93 });
94 let deleteEntityTimeseriesObservable: Observable<any>; 94 let deleteEntityTimeseriesObservable: Observable<any>;
95 if (deleteTimeseries.length) { 95 if (deleteTimeseries.length) {
96 - deleteEntityTimeseriesObservable = this.deleteEntityTimeseries(entityId, deleteTimeseries, config); 96 + deleteEntityTimeseriesObservable = this.deleteEntityTimeseries(entityId, deleteTimeseries, true, config);
97 } else { 97 } else {
98 deleteEntityTimeseriesObservable = of(null); 98 deleteEntityTimeseriesObservable = of(null);
99 } 99 }
@@ -20,7 +20,13 @@ import { Observable, ReplaySubject } from 'rxjs'; @@ -20,7 +20,13 @@ import { Observable, ReplaySubject } from 'rxjs';
20 import { HttpClient } from '@angular/common/http'; 20 import { HttpClient } from '@angular/common/http';
21 import { PageLink } from '@shared/models/page/page-link'; 21 import { PageLink } from '@shared/models/page/page-link';
22 import { PageData } from '@shared/models/page/page-data'; 22 import { PageData } from '@shared/models/page/page-data';
23 -import { Device, DeviceCredentials, DeviceInfo, DeviceSearchQuery } from '@app/shared/models/device.models'; 23 +import {
  24 + ClaimRequest, ClaimResult,
  25 + Device,
  26 + DeviceCredentials,
  27 + DeviceInfo,
  28 + DeviceSearchQuery
  29 +} from '@app/shared/models/device.models';
24 import { EntitySubtype } from '@app/shared/models/entity-type.models'; 30 import { EntitySubtype } from '@app/shared/models/entity-type.models';
25 import { AuthService } from '@core/auth/auth.service'; 31 import { AuthService } from '@core/auth/auth.service';
26 32
@@ -127,4 +133,13 @@ export class DeviceService { @@ -127,4 +133,13 @@ export class DeviceService {
127 return this.http.get<Device>(`/api/tenant/devices?deviceName=${deviceName}`, defaultHttpOptionsFromConfig(config)); 133 return this.http.get<Device>(`/api/tenant/devices?deviceName=${deviceName}`, defaultHttpOptionsFromConfig(config));
128 } 134 }
129 135
  136 + public claimDevice(deviceName: string, claimRequest: ClaimRequest,
  137 + config?: RequestConfig): Observable<ClaimResult> {
  138 + return this.http.post<ClaimResult>(`api/customer/device/${deviceName}/claim`, claimRequest, defaultHttpOptionsFromConfig(config));
  139 + }
  140 +
  141 + public unclaimDevice(deviceName: string, config?: RequestConfig) {
  142 + return this.http.delete(`/api/customer/device/${deviceName}/claim`, defaultHttpOptionsFromConfig(config));
  143 + }
  144 +
130 } 145 }
@@ -44,7 +44,7 @@ import { AliasInfo, StateParams, SubscriptionInfo } from '@core/api/widget-api.m @@ -44,7 +44,7 @@ import { AliasInfo, StateParams, SubscriptionInfo } from '@core/api/widget-api.m
44 import { Datasource, DatasourceType, KeyInfo } from '@app/shared/models/widget.models'; 44 import { Datasource, DatasourceType, KeyInfo } from '@app/shared/models/widget.models';
45 import { UtilsService } from '@core/services/utils.service'; 45 import { UtilsService } from '@core/services/utils.service';
46 import { AliasFilterType, EntityAlias, EntityAliasFilter, EntityAliasFilterResult } from '@shared/models/alias.models'; 46 import { AliasFilterType, EntityAlias, EntityAliasFilter, EntityAliasFilterResult } from '@shared/models/alias.models';
47 -import { EntityInfo, ImportEntitiesResultInfo, ImportEntityData } from '@shared/models/entity.models'; 47 +import { entityFields, EntityInfo, ImportEntitiesResultInfo, ImportEntityData } from '@shared/models/entity.models';
48 import { 48 import {
49 EntityRelationInfo, 49 EntityRelationInfo,
50 EntityRelationsQuery, 50 EntityRelationsQuery,
@@ -503,8 +503,50 @@ export class EntityService { @@ -503,8 +503,50 @@ export class EntityService {
503 return entityTypes; 503 return entityTypes;
504 } 504 }
505 505
  506 + private getEntityFieldKeys (entityType: EntityType, searchText: string): Array<string> {
  507 + const entityFieldKeys: string[] = [];
  508 + const query = searchText.toLowerCase();
  509 + switch(entityType) {
  510 + case EntityType.USER:
  511 + entityFieldKeys.push(entityFields.name.keyName);
  512 + entityFieldKeys.push(entityFields.email.keyName);
  513 + entityFieldKeys.push(entityFields.firstName.keyName);
  514 + entityFieldKeys.push(entityFields.lastName.keyName);
  515 + break;
  516 + case EntityType.TENANT:
  517 + case EntityType.CUSTOMER:
  518 + entityFieldKeys.push(entityFields.title.keyName);
  519 + entityFieldKeys.push(entityFields.email.keyName);
  520 + entityFieldKeys.push(entityFields.country.keyName);
  521 + entityFieldKeys.push(entityFields.state.keyName);
  522 + entityFieldKeys.push(entityFields.city.keyName);
  523 + entityFieldKeys.push(entityFields.address.keyName);
  524 + entityFieldKeys.push(entityFields.address2.keyName);
  525 + entityFieldKeys.push(entityFields.zip.keyName);
  526 + entityFieldKeys.push(entityFields.phone.keyName);
  527 + break;
  528 + case EntityType.ENTITY_VIEW:
  529 + entityFieldKeys.push(entityFields.name.keyName);
  530 + entityFieldKeys.push(entityFields.type.keyName);
  531 + break;
  532 + case EntityType.DEVICE:
  533 + case EntityType.ASSET:
  534 + entityFieldKeys.push(entityFields.name.keyName);
  535 + entityFieldKeys.push(entityFields.type.keyName);
  536 + entityFieldKeys.push(entityFields.label.keyName);
  537 + break;
  538 + case EntityType.DASHBOARD:
  539 + entityFieldKeys.push(entityFields.title.keyName);
  540 + break;
  541 + }
  542 + return query ? entityFieldKeys.filter((entityField) => entityField.toLowerCase().indexOf(query) === 0) : entityFieldKeys;
  543 + }
  544 +
506 public getEntityKeys(entityId: EntityId, query: string, type: DataKeyType, 545 public getEntityKeys(entityId: EntityId, query: string, type: DataKeyType,
507 config?: RequestConfig): Observable<Array<string>> { 546 config?: RequestConfig): Observable<Array<string>> {
  547 + if (type === DataKeyType.entityField) {
  548 + return of(this.getEntityFieldKeys(entityId.entityType as EntityType, query));
  549 + }
508 let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/keys/`; 550 let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/keys/`;
509 if (type === DataKeyType.timeseries) { 551 if (type === DataKeyType.timeseries) {
510 url += 'timeseries'; 552 url += 'timeseries';
@@ -588,7 +630,8 @@ export class EntityService { @@ -588,7 +630,8 @@ export class EntityService {
588 if (filter.stateEntityParamName && filter.stateEntityParamName.length) { 630 if (filter.stateEntityParamName && filter.stateEntityParamName.length) {
589 result.entityParamName = filter.stateEntityParamName; 631 result.entityParamName = filter.stateEntityParamName;
590 } 632 }
591 - const stateEntityId = this.getStateEntityId(filter, stateParams); 633 + const stateEntityInfo = this.getStateEntityInfo(filter, stateParams);
  634 + const stateEntityId = stateEntityInfo.entityId;
592 switch (filter.type) { 635 switch (filter.type) {
593 case AliasFilterType.singleEntity: 636 case AliasFilterType.singleEntity:
594 const aliasEntityId = this.resolveAliasEntityId(filter.singleEntity.entityType, filter.singleEntity.id); 637 const aliasEntityId = this.resolveAliasEntityId(filter.singleEntity.entityType, filter.singleEntity.id);
@@ -697,7 +740,8 @@ export class EntityService { @@ -697,7 +740,8 @@ export class EntityService {
697 parameters: { 740 parameters: {
698 rootId: relationQueryRootEntityId.id, 741 rootId: relationQueryRootEntityId.id,
699 rootType: relationQueryRootEntityId.entityType as EntityType, 742 rootType: relationQueryRootEntityId.entityType as EntityType,
700 - direction: filter.direction 743 + direction: filter.direction,
  744 + fetchLastLevelOnly: filter.fetchLastLevelOnly
701 }, 745 },
702 filters: filter.filters 746 filters: filter.filters
703 }; 747 };
@@ -741,10 +785,12 @@ export class EntityService { @@ -741,10 +785,12 @@ export class EntityService {
741 parameters: { 785 parameters: {
742 rootId: searchQueryRootEntityId.id, 786 rootId: searchQueryRootEntityId.id,
743 rootType: searchQueryRootEntityId.entityType as EntityType, 787 rootType: searchQueryRootEntityId.entityType as EntityType,
744 - direction: filter.direction 788 + direction: filter.direction,
  789 + fetchLastLevelOnly: filter.fetchLastLevelOnly
745 }, 790 },
746 relationType: filter.relationType 791 relationType: filter.relationType
747 }; 792 };
  793 + searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
748 let findByQueryObservable: Observable<Array<BaseData<EntityId>>>; 794 let findByQueryObservable: Observable<Array<BaseData<EntityId>>>;
749 if (filter.type === AliasFilterType.assetSearchQuery) { 795 if (filter.type === AliasFilterType.assetSearchQuery) {
750 const assetSearchQuery = searchQuery as AssetSearchQuery; 796 const assetSearchQuery = searchQuery as AssetSearchQuery;
@@ -988,8 +1034,8 @@ export class EntityService { @@ -988,8 +1034,8 @@ export class EntityService {
988 ); 1034 );
989 } 1035 }
990 1036
991 - private getStateEntityId(filter: EntityAliasFilter, stateParams: StateParams): EntityId {  
992 - let entityId = null; 1037 + private getStateEntityInfo(filter: EntityAliasFilter, stateParams: StateParams): {entityId: EntityId} {
  1038 + let entityId: EntityId = null;
993 if (stateParams) { 1039 if (stateParams) {
994 if (filter.stateEntityParamName && filter.stateEntityParamName.length) { 1040 if (filter.stateEntityParamName && filter.stateEntityParamName.length) {
995 if (stateParams[filter.stateEntityParamName]) { 1041 if (stateParams[filter.stateEntityParamName]) {
@@ -1005,7 +1051,7 @@ export class EntityService { @@ -1005,7 +1051,7 @@ export class EntityService {
1005 if (entityId) { 1051 if (entityId) {
1006 entityId = this.resolveAliasEntityId(entityId.entityType, entityId.id); 1052 entityId = this.resolveAliasEntityId(entityId.entityType, entityId.id);
1007 } 1053 }
1008 - return entityId; 1054 + return {entityId};
1009 } 1055 }
1010 1056
1011 private resolveAliasEntityId(entityType: EntityType | AliasEntityType, id: string): EntityId { 1057 private resolveAliasEntityId(entityType: EntityType | AliasEntityType, id: string): EntityId {
@@ -133,6 +133,14 @@ @@ -133,6 +133,14 @@
133 </tb-entity-select> 133 </tb-entity-select>
134 </div> 134 </div>
135 </div> 135 </div>
  136 + <div fxFlex fxLayout="row">
  137 + <section class="tb-root-state-entity-switch" fxLayout="row" fxLayoutAlign="start center" style="padding-left: 0px;">
  138 + <mat-slide-toggle class="root-state-entity-switch"
  139 + formControlName="fetchLastLevelOnly">
  140 + </mat-slide-toggle>
  141 + <label class="tb-small root-state-entity-label" translate>alias.last-level-relation</label>
  142 + </section>
  143 + </div>
136 <div fxFlex fxLayoutGap="8px" fxLayout="row"> 144 <div fxFlex fxLayoutGap="8px" fxLayout="row">
137 <mat-form-field class="mat-block" style="min-width: 100px;"> 145 <mat-form-field class="mat-block" style="min-width: 100px;">
138 <mat-label translate>relation.direction</mat-label> 146 <mat-label translate>relation.direction</mat-label>
@@ -191,6 +199,14 @@ @@ -191,6 +199,14 @@
191 </tb-entity-select> 199 </tb-entity-select>
192 </div> 200 </div>
193 </div> 201 </div>
  202 + <div fxFlex fxLayout="row">
  203 + <section class="tb-root-state-entity-switch" fxLayout="row" fxLayoutAlign="start center" style="padding-left: 0px;">
  204 + <mat-slide-toggle class="root-state-entity-switch"
  205 + formControlName="fetchLastLevelOnly">
  206 + </mat-slide-toggle>
  207 + <label class="tb-small root-state-entity-label" translate>alias.last-level-relation</label>
  208 + </section>
  209 + </div>
194 <div fxFlex fxLayoutGap="8px" fxLayout="row"> 210 <div fxFlex fxLayoutGap="8px" fxLayout="row">
195 <mat-form-field class="mat-block" style="min-width: 100px;"> 211 <mat-form-field class="mat-block" style="min-width: 100px;">
196 <mat-label translate>relation.direction</mat-label> 212 <mat-label translate>relation.direction</mat-label>
@@ -25,6 +25,7 @@ @@ -25,6 +25,7 @@
25 25
26 .tb-root-state-entity-switch { 26 .tb-root-state-entity-switch {
27 padding-left: 10px; 27 padding-left: 10px;
  28 + padding-bottom: 10px;
28 29
29 .root-state-entity-switch { 30 .root-state-entity-switch {
30 margin: 0; 31 margin: 0;
@@ -165,6 +165,7 @@ export class EntityFilterComponent implements ControlValueAccessor, OnInit { @@ -165,6 +165,7 @@ export class EntityFilterComponent implements ControlValueAccessor, OnInit {
165 rootEntity: [filter ? filter.rootEntity : null, (filter && filter.rootStateEntity) ? [] : [Validators.required]], 165 rootEntity: [filter ? filter.rootEntity : null, (filter && filter.rootStateEntity) ? [] : [Validators.required]],
166 direction: [filter ? filter.direction : EntitySearchDirection.FROM, [Validators.required]], 166 direction: [filter ? filter.direction : EntitySearchDirection.FROM, [Validators.required]],
167 maxLevel: [filter ? filter.maxLevel : 1, []], 167 maxLevel: [filter ? filter.maxLevel : 1, []],
  168 + fetchLastLevelOnly: [filter ? filter.fetchLastLevelOnly : false, []]
168 }); 169 });
169 this.filterFormGroup.get('rootStateEntity').valueChanges.subscribe((rootStateEntity: boolean) => { 170 this.filterFormGroup.get('rootStateEntity').valueChanges.subscribe((rootStateEntity: boolean) => {
170 this.filterFormGroup.get('rootEntity').setValidators(rootStateEntity ? [] : [Validators.required]); 171 this.filterFormGroup.get('rootEntity').setValidators(rootStateEntity ? [] : [Validators.required]);
@@ -38,6 +38,12 @@ @@ -38,6 +38,12 @@
38 <mat-icon class="tb-mat-20" 38 <mat-icon class="tb-mat-20"
39 svgIcon="alpha-a-circle-outline"></mat-icon> 39 svgIcon="alpha-a-circle-outline"></mat-icon>
40 </span> 40 </span>
  41 + <span *ngIf="key.type === dataKeyTypes.entityField"
  42 + matTooltip="{{'datakey.entity-field' | translate }}"
  43 + matTooltipPosition="above">
  44 + <mat-icon class="tb-mat-20"
  45 + svgIcon="alpha-e-circle-outline"></mat-icon>
  46 + </span>
41 <span *ngIf="key.type === dataKeyTypes.timeseries" 47 <span *ngIf="key.type === dataKeyTypes.timeseries"
42 matTooltip="{{'datakey.timeseries' | translate }}" 48 matTooltip="{{'datakey.timeseries' | translate }}"
43 matTooltipPosition="above"> 49 matTooltipPosition="above">
@@ -87,6 +93,12 @@ @@ -87,6 +93,12 @@
87 <mat-icon class="tb-mat-16" 93 <mat-icon class="tb-mat-16"
88 svgIcon="alpha-a-circle-outline"></mat-icon> 94 svgIcon="alpha-a-circle-outline"></mat-icon>
89 </span> 95 </span>
  96 + <span *ngIf="key.type === dataKeyTypes.entityField"
  97 + matTooltip="{{'datakey.entity-field' | translate }}"
  98 + matTooltipPosition="above">
  99 + <mat-icon class="tb-mat-16"
  100 + svgIcon="alpha-e-circle-outline"></mat-icon>
  101 + </span>
90 <span *ngIf="key.type === dataKeyTypes.timeseries" 102 <span *ngIf="key.type === dataKeyTypes.timeseries"
91 matTooltip="{{'datakey.timeseries' | translate }}" 103 matTooltip="{{'datakey.timeseries' | translate }}"
92 matTooltipPosition="above"> 104 matTooltipPosition="above">
@@ -114,15 +126,22 @@ @@ -114,15 +126,22 @@
114 <span *ngIf="widgetType == widgetTypes.latest" 126 <span *ngIf="widgetType == widgetTypes.latest"
115 matTooltip="{{'datakey.attributes' | translate }}" 127 matTooltip="{{'datakey.attributes' | translate }}"
116 matTooltipPosition="above"> 128 matTooltipPosition="above">
117 - <mat-icon (click)="createKey(searchText, dataKeyTypes.attribute)" 129 + <mat-icon (click)="createKey(searchText, dataKeyTypes.attribute)"
118 class="tb-mat-16" 130 class="tb-mat-16"
119 svgIcon="alpha-a-circle-outline"></mat-icon> 131 svgIcon="alpha-a-circle-outline"></mat-icon>
120 - </span> 132 + </span>
  133 + <span *ngIf="widgetType == widgetTypes.latest"
  134 + matTooltip="{{'datakey.entity-field' | translate }}"
  135 + matTooltipPosition="above">
  136 + <mat-icon (click)="createKey(searchText, dataKeyTypes.entityField)"
  137 + class="tb-mat-16"
  138 + svgIcon="alpha-e-circle-outline"></mat-icon>
  139 + </span>
121 <span matTooltip="{{'datakey.timeseries' | translate }}" 140 <span matTooltip="{{'datakey.timeseries' | translate }}"
122 matTooltipPosition="above"> 141 matTooltipPosition="above">
123 - <mat-icon (click)="createKey(searchText, dataKeyTypes.timeseries)" 142 + <mat-icon (click)="createKey(searchText, dataKeyTypes.timeseries)"
124 class="tb-mat-16">timeline</mat-icon> 143 class="tb-mat-16">timeline</mat-icon>
125 - </span> 144 + </span>
126 </ng-template> 145 </ng-template>
127 </ng-template> 146 </ng-template>
128 </div> 147 </div>
@@ -420,6 +420,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie @@ -420,6 +420,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie
420 const dataKeyTypes = [DataKeyType.timeseries]; 420 const dataKeyTypes = [DataKeyType.timeseries];
421 if (this.widgetType === widgetType.latest) { 421 if (this.widgetType === widgetType.latest) {
422 dataKeyTypes.push(DataKeyType.attribute); 422 dataKeyTypes.push(DataKeyType.attribute);
  423 + dataKeyTypes.push(DataKeyType.entityField);
423 } 424 }
424 fetchObservable = this.callbacks.fetchEntityKeys(this.entityAliasId, this.searchText, dataKeyTypes); 425 fetchObservable = this.callbacks.fetchEntityKeys(this.entityAliasId, this.searchText, dataKeyTypes);
425 } else { 426 } else {
@@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import { ChangeDetectionStrategy, Component, forwardRef, Input, OnInit } from '@angular/core'; 17 +import { Component, forwardRef, Input, OnInit } from '@angular/core';
18 import { PageComponent } from '@shared/components/page.component'; 18 import { PageComponent } from '@shared/components/page.component';
19 import { Store } from '@ngrx/store'; 19 import { Store } from '@ngrx/store';
20 import { AppState } from '@core/core.state'; 20 import { AppState } from '@core/core.state';
@@ -22,12 +22,11 @@ import { @@ -22,12 +22,11 @@ import {
22 DataKey, 22 DataKey,
23 Datasource, 23 Datasource,
24 DatasourceType, 24 DatasourceType,
25 - datasourceTypeTranslationMap, defaultLegendConfig,  
26 - WidgetActionDescriptor, 25 + datasourceTypeTranslationMap,
  26 + defaultLegendConfig,
27 widgetType 27 widgetType
28 } from '@shared/models/widget.models'; 28 } from '@shared/models/widget.models';
29 import { 29 import {
30 - AbstractControl,  
31 ControlValueAccessor, 30 ControlValueAccessor,
32 FormArray, 31 FormArray,
33 FormBuilder, 32 FormBuilder,
@@ -58,7 +57,8 @@ import { MatDialog } from '@angular/material/dialog'; @@ -58,7 +57,8 @@ import { MatDialog } from '@angular/material/dialog';
58 import { EntityService } from '@core/http/entity.service'; 57 import { EntityService } from '@core/http/entity.service';
59 import { JsonFormComponentData } from '@shared/components/json-form/json-form-component.models'; 58 import { JsonFormComponentData } from '@shared/components/json-form/json-form-component.models';
60 import { WidgetActionsData } from './action/manage-widget-actions.component.models'; 59 import { WidgetActionsData } from './action/manage-widget-actions.component.models';
61 -import { Dashboard, DashboardState } from '@shared/models/dashboard.models'; 60 +import { DashboardState } from '@shared/models/dashboard.models';
  61 +import { entityFields } from '@shared/models/entity.models';
62 62
63 const emptySettingsSchema = { 63 const emptySettingsSchema = {
64 type: 'object', 64 type: 'object',
@@ -621,10 +621,10 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont @@ -621,10 +621,10 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
621 return chip; 621 return chip;
622 } else { 622 } else {
623 let label: string = chip; 623 let label: string = chip;
624 - if (type === DataKeyType.alarm) {  
625 - const alarmField = alarmFields[label];  
626 - if (alarmField) {  
627 - label = this.translate.instant(alarmField.name); 624 + if (type === DataKeyType.alarm || type === DataKeyType.entityField) {
  625 + const keyField = type === DataKeyType.alarm ? alarmFields[label] : entityFields[chip];;
  626 + if (keyField) {
  627 + label = this.translate.instant(keyField.name);
628 } 628 }
629 } 629 }
630 label = this.genNextLabel(label); 630 label = this.genNextLabel(label);
@@ -91,6 +91,7 @@ export interface RelationsQueryFilter { @@ -91,6 +91,7 @@ export interface RelationsQueryFilter {
91 direction?: EntitySearchDirection; 91 direction?: EntitySearchDirection;
92 filters?: Array<EntityTypeFilter>; 92 filters?: Array<EntityTypeFilter>;
93 maxLevel?: number; 93 maxLevel?: number;
  94 + fetchLastLevelOnly?: boolean;
94 } 95 }
95 96
96 export interface EntitySearchQueryFilter { 97 export interface EntitySearchQueryFilter {
@@ -100,6 +101,8 @@ export interface EntitySearchQueryFilter { @@ -100,6 +101,8 @@ export interface EntitySearchQueryFilter {
100 rootEntity?: EntityId; 101 rootEntity?: EntityId;
101 relationType?: string; 102 relationType?: string;
102 direction?: EntitySearchDirection; 103 direction?: EntitySearchDirection;
  104 + maxLevel?: number;
  105 + fetchLastLevelOnly?: boolean;
103 } 106 }
104 107
105 export interface AssetSearchQueryFilter extends EntitySearchQueryFilter { 108 export interface AssetSearchQueryFilter extends EntitySearchQueryFilter {
@@ -57,3 +57,18 @@ export interface DeviceCredentials extends BaseData<DeviceCredentialsId> { @@ -57,3 +57,18 @@ export interface DeviceCredentials extends BaseData<DeviceCredentialsId> {
57 export interface DeviceSearchQuery extends EntitySearchQuery { 57 export interface DeviceSearchQuery extends EntitySearchQuery {
58 deviceTypes: Array<string>; 58 deviceTypes: Array<string>;
59 } 59 }
  60 +
  61 +export interface ClaimRequest {
  62 + secretKey: string;
  63 +}
  64 +
  65 +export enum ClaimResponse {
  66 + SUCCESS = 'SUCCESS',
  67 + FAILURE = 'FAILURE',
  68 + CLAIMED = 'CLAIMED'
  69 +}
  70 +
  71 +export interface ClaimResult {
  72 + device: Device,
  73 + response: ClaimResponse
  74 +}
@@ -53,3 +53,89 @@ export interface ImportEntitiesResultInfo { @@ -53,3 +53,89 @@ export interface ImportEntitiesResultInfo {
53 entity: number; 53 entity: number;
54 }; 54 };
55 } 55 }
  56 +
  57 +export interface EntityField {
  58 + keyName: string;
  59 + value: string;
  60 + name: string;
  61 + time?: boolean;
  62 +}
  63 +
  64 +export const entityFields: {[fieldName: string]: EntityField} = {
  65 + createdTime: {
  66 + keyName: 'createdTime',
  67 + name: 'entity-field.created-time',
  68 + value: 'createdTime',
  69 + time: true
  70 + },
  71 + name: {
  72 + keyName: 'name',
  73 + name: 'entity-field.name',
  74 + value: 'name'
  75 + },
  76 + type: {
  77 + keyName: 'type',
  78 + name: 'entity-field.type',
  79 + value: 'type'
  80 + },
  81 + firstName: {
  82 + keyName: 'firstName',
  83 + name: 'entity-field.first-name',
  84 + value: 'firstName'
  85 + },
  86 + lastName: {
  87 + keyName: 'lastName',
  88 + name: 'entity-field.last-name',
  89 + value: 'lastName'
  90 + },
  91 + email: {
  92 + keyName: 'email',
  93 + name: 'entity-field.email',
  94 + value: 'email'
  95 + },
  96 + title: {
  97 + keyName: 'title',
  98 + name: 'entity-field.title',
  99 + value: 'title'
  100 + },
  101 + country: {
  102 + keyName: 'country',
  103 + name: 'entity-field.country',
  104 + value: 'country'
  105 + },
  106 + state: {
  107 + keyName: 'state',
  108 + name: 'entity-field.state',
  109 + value: 'state'
  110 + },
  111 + city: {
  112 + keyName: 'city',
  113 + name: 'entity-field.city',
  114 + value: 'city'
  115 + },
  116 + address: {
  117 + keyName: 'address',
  118 + name: 'entity-field.address',
  119 + value: 'address'
  120 + },
  121 + address2: {
  122 + keyName: 'address2',
  123 + name: 'entity-field.address2',
  124 + value: 'address2'
  125 + },
  126 + zip: {
  127 + keyName: 'zip',
  128 + name: 'entity-field.zip',
  129 + value: 'zip'
  130 + },
  131 + phone: {
  132 + keyName: 'phone',
  133 + name: 'entity-field.phone',
  134 + value: 'phone'
  135 + },
  136 + label: {
  137 + keyName: 'label',
  138 + name: 'entity-field.label',
  139 + value: 'label'
  140 + }
  141 +};
@@ -64,6 +64,7 @@ export interface RelationsSearchParameters { @@ -64,6 +64,7 @@ export interface RelationsSearchParameters {
64 direction: EntitySearchDirection; 64 direction: EntitySearchDirection;
65 relationTypeGroup?: RelationTypeGroup; 65 relationTypeGroup?: RelationTypeGroup;
66 maxLevel?: number; 66 maxLevel?: number;
  67 + fetchLastLevelOnly?: boolean;
67 } 68 }
68 69
69 export interface EntityRelationsQuery { 70 export interface EntityRelationsQuery {
@@ -26,7 +26,8 @@ export enum DataKeyType { @@ -26,7 +26,8 @@ export enum DataKeyType {
26 timeseries = 'timeseries', 26 timeseries = 'timeseries',
27 attribute = 'attribute', 27 attribute = 'attribute',
28 function = 'function', 28 function = 'function',
29 - alarm = 'alarm' 29 + alarm = 'alarm',
  30 + entityField = 'entityField'
30 } 31 }
31 32
32 export enum LatestTelemetry { 33 export enum LatestTelemetry {