2020-07-02 20:05:34 +03:00

587 lines
18 KiB
TypeScript

///
/// Copyright © 2016-2020 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { AliasFilterType, EntityFilters } from '@shared/models/alias.models';
import { EntityId } from '@shared/models/id/entity-id';
import { SortDirection } from '@angular/material/sort';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { EntityInfo } from '@shared/models/entity.models';
import { EntityType } from '@shared/models/entity-type.models';
import { Datasource, DatasourceType } from '@shared/models/widget.models';
import { PageData } from '@shared/models/page/page-data';
import { isDefined, isEqual } from '@core/utils';
import { TranslateService } from '@ngx-translate/core';
export enum EntityKeyType {
ATTRIBUTE = 'ATTRIBUTE',
CLIENT_ATTRIBUTE = 'CLIENT_ATTRIBUTE',
SHARED_ATTRIBUTE = 'SHARED_ATTRIBUTE',
SERVER_ATTRIBUTE = 'SERVER_ATTRIBUTE',
TIME_SERIES = 'TIME_SERIES',
ENTITY_FIELD = 'ENTITY_FIELD'
}
export const entityKeyTypeTranslationMap = new Map<EntityKeyType, string>(
[
[EntityKeyType.ATTRIBUTE, 'filter.key-type.attribute'],
[EntityKeyType.TIME_SERIES, 'filter.key-type.timeseries'],
[EntityKeyType.ENTITY_FIELD, 'filter.key-type.entity-field']
]
);
export function entityKeyTypeToDataKeyType(entityKeyType: EntityKeyType): DataKeyType {
switch (entityKeyType) {
case EntityKeyType.ATTRIBUTE:
case EntityKeyType.CLIENT_ATTRIBUTE:
case EntityKeyType.SHARED_ATTRIBUTE:
case EntityKeyType.SERVER_ATTRIBUTE:
return DataKeyType.attribute
case EntityKeyType.TIME_SERIES:
return DataKeyType.timeseries;
case EntityKeyType.ENTITY_FIELD:
return DataKeyType.entityField;
}
}
export interface EntityKey {
type: EntityKeyType;
key: string;
}
export enum EntityKeyValueType {
STRING = 'STRING',
NUMERIC = 'NUMERIC',
BOOLEAN = 'BOOLEAN',
DATE_TIME = 'DATE_TIME'
}
export interface EntityKeyValueTypeData {
name: string;
icon: string;
}
export const entityKeyValueTypesMap = new Map<EntityKeyValueType, EntityKeyValueTypeData>(
[
[
EntityKeyValueType.STRING,
{
name: 'filter.value-type.string',
icon: 'mdi:format-text'
}
],
[
EntityKeyValueType.NUMERIC,
{
name: 'filter.value-type.numeric',
icon: 'mdi:numeric'
}
],
[
EntityKeyValueType.BOOLEAN,
{
name: 'filter.value-type.boolean',
icon: 'mdi:checkbox-marked-outline'
}
],
[
EntityKeyValueType.DATE_TIME,
{
name: 'filter.value-type.date-time',
icon: 'mdi:calendar-clock'
}
]
]
);
export function entityKeyValueTypeToFilterPredicateType(valueType: EntityKeyValueType): FilterPredicateType {
switch (valueType) {
case EntityKeyValueType.STRING:
return FilterPredicateType.STRING;
case EntityKeyValueType.NUMERIC:
case EntityKeyValueType.DATE_TIME:
return FilterPredicateType.NUMERIC;
case EntityKeyValueType.BOOLEAN:
return FilterPredicateType.BOOLEAN;
}
}
export function createDefaultFilterPredicateInfo(valueType: EntityKeyValueType, complex: boolean): KeyFilterPredicateInfo {
const predicate = createDefaultFilterPredicate(valueType, complex);
return {
keyFilterPredicate: predicate,
userInfo: {
editable: true,
label: '',
autogeneratedLabel: true,
order: 0
}
};
}
export function createDefaultFilterPredicate(valueType: EntityKeyValueType, complex: boolean): KeyFilterPredicate {
const predicate = {
type: complex ? FilterPredicateType.COMPLEX : entityKeyValueTypeToFilterPredicateType(valueType)
} as KeyFilterPredicate;
switch (predicate.type) {
case FilterPredicateType.STRING:
predicate.operation = StringOperation.STARTS_WITH;
predicate.value = {
defaultValue: ''
};
predicate.ignoreCase = false;
break;
case FilterPredicateType.NUMERIC:
predicate.operation = NumericOperation.EQUAL;
predicate.value = {
defaultValue: valueType === EntityKeyValueType.DATE_TIME ? Date.now() : 0
};
break;
case FilterPredicateType.BOOLEAN:
predicate.operation = BooleanOperation.EQUAL;
predicate.value = {
defaultValue: false
};
break;
case FilterPredicateType.COMPLEX:
predicate.operation = ComplexOperation.AND;
predicate.predicates = [];
break;
}
return predicate;
}
export enum FilterPredicateType {
STRING = 'STRING',
NUMERIC = 'NUMERIC',
BOOLEAN = 'BOOLEAN',
COMPLEX = 'COMPLEX'
}
export enum StringOperation {
EQUAL = 'EQUAL',
NOT_EQUAL = 'NOT_EQUAL',
STARTS_WITH = 'STARTS_WITH',
ENDS_WITH = 'ENDS_WITH',
CONTAINS = 'CONTAINS',
NOT_CONTAIN = 'NOT_CONTAIN'
}
export const stringOperationTranslationMap = new Map<StringOperation, string>(
[
[StringOperation.EQUAL, 'filter.operation.equal'],
[StringOperation.NOT_EQUAL, 'filter.operation.not-equal'],
[StringOperation.STARTS_WITH, 'filter.operation.starts-with'],
[StringOperation.ENDS_WITH, 'filter.operation.ends-with'],
[StringOperation.CONTAINS, 'filter.operation.contains'],
[StringOperation.NOT_CONTAIN, 'filter.operation.not-contain']
]
);
export enum NumericOperation {
EQUAL = 'EQUAL',
NOT_EQUAL = 'NOT_EQUAL',
GREATER = 'GREATER',
LESS = 'LESS',
GREATER_OR_EQUAL = 'GREATER_OR_EQUAL',
LESS_OR_EQUAL = 'LESS_OR_EQUAL'
}
export const numericOperationTranslationMap = new Map<NumericOperation, string>(
[
[NumericOperation.EQUAL, 'filter.operation.equal'],
[NumericOperation.NOT_EQUAL, 'filter.operation.not-equal'],
[NumericOperation.GREATER, 'filter.operation.greater'],
[NumericOperation.LESS, 'filter.operation.less'],
[NumericOperation.GREATER_OR_EQUAL, 'filter.operation.greater-or-equal'],
[NumericOperation.LESS_OR_EQUAL, 'filter.operation.less-or-equal']
]
);
export enum BooleanOperation {
EQUAL = 'EQUAL',
NOT_EQUAL = 'NOT_EQUAL'
}
export const booleanOperationTranslationMap = new Map<BooleanOperation, string>(
[
[BooleanOperation.EQUAL, 'filter.operation.equal'],
[BooleanOperation.NOT_EQUAL, 'filter.operation.not-equal']
]
);
export enum ComplexOperation {
AND = 'AND',
OR = 'OR'
}
export const complexOperationTranslationMap = new Map<ComplexOperation, string>(
[
[ComplexOperation.AND, 'filter.operation.and'],
[ComplexOperation.OR, 'filter.operation.or']
]
);
export enum DynamicValueSourceType {
CURRENT_TENANT = 'CURRENT_TENANT',
CURRENT_CUSTOMER = 'CURRENT_CUSTOMER',
CURRENT_USER = 'CURRENT_USER'
}
export const dynamicValueSourceTypeTranslationMap = new Map<DynamicValueSourceType, string>(
[
[DynamicValueSourceType.CURRENT_TENANT, 'filter.current-tenant'],
[DynamicValueSourceType.CURRENT_CUSTOMER, 'filter.current-customer'],
[DynamicValueSourceType.CURRENT_USER, 'filter.current-user']
]
);
export interface DynamicValue<T> {
sourceType: DynamicValueSourceType;
sourceAttribute: string;
}
export interface FilterPredicateValue<T> {
defaultValue: T;
dynamicValue?: DynamicValue<T>;
}
export interface StringFilterPredicate {
type: FilterPredicateType.STRING,
operation: StringOperation;
value: FilterPredicateValue<string>;
ignoreCase: boolean;
}
export interface NumericFilterPredicate {
type: FilterPredicateType.NUMERIC,
operation: NumericOperation;
value: FilterPredicateValue<number>;
}
export interface BooleanFilterPredicate {
type: FilterPredicateType.BOOLEAN,
operation: BooleanOperation;
value: FilterPredicateValue<boolean>;
}
export interface BaseComplexFilterPredicate<T extends KeyFilterPredicate | KeyFilterPredicateInfo> {
type: FilterPredicateType.COMPLEX,
operation: ComplexOperation;
predicates: Array<T>;
}
export type ComplexFilterPredicate = BaseComplexFilterPredicate<KeyFilterPredicate>;
export type ComplexFilterPredicateInfo = BaseComplexFilterPredicate<KeyFilterPredicateInfo>;
export type KeyFilterPredicate = StringFilterPredicate |
NumericFilterPredicate |
BooleanFilterPredicate |
ComplexFilterPredicate |
ComplexFilterPredicateInfo;
export interface KeyFilterPredicateUserInfo {
editable: boolean;
label: string;
autogeneratedLabel: boolean;
order?: number;
}
export interface KeyFilterPredicateInfo {
keyFilterPredicate: KeyFilterPredicate;
userInfo: KeyFilterPredicateUserInfo;
}
export interface KeyFilter {
key: EntityKey;
predicate: KeyFilterPredicate;
}
export interface KeyFilterInfo {
key: EntityKey;
valueType: EntityKeyValueType;
predicates: Array<KeyFilterPredicateInfo>;
}
export interface FilterInfo {
filter: string;
editable: boolean;
keyFilters: Array<KeyFilterInfo>;
}
export interface FiltersInfo {
datasourceFilters: {[datasourceIndex: number]: FilterInfo};
}
export function filterInfoToKeyFilters(filter: FilterInfo): Array<KeyFilter> {
const keyFilterInfos = filter.keyFilters;
const keyFilters: Array<KeyFilter> = [];
for (const keyFilterInfo of keyFilterInfos) {
const key = keyFilterInfo.key;
for (const predicate of keyFilterInfo.predicates) {
const keyFilter: KeyFilter = {
key,
predicate: keyFilterPredicateInfoToKeyFilterPredicate(predicate)
};
keyFilters.push(keyFilter);
}
}
return keyFilters;
}
export function keyFilterPredicateInfoToKeyFilterPredicate(keyFilterPredicateInfo: KeyFilterPredicateInfo): KeyFilterPredicate {
let keyFilterPredicate = keyFilterPredicateInfo.keyFilterPredicate;
if (keyFilterPredicate.type === FilterPredicateType.COMPLEX) {
const complexInfo = keyFilterPredicate as ComplexFilterPredicateInfo;
const predicates = complexInfo.predicates.map((predicateInfo => keyFilterPredicateInfoToKeyFilterPredicate(predicateInfo)));
keyFilterPredicate = {
type: FilterPredicateType.COMPLEX,
operation: complexInfo.operation,
predicates
} as ComplexFilterPredicate;
}
return keyFilterPredicate;
}
export function isFilterEditable(filter: FilterInfo): boolean {
if (filter.editable) {
return filter.keyFilters.some(value => isKeyFilterInfoEditable(value));
} else {
return false;
}
}
export function isKeyFilterInfoEditable(keyFilterInfo: KeyFilterInfo): boolean {
return keyFilterInfo.predicates.some(value => isPredicateInfoEditable(value));
}
export function isPredicateInfoEditable(predicateInfo: KeyFilterPredicateInfo): boolean {
if (predicateInfo.keyFilterPredicate.type === FilterPredicateType.COMPLEX) {
const complexFilterPredicateInfo: ComplexFilterPredicateInfo = predicateInfo.keyFilterPredicate as ComplexFilterPredicateInfo;
return complexFilterPredicateInfo.predicates.some(value => isPredicateInfoEditable(value));
} else {
return predicateInfo.userInfo.editable;
}
}
export interface UserFilterInputInfo {
label: string;
valueType: EntityKeyValueType;
info: KeyFilterPredicateInfo;
}
export function filterToUserFilterInfoList(filter: Filter, translate: TranslateService): Array<UserFilterInputInfo> {
const result = filter.keyFilters.map((keyFilterInfo => keyFilterInfoToUserFilterInfoList(keyFilterInfo, translate)));
let userInputs: Array<UserFilterInputInfo> = [].concat.apply([], result);
userInputs = userInputs.sort((input1, input2) => {
const order1 = isDefined(input1.info.userInfo.order) ? input1.info.userInfo.order : 0;
const order2 = isDefined(input2.info.userInfo.order) ? input2.info.userInfo.order : 0;
return order1 - order2;
});
return userInputs;
}
export function keyFilterInfoToUserFilterInfoList(keyFilterInfo: KeyFilterInfo, translate: TranslateService): Array<UserFilterInputInfo> {
const result = keyFilterInfo.predicates.map((predicateInfo => predicateInfoToUserFilterInfoList(keyFilterInfo.key,
keyFilterInfo.valueType, predicateInfo, translate)));
return [].concat.apply([], result);
}
export function predicateInfoToUserFilterInfoList(key: EntityKey,
valueType: EntityKeyValueType,
predicateInfo: KeyFilterPredicateInfo,
translate: TranslateService): Array<UserFilterInputInfo> {
if (predicateInfo.keyFilterPredicate.type === FilterPredicateType.COMPLEX) {
const complexFilterPredicateInfo: ComplexFilterPredicateInfo = predicateInfo.keyFilterPredicate as ComplexFilterPredicateInfo;
const result = complexFilterPredicateInfo.predicates.map((predicateInfo1 =>
predicateInfoToUserFilterInfoList(key, valueType, predicateInfo1, translate)));
return [].concat.apply([], result);
} else {
if (predicateInfo.userInfo.editable) {
const userInput: UserFilterInputInfo = {
info: predicateInfo,
label: predicateInfo.userInfo.label,
valueType
};
if (predicateInfo.userInfo.autogeneratedLabel) {
userInput.label = generateUserFilterValueLabel(key.key, valueType,
predicateInfo.keyFilterPredicate.operation, translate);
}
return [userInput];
} else {
return [];
}
}
}
export function generateUserFilterValueLabel(key: string, valueType: EntityKeyValueType,
operation: StringOperation | BooleanOperation | NumericOperation,
translate: TranslateService) {
let label = key;
let operationTranslationKey: string;
switch (valueType) {
case EntityKeyValueType.STRING:
operationTranslationKey = stringOperationTranslationMap.get(operation as StringOperation);
break;
case EntityKeyValueType.NUMERIC:
case EntityKeyValueType.DATE_TIME:
operationTranslationKey = numericOperationTranslationMap.get(operation as NumericOperation);
break;
case EntityKeyValueType.BOOLEAN:
operationTranslationKey = booleanOperationTranslationMap.get(operation as BooleanOperation);
break;
}
label += ' ' + translate.instant(operationTranslationKey);
return label;
}
export interface Filter extends FilterInfo {
id: string;
}
export interface Filters {
[id: string]: Filter
}
export interface EntityFilter extends EntityFilters {
type?: AliasFilterType;
}
export enum Direction {
ASC = 'ASC',
DESC = 'DESC'
}
export interface EntityDataSortOrder {
key: EntityKey;
direction: Direction;
}
export interface EntityDataPageLink {
pageSize: number;
page: number;
textSearch?: string;
sortOrder?: EntityDataSortOrder;
dynamic?: boolean;
}
export function entityDataPageLinkSortDirection(pageLink: EntityDataPageLink): SortDirection {
if (pageLink.sortOrder) {
return (pageLink.sortOrder.direction + '').toLowerCase() as SortDirection;
} else {
return '' as SortDirection;
}
}
export function createDefaultEntityDataPageLink(pageSize: number): EntityDataPageLink {
return {
pageSize,
page: 0,
sortOrder: {
key: {
type: EntityKeyType.ENTITY_FIELD,
key: 'createdTime'
},
direction: Direction.DESC
}
}
}
export const singleEntityDataPageLink: EntityDataPageLink = createDefaultEntityDataPageLink(1);
export const defaultEntityDataPageLink: EntityDataPageLink = createDefaultEntityDataPageLink(1024);
export interface EntityCountQuery {
entityFilter: EntityFilter;
}
export interface EntityDataQuery extends EntityCountQuery {
pageLink: EntityDataPageLink;
entityFields?: Array<EntityKey>;
latestValues?: Array<EntityKey>;
keyFilters?: Array<KeyFilter>;
}
export interface TsValue {
ts: number;
value: string;
}
export interface EntityData {
entityId: EntityId;
latest: {[entityKeyType: string]: {[key: string]: TsValue}};
timeseries: {[key: string]: Array<TsValue>};
}
export function entityPageDataChanged(prevPageData: PageData<EntityData>, nextPageData: PageData<EntityData>): boolean {
const prevIds = prevPageData.data.map((entityData) => entityData.entityId.id);
const nextIds = nextPageData.data.map((entityData) => entityData.entityId.id);
return !isEqual(prevIds, nextIds);
}
export const entityInfoFields: EntityKey[] = [
{
type: EntityKeyType.ENTITY_FIELD,
key: 'name'
},
{
type: EntityKeyType.ENTITY_FIELD,
key: 'label'
}
];
export function entityDataToEntityInfo(entityData: EntityData): EntityInfo {
const entityInfo: EntityInfo = {
id: entityData.entityId.id,
entityType: entityData.entityId.entityType as EntityType
};
if (entityData.latest && entityData.latest[EntityKeyType.ENTITY_FIELD]) {
const fields = entityData.latest[EntityKeyType.ENTITY_FIELD];
if (fields.name) {
entityInfo.name = fields.name.value;
} else {
entityInfo.name = '';
}
if (fields.label) {
entityInfo.label = fields.label.value;
} else {
entityInfo.label = '';
}
entityInfo.entityDescription = 'TODO: Not implemented';
}
return entityInfo;
}
export function updateDatasourceFromEntityInfo(datasource: Datasource, entity: EntityInfo, createFilter = false) {
datasource.entity = {};
datasource.entityId = entity.id;
datasource.entityType = entity.entityType;
if (datasource.type === DatasourceType.entity) {
datasource.entityName = entity.name;
datasource.entityLabel = entity.label;
datasource.name = entity.name;
datasource.entityDescription = entity.entityDescription;
if (createFilter) {
datasource.entityFilter = {
type: AliasFilterType.singleEntity,
singleEntity: {
id: entity.id,
entityType: entity.entityType
}
};
}
}
}