Commit 44a938d8f6a70bbbe5bf417c0906046c4971013c

Authored by Igor Kulikov
1 parent 416432e3

Propagate UI changes

... ... @@ -57,6 +57,14 @@ export class AppComponent implements OnInit {
57 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 69 this.storageService.testLocalStorage();
62 70
... ...
... ... @@ -50,6 +50,8 @@ import { AlarmSourceListener } from '@core/http/alarm.service';
50 50 import { DatasourceListener } from '@core/api/datasource.service';
51 51 import * as deepEqual from 'deep-equal';
52 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 56 export class WidgetSubscription implements IWidgetSubscription {
55 57
... ... @@ -342,6 +344,12 @@ export class WidgetSubscription implements IWidgetSubscription {
342 344 dataKey,
343 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 353 this.data.push(datasourceData);
346 354 this.hiddenData.push({data: []});
347 355 if (this.displayLegend) {
... ... @@ -682,8 +690,15 @@ export class WidgetSubscription implements IWidgetSubscription {
682 690 },
683 691 datasourceIndex: index
684 692 };
  693 +
  694 + let entityFieldKey = false;
  695 +
685 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 703 index += datasource.dataKeys.length;
689 704 this.datasourceListeners.push(listener);
... ... @@ -691,7 +706,7 @@ export class WidgetSubscription implements IWidgetSubscription {
691 706 if (datasource.dataKeys.length) {
692 707 this.ctx.datasourceService.subscribeToDatasource(listener);
693 708 }
694   - if (datasource.unresolvedStateEntity ||
  709 + if (datasource.unresolvedStateEntity || entityFieldKey ||
695 710 !datasource.dataKeys.length ||
696 711 (datasource.type === DatasourceType.entity && !datasource.entityId)
697 712 ) {
... ...
... ... @@ -45,11 +45,11 @@ export class AttributeService {
45 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 49 config?: RequestConfig): Observable<any> {
50 50 const keys = timeseries.map(attribute => attribute.key).join(',');
51 51 return this.http.delete(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/delete` +
52   - `?keys=${keys}`,
  52 + `?keys=${keys}&deleteAllDataForKeys=${deleteAllDataForKeys}`,
53 53 defaultHttpOptionsFromConfig(config));
54 54 }
55 55
... ... @@ -93,7 +93,7 @@ export class AttributeService {
93 93 });
94 94 let deleteEntityTimeseriesObservable: Observable<any>;
95 95 if (deleteTimeseries.length) {
96   - deleteEntityTimeseriesObservable = this.deleteEntityTimeseries(entityId, deleteTimeseries, config);
  96 + deleteEntityTimeseriesObservable = this.deleteEntityTimeseries(entityId, deleteTimeseries, true, config);
97 97 } else {
98 98 deleteEntityTimeseriesObservable = of(null);
99 99 }
... ...
... ... @@ -20,7 +20,13 @@ import { Observable, ReplaySubject } from 'rxjs';
20 20 import { HttpClient } from '@angular/common/http';
21 21 import { PageLink } from '@shared/models/page/page-link';
22 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 30 import { EntitySubtype } from '@app/shared/models/entity-type.models';
25 31 import { AuthService } from '@core/auth/auth.service';
26 32
... ... @@ -127,4 +133,13 @@ export class DeviceService {
127 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 44 import { Datasource, DatasourceType, KeyInfo } from '@app/shared/models/widget.models';
45 45 import { UtilsService } from '@core/services/utils.service';
46 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 48 import {
49 49 EntityRelationInfo,
50 50 EntityRelationsQuery,
... ... @@ -503,8 +503,50 @@ export class EntityService {
503 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 545 public getEntityKeys(entityId: EntityId, query: string, type: DataKeyType,
507 546 config?: RequestConfig): Observable<Array<string>> {
  547 + if (type === DataKeyType.entityField) {
  548 + return of(this.getEntityFieldKeys(entityId.entityType as EntityType, query));
  549 + }
508 550 let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/keys/`;
509 551 if (type === DataKeyType.timeseries) {
510 552 url += 'timeseries';
... ... @@ -588,7 +630,8 @@ export class EntityService {
588 630 if (filter.stateEntityParamName && filter.stateEntityParamName.length) {
589 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 635 switch (filter.type) {
593 636 case AliasFilterType.singleEntity:
594 637 const aliasEntityId = this.resolveAliasEntityId(filter.singleEntity.entityType, filter.singleEntity.id);
... ... @@ -697,7 +740,8 @@ export class EntityService {
697 740 parameters: {
698 741 rootId: relationQueryRootEntityId.id,
699 742 rootType: relationQueryRootEntityId.entityType as EntityType,
700   - direction: filter.direction
  743 + direction: filter.direction,
  744 + fetchLastLevelOnly: filter.fetchLastLevelOnly
701 745 },
702 746 filters: filter.filters
703 747 };
... ... @@ -741,10 +785,12 @@ export class EntityService {
741 785 parameters: {
742 786 rootId: searchQueryRootEntityId.id,
743 787 rootType: searchQueryRootEntityId.entityType as EntityType,
744   - direction: filter.direction
  788 + direction: filter.direction,
  789 + fetchLastLevelOnly: filter.fetchLastLevelOnly
745 790 },
746 791 relationType: filter.relationType
747 792 };
  793 + searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1;
748 794 let findByQueryObservable: Observable<Array<BaseData<EntityId>>>;
749 795 if (filter.type === AliasFilterType.assetSearchQuery) {
750 796 const assetSearchQuery = searchQuery as AssetSearchQuery;
... ... @@ -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 1039 if (stateParams) {
994 1040 if (filter.stateEntityParamName && filter.stateEntityParamName.length) {
995 1041 if (stateParams[filter.stateEntityParamName]) {
... ... @@ -1005,7 +1051,7 @@ export class EntityService {
1005 1051 if (entityId) {
1006 1052 entityId = this.resolveAliasEntityId(entityId.entityType, entityId.id);
1007 1053 }
1008   - return entityId;
  1054 + return {entityId};
1009 1055 }
1010 1056
1011 1057 private resolveAliasEntityId(entityType: EntityType | AliasEntityType, id: string): EntityId {
... ...
... ... @@ -133,6 +133,14 @@
133 133 </tb-entity-select>
134 134 </div>
135 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 144 <div fxFlex fxLayoutGap="8px" fxLayout="row">
137 145 <mat-form-field class="mat-block" style="min-width: 100px;">
138 146 <mat-label translate>relation.direction</mat-label>
... ... @@ -191,6 +199,14 @@
191 199 </tb-entity-select>
192 200 </div>
193 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 210 <div fxFlex fxLayoutGap="8px" fxLayout="row">
195 211 <mat-form-field class="mat-block" style="min-width: 100px;">
196 212 <mat-label translate>relation.direction</mat-label>
... ...
... ... @@ -25,6 +25,7 @@
25 25
26 26 .tb-root-state-entity-switch {
27 27 padding-left: 10px;
  28 + padding-bottom: 10px;
28 29
29 30 .root-state-entity-switch {
30 31 margin: 0;
... ...
... ... @@ -165,6 +165,7 @@ export class EntityFilterComponent implements ControlValueAccessor, OnInit {
165 165 rootEntity: [filter ? filter.rootEntity : null, (filter && filter.rootStateEntity) ? [] : [Validators.required]],
166 166 direction: [filter ? filter.direction : EntitySearchDirection.FROM, [Validators.required]],
167 167 maxLevel: [filter ? filter.maxLevel : 1, []],
  168 + fetchLastLevelOnly: [filter ? filter.fetchLastLevelOnly : false, []]
168 169 });
169 170 this.filterFormGroup.get('rootStateEntity').valueChanges.subscribe((rootStateEntity: boolean) => {
170 171 this.filterFormGroup.get('rootEntity').setValidators(rootStateEntity ? [] : [Validators.required]);
... ...
... ... @@ -38,6 +38,12 @@
38 38 <mat-icon class="tb-mat-20"
39 39 svgIcon="alpha-a-circle-outline"></mat-icon>
40 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 47 <span *ngIf="key.type === dataKeyTypes.timeseries"
42 48 matTooltip="{{'datakey.timeseries' | translate }}"
43 49 matTooltipPosition="above">
... ... @@ -87,6 +93,12 @@
87 93 <mat-icon class="tb-mat-16"
88 94 svgIcon="alpha-a-circle-outline"></mat-icon>
89 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 102 <span *ngIf="key.type === dataKeyTypes.timeseries"
91 103 matTooltip="{{'datakey.timeseries' | translate }}"
92 104 matTooltipPosition="above">
... ... @@ -114,15 +126,22 @@
114 126 <span *ngIf="widgetType == widgetTypes.latest"
115 127 matTooltip="{{'datakey.attributes' | translate }}"
116 128 matTooltipPosition="above">
117   - <mat-icon (click)="createKey(searchText, dataKeyTypes.attribute)"
  129 + <mat-icon (click)="createKey(searchText, dataKeyTypes.attribute)"
118 130 class="tb-mat-16"
119 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 140 <span matTooltip="{{'datakey.timeseries' | translate }}"
122 141 matTooltipPosition="above">
123   - <mat-icon (click)="createKey(searchText, dataKeyTypes.timeseries)"
  142 + <mat-icon (click)="createKey(searchText, dataKeyTypes.timeseries)"
124 143 class="tb-mat-16">timeline</mat-icon>
125   - </span>
  144 + </span>
126 145 </ng-template>
127 146 </ng-template>
128 147 </div>
... ...
... ... @@ -420,6 +420,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie
420 420 const dataKeyTypes = [DataKeyType.timeseries];
421 421 if (this.widgetType === widgetType.latest) {
422 422 dataKeyTypes.push(DataKeyType.attribute);
  423 + dataKeyTypes.push(DataKeyType.entityField);
423 424 }
424 425 fetchObservable = this.callbacks.fetchEntityKeys(this.entityAliasId, this.searchText, dataKeyTypes);
425 426 } else {
... ...
... ... @@ -14,7 +14,7 @@
14 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 18 import { PageComponent } from '@shared/components/page.component';
19 19 import { Store } from '@ngrx/store';
20 20 import { AppState } from '@core/core.state';
... ... @@ -22,12 +22,11 @@ import {
22 22 DataKey,
23 23 Datasource,
24 24 DatasourceType,
25   - datasourceTypeTranslationMap, defaultLegendConfig,
26   - WidgetActionDescriptor,
  25 + datasourceTypeTranslationMap,
  26 + defaultLegendConfig,
27 27 widgetType
28 28 } from '@shared/models/widget.models';
29 29 import {
30   - AbstractControl,
31 30 ControlValueAccessor,
32 31 FormArray,
33 32 FormBuilder,
... ... @@ -58,7 +57,8 @@ import { MatDialog } from '@angular/material/dialog';
58 57 import { EntityService } from '@core/http/entity.service';
59 58 import { JsonFormComponentData } from '@shared/components/json-form/json-form-component.models';
60 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 63 const emptySettingsSchema = {
64 64 type: 'object',
... ... @@ -621,10 +621,10 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
621 621 return chip;
622 622 } else {
623 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 630 label = this.genNextLabel(label);
... ...
... ... @@ -91,6 +91,7 @@ export interface RelationsQueryFilter {
91 91 direction?: EntitySearchDirection;
92 92 filters?: Array<EntityTypeFilter>;
93 93 maxLevel?: number;
  94 + fetchLastLevelOnly?: boolean;
94 95 }
95 96
96 97 export interface EntitySearchQueryFilter {
... ... @@ -100,6 +101,8 @@ export interface EntitySearchQueryFilter {
100 101 rootEntity?: EntityId;
101 102 relationType?: string;
102 103 direction?: EntitySearchDirection;
  104 + maxLevel?: number;
  105 + fetchLastLevelOnly?: boolean;
103 106 }
104 107
105 108 export interface AssetSearchQueryFilter extends EntitySearchQueryFilter {
... ...
... ... @@ -57,3 +57,18 @@ export interface DeviceCredentials extends BaseData<DeviceCredentialsId> {
57 57 export interface DeviceSearchQuery extends EntitySearchQuery {
58 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 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 64 direction: EntitySearchDirection;
65 65 relationTypeGroup?: RelationTypeGroup;
66 66 maxLevel?: number;
  67 + fetchLastLevelOnly?: boolean;
67 68 }
68 69
69 70 export interface EntityRelationsQuery {
... ...
... ... @@ -26,7 +26,8 @@ export enum DataKeyType {
26 26 timeseries = 'timeseries',
27 27 attribute = 'attribute',
28 28 function = 'function',
29   - alarm = 'alarm'
  29 + alarm = 'alarm',
  30 + entityField = 'entityField'
30 31 }
31 32
32 33 export enum LatestTelemetry {
... ...