Filter predicate dynamic value

This commit is contained in:
Igor Kulikov 2020-07-02 20:05:34 +03:00
parent faee8e6bf0
commit cf6824cf22
25 changed files with 395 additions and 69 deletions

View File

@ -44,6 +44,7 @@ import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityListFilter; import org.thingsboard.server.common.data.query.EntityListFilter;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
@ -259,7 +260,7 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe
KeyFilter highTemperatureFilter = new KeyFilter(); KeyFilter highTemperatureFilter = new KeyFilter();
highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature")); highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
NumericFilterPredicate predicate = new NumericFilterPredicate(); NumericFilterPredicate predicate = new NumericFilterPredicate();
predicate.setValue(45); predicate.setValue(FilterPredicateValue.fromDouble(45));
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperatureFilter.setPredicate(predicate); highTemperatureFilter.setPredicate(predicate);
List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter); List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter);

View File

@ -26,9 +26,9 @@ import java.util.Arrays;
@RunWith(ClasspathSuite.class) @RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({ @ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.controller.sql.WebsocketApiSqlTest", // "org.thingsboard.server.controller.sql.WebsocketApiSqlTest",
// "org.thingsboard.server.controller.sql.EntityQueryControllerSqlTest", // "org.thingsboard.server.controller.sql.EntityQueryControllerSqlTest",
// "org.thingsboard.server.controller.sql.*Test", "org.thingsboard.server.controller.sql.*Test",
}) })
public class ControllerSqlTestSuite { public class ControllerSqlTestSuite {

View File

@ -21,7 +21,7 @@ import lombok.Data;
public class BooleanFilterPredicate implements KeyFilterPredicate { public class BooleanFilterPredicate implements KeyFilterPredicate {
private BooleanOperation operation; private BooleanOperation operation;
private boolean value; private FilterPredicateValue<Boolean> value;
@Override @Override
public FilterPredicateType getType() { public FilterPredicateType getType() {

View File

@ -0,0 +1,33 @@
/**
* 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.
*/
package org.thingsboard.server.common.data.query;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.Getter;
@Data
public class DynamicValue<T> {
@JsonIgnore
private T resolvedValue;
@Getter
private final DynamicValueSourceType sourceType;
@Getter
private final String sourceAttribute;
}

View File

@ -0,0 +1,22 @@
/**
* 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.
*/
package org.thingsboard.server.common.data.query;
public enum DynamicValueSourceType {
CURRENT_TENANT,
CURRENT_CUSTOMER,
CURRENT_USER
}

View File

@ -0,0 +1,63 @@
/**
* 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.
*/
package org.thingsboard.server.common.data.query;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.Getter;
@Data
public class FilterPredicateValue<T> {
@Getter
private final T defaultValue;
@Getter
private final DynamicValue<T> dynamicValue;
public FilterPredicateValue(T defaultValue) {
this(defaultValue, null);
}
@JsonCreator
public FilterPredicateValue(@JsonProperty("defaultValue") T defaultValue,
@JsonProperty("dynamicValue") DynamicValue<T> dynamicValue) {
this.defaultValue = defaultValue;
this.dynamicValue = dynamicValue;
}
@JsonIgnore
public T getValue() {
if (this.dynamicValue != null && this.dynamicValue.getResolvedValue() != null) {
return this.dynamicValue.getResolvedValue();
} else {
return defaultValue;
}
}
public static FilterPredicateValue<Double> fromDouble(double value) {
return new FilterPredicateValue<>(value);
}
public static FilterPredicateValue<String> fromString(String value) {
return new FilterPredicateValue<>(value);
}
public static FilterPredicateValue<Boolean> fromBoolean(boolean value) {
return new FilterPredicateValue<>(value);
}
}

View File

@ -21,7 +21,7 @@ import lombok.Data;
public class NumericFilterPredicate implements KeyFilterPredicate { public class NumericFilterPredicate implements KeyFilterPredicate {
private NumericOperation operation; private NumericOperation operation;
private double value; private FilterPredicateValue<Double> value;
@Override @Override
public FilterPredicateType getType() { public FilterPredicateType getType() {

View File

@ -21,7 +21,7 @@ import lombok.Data;
public class StringFilterPredicate implements KeyFilterPredicate { public class StringFilterPredicate implements KeyFilterPredicate {
private StringOperation operation; private StringOperation operation;
private String value; private FilterPredicateValue<String> value;
private boolean ignoreCase; private boolean ignoreCase;
@Override @Override

View File

@ -440,7 +440,7 @@ public class EntityKeyMapping {
private String buildStringPredicateQuery(QueryContext ctx, String field, StringFilterPredicate stringFilterPredicate) { private String buildStringPredicateQuery(QueryContext ctx, String field, StringFilterPredicate stringFilterPredicate) {
String operationField = field; String operationField = field;
String paramName = getNextParameterName(field); String paramName = getNextParameterName(field);
String value = stringFilterPredicate.getValue(); String value = stringFilterPredicate.getValue().getValue();
String stringOperationQuery = ""; String stringOperationQuery = "";
if (stringFilterPredicate.isIgnoreCase()) { if (stringFilterPredicate.isIgnoreCase()) {
value = value.toLowerCase(); value = value.toLowerCase();
@ -476,7 +476,7 @@ public class EntityKeyMapping {
private String buildNumericPredicateQuery(QueryContext ctx, String field, NumericFilterPredicate numericFilterPredicate) { private String buildNumericPredicateQuery(QueryContext ctx, String field, NumericFilterPredicate numericFilterPredicate) {
String paramName = getNextParameterName(field); String paramName = getNextParameterName(field);
ctx.addDoubleParameter(paramName, numericFilterPredicate.getValue()); ctx.addDoubleParameter(paramName, numericFilterPredicate.getValue().getValue());
String numericOperationQuery = ""; String numericOperationQuery = "";
switch (numericFilterPredicate.getOperation()) { switch (numericFilterPredicate.getOperation()) {
case EQUAL: case EQUAL:
@ -504,7 +504,7 @@ public class EntityKeyMapping {
private String buildBooleanPredicateQuery(QueryContext ctx, String field, private String buildBooleanPredicateQuery(QueryContext ctx, String field,
BooleanFilterPredicate booleanFilterPredicate) { BooleanFilterPredicate booleanFilterPredicate) {
String paramName = getNextParameterName(field); String paramName = getNextParameterName(field);
ctx.addBooleanParameter(paramName, booleanFilterPredicate.isValue()); ctx.addBooleanParameter(paramName, booleanFilterPredicate.getValue().getValue());
String booleanOperationQuery = ""; String booleanOperationQuery = "";
switch (booleanFilterPredicate.getOperation()) { switch (booleanFilterPredicate.getOperation()) {
case EQUAL: case EQUAL:

View File

@ -49,6 +49,7 @@ import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityListFilter; import org.thingsboard.server.common.data.query.EntityListFilter;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.RelationsQueryFilter; import org.thingsboard.server.common.data.query.RelationsQueryFilter;
@ -239,7 +240,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
KeyFilter highTemperatureFilter = new KeyFilter(); KeyFilter highTemperatureFilter = new KeyFilter();
highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature")); highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
NumericFilterPredicate predicate = new NumericFilterPredicate(); NumericFilterPredicate predicate = new NumericFilterPredicate();
predicate.setValue(45); predicate.setValue(FilterPredicateValue.fromDouble(45));
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperatureFilter.setPredicate(predicate); highTemperatureFilter.setPredicate(predicate);
List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter); List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter);
@ -311,7 +312,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
KeyFilter highTemperatureFilter = new KeyFilter(); KeyFilter highTemperatureFilter = new KeyFilter();
highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature")); highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
NumericFilterPredicate predicate = new NumericFilterPredicate(); NumericFilterPredicate predicate = new NumericFilterPredicate();
predicate.setValue(45); predicate.setValue(FilterPredicateValue.fromDouble(45));
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperatureFilter.setPredicate(predicate); highTemperatureFilter.setPredicate(predicate);
List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter); List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter);
@ -382,7 +383,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
KeyFilter highTemperatureFilter = new KeyFilter(); KeyFilter highTemperatureFilter = new KeyFilter();
highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "consumption")); highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "consumption"));
NumericFilterPredicate predicate = new NumericFilterPredicate(); NumericFilterPredicate predicate = new NumericFilterPredicate();
predicate.setValue(50); predicate.setValue(FilterPredicateValue.fromDouble(50));
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperatureFilter.setPredicate(predicate); highTemperatureFilter.setPredicate(predicate);
List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter); List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter);
@ -584,7 +585,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
KeyFilter highTemperatureFilter = new KeyFilter(); KeyFilter highTemperatureFilter = new KeyFilter();
highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature")); highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
NumericFilterPredicate predicate = new NumericFilterPredicate(); NumericFilterPredicate predicate = new NumericFilterPredicate();
predicate.setValue(45); predicate.setValue(FilterPredicateValue.fromDouble(45));
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperatureFilter.setPredicate(predicate); highTemperatureFilter.setPredicate(predicate);
List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter); List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter);

View File

@ -396,7 +396,9 @@ export class EntityService {
type: FilterPredicateType.STRING, type: FilterPredicateType.STRING,
operation: StringOperation.STARTS_WITH, operation: StringOperation.STARTS_WITH,
ignoreCase: true, ignoreCase: true,
value: searchText value: {
defaultValue: searchText
}
} }
} }
] : null ] : null

View File

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="booleanFilterPredicateFormGroup"> <div fxFlex fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="8px" [formGroup]="booleanFilterPredicateFormGroup">
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex="40" class="mat-block"> <mat-form-field floatLabel="always" hideRequiredMarker fxFlex="40" class="mat-block">
<mat-label></mat-label> <mat-label></mat-label>
<mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}"> <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
@ -24,7 +24,8 @@
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-checkbox fxFlex="60" formControlName="value"> <tb-filter-predicate-value fxFlex="60"
{{ (booleanFilterPredicateFormGroup.get('value').value ? 'value.true' : 'value.false') | translate }} [valueType]="valueTypeEnum.BOOLEAN"
</mat-checkbox> formControlName="value">
</tb-filter-predicate-value>
</div> </div>

View File

@ -18,10 +18,10 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { import {
BooleanFilterPredicate, BooleanFilterPredicate,
BooleanOperation, booleanOperationTranslationMap, BooleanOperation,
booleanOperationTranslationMap, EntityKeyValueType,
FilterPredicateType FilterPredicateType
} from '@shared/models/query/query.models'; } from '@shared/models/query/query.models';
import { isDefined } from '@core/utils';
@Component({ @Component({
selector: 'tb-boolean-filter-predicate', selector: 'tb-boolean-filter-predicate',
@ -39,6 +39,8 @@ export class BooleanFilterPredicateComponent implements ControlValueAccessor, On
@Input() disabled: boolean; @Input() disabled: boolean;
valueTypeEnum = EntityKeyValueType;
booleanFilterPredicateFormGroup: FormGroup; booleanFilterPredicateFormGroup: FormGroup;
booleanOperations = Object.keys(BooleanOperation); booleanOperations = Object.keys(BooleanOperation);
@ -53,7 +55,7 @@ export class BooleanFilterPredicateComponent implements ControlValueAccessor, On
ngOnInit(): void { ngOnInit(): void {
this.booleanFilterPredicateFormGroup = this.fb.group({ this.booleanFilterPredicateFormGroup = this.fb.group({
operation: [BooleanOperation.EQUAL, [Validators.required]], operation: [BooleanOperation.EQUAL, [Validators.required]],
value: [false] value: [null, [Validators.required]]
}); });
this.booleanFilterPredicateFormGroup.valueChanges.subscribe(() => { this.booleanFilterPredicateFormGroup.valueChanges.subscribe(() => {
this.updateModel(); this.updateModel();
@ -78,16 +80,13 @@ export class BooleanFilterPredicateComponent implements ControlValueAccessor, On
writeValue(predicate: BooleanFilterPredicate): void { writeValue(predicate: BooleanFilterPredicate): void {
this.booleanFilterPredicateFormGroup.get('operation').patchValue(predicate.operation, {emitEvent: false}); this.booleanFilterPredicateFormGroup.get('operation').patchValue(predicate.operation, {emitEvent: false});
this.booleanFilterPredicateFormGroup.get('value').patchValue(isDefined(predicate.value) ? predicate.value : false, {emitEvent: false}); this.booleanFilterPredicateFormGroup.get('value').patchValue(predicate.value, {emitEvent: false});
} }
private updateModel() { private updateModel() {
let predicate: BooleanFilterPredicate = null; let predicate: BooleanFilterPredicate = null;
if (this.booleanFilterPredicateFormGroup.valid) { if (this.booleanFilterPredicateFormGroup.valid) {
predicate = this.booleanFilterPredicateFormGroup.getRawValue(); predicate = this.booleanFilterPredicateFormGroup.getRawValue();
if (!isDefined(predicate.value)) {
predicate.value = false;
}
predicate.type = FilterPredicateType.BOOLEAN; predicate.type = FilterPredicateType.BOOLEAN;
} }
this.propagateChange(predicate); this.propagateChange(predicate);

View File

@ -39,7 +39,7 @@
</div> </div>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<div class="predicate-list"> <div class="predicate-list">
<div fxLayout="row" fxLayoutAlign="start center" style="height: 40px;" <div fxLayout="row" fxLayoutAlign="start center" style="height: 45px;"
formArrayName="predicates" formArrayName="predicates"
*ngFor="let predicateControl of predicatesFormArray().controls; let $index = index"> *ngFor="let predicateControl of predicatesFormArray().controls; let $index = index">
<div fxFlex="8" fxLayout="row" fxLayoutAlign="center" class="filters-operation"> <div fxFlex="8" fxLayout="row" fxLayoutAlign="center" class="filters-operation">

View File

@ -0,0 +1,48 @@
<!--
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.
-->
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="filterPredicateValueFormGroup">
<div fxFlex fxLayout="column">
<div fxFlex fxLayout="column" [ngSwitch]="valueType">
<ng-template [ngSwitchCase]="valueTypeEnum.STRING">
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
<mat-label></mat-label>
<input matInput formControlName="defaultValue" placeholder="{{'filter.value' | translate}}">
</mat-form-field>
</ng-template>
<ng-template [ngSwitchCase]="valueTypeEnum.NUMERIC">
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
<mat-label></mat-label>
<input required type="number" matInput formControlName="defaultValue"
placeholder="{{'filter.value' | translate}}">
</mat-form-field>
</ng-template>
<ng-template [ngSwitchCase]="valueTypeEnum.DATE_TIME">
<tb-datetime fxFlex formControlName="defaultValue"
dateText="filter.date"
timeText="filter.time"
required [showLabel]="false"></tb-datetime>
</ng-template>
<ng-template [ngSwitchCase]="valueTypeEnum.BOOLEAN">
<mat-checkbox fxFlex formControlName="defaultValue">
{{ (filterPredicateValueFormGroup.get('defaultValue').value ? 'value.true' : 'value.false') | translate }}
</mat-checkbox>
</ng-template>
</div>
<div class="tb-hint">Default value</div>
</div>
</div>

View File

@ -0,0 +1,126 @@
///
/// 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 { Component, forwardRef, Input, OnInit } from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
FormGroup,
NG_VALUE_ACCESSOR,
ValidatorFn,
Validators
} from '@angular/forms';
import { EntityKeyValueType, FilterPredicateValue } from '@shared/models/query/query.models';
@Component({
selector: 'tb-filter-predicate-value',
templateUrl: './filter-predicate-value.component.html',
styleUrls: ['./filter-predicate.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FilterPredicateValueComponent),
multi: true
}
]
})
export class FilterPredicateValueComponent implements ControlValueAccessor, OnInit {
@Input() disabled: boolean;
@Input()
valueType: EntityKeyValueType;
valueTypeEnum = EntityKeyValueType;
filterPredicateValueFormGroup: FormGroup;
private propagateChange = null;
constructor(private fb: FormBuilder) {
}
ngOnInit(): void {
let defaultValue: string | number | boolean;
let defaultValueValidators: ValidatorFn[];
switch (this.valueType) {
case EntityKeyValueType.STRING:
defaultValue = '';
defaultValueValidators = [];
break;
case EntityKeyValueType.NUMERIC:
defaultValue = 0;
defaultValueValidators = [Validators.required];
break;
case EntityKeyValueType.BOOLEAN:
defaultValue = false;
defaultValueValidators = [];
break;
case EntityKeyValueType.DATE_TIME:
defaultValue = Date.now();
defaultValueValidators = [Validators.required];
break;
}
this.filterPredicateValueFormGroup = this.fb.group({
defaultValue: [defaultValue, defaultValueValidators],
dynamicValue: this.fb.group(
{
sourceType: [null],
sourceAttribute: [null]
}
)
});
this.filterPredicateValueFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.filterPredicateValueFormGroup.disable({emitEvent: false});
} else {
this.filterPredicateValueFormGroup.enable({emitEvent: false});
}
}
writeValue(predicateValue: FilterPredicateValue<string | number | boolean>): void {
this.filterPredicateValueFormGroup.get('defaultValue').patchValue(predicateValue.defaultValue, {emitEvent: false});
this.filterPredicateValueFormGroup.get('dynamicValue').patchValue(predicateValue.dynamicValue ?
predicateValue.dynamicValue : { sourceType: null, sourceAttribute: null }, {emitEvent: false});
}
private updateModel() {
let predicateValue: FilterPredicateValue<string | number | boolean> = null;
if (this.filterPredicateValueFormGroup.valid) {
predicateValue = this.filterPredicateValueFormGroup.getRawValue();
if (predicateValue.dynamicValue) {
if (!predicateValue.dynamicValue.sourceType || !predicateValue.dynamicValue.sourceAttribute) {
predicateValue.dynamicValue = null;
}
}
}
this.propagateChange(predicateValue);
}
}

View File

@ -13,6 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
:host {
.tb-hint {
padding-bottom: 0;
}
}
:host ::ng-deep { :host ::ng-deep {
mat-form-field { mat-form-field {
.mat-form-field-wrapper { .mat-form-field-wrapper {

View File

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="numericFilterPredicateFormGroup"> <div fxFlex fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="8px" [formGroup]="numericFilterPredicateFormGroup">
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex="40" class="mat-block"> <mat-form-field floatLabel="always" hideRequiredMarker fxFlex="40" class="mat-block">
<mat-label></mat-label> <mat-label></mat-label>
<mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}"> <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
@ -24,19 +24,8 @@
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<div fxFlex="60" fxLayout="column" [ngSwitch]="valueType"> <tb-filter-predicate-value fxFlex="60"
<ng-template [ngSwitchCase]="valueTypeEnum.NUMERIC"> [valueType]="valueType"
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block"> formControlName="value">
<mat-label></mat-label> </tb-filter-predicate-value>
<input required type="number" matInput formControlName="value"
placeholder="{{'filter.value' | translate}}">
</mat-form-field>
</ng-template>
<ng-template [ngSwitchCase]="valueTypeEnum.DATE_TIME">
<tb-datetime fxFlex formControlName="value"
dateText="filter.date"
timeText="filter.time"
required [showLabel]="false"></tb-datetime>
</ng-template>
</div>
</div> </div>

View File

@ -18,9 +18,11 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { import {
EntityKeyValueType, EntityKeyValueType,
FilterPredicateType, NumericFilterPredicate, NumericOperation, numericOperationTranslationMap, FilterPredicateType,
NumericFilterPredicate,
NumericOperation,
numericOperationTranslationMap,
} from '@shared/models/query/query.models'; } from '@shared/models/query/query.models';
import { isDefined } from '@core/utils';
@Component({ @Component({
selector: 'tb-numeric-filter-predicate', selector: 'tb-numeric-filter-predicate',
@ -56,7 +58,7 @@ export class NumericFilterPredicateComponent implements ControlValueAccessor, On
ngOnInit(): void { ngOnInit(): void {
this.numericFilterPredicateFormGroup = this.fb.group({ this.numericFilterPredicateFormGroup = this.fb.group({
operation: [NumericOperation.EQUAL, [Validators.required]], operation: [NumericOperation.EQUAL, [Validators.required]],
value: [0, [Validators.required]] value: [null, [Validators.required]]
}); });
this.numericFilterPredicateFormGroup.valueChanges.subscribe(() => { this.numericFilterPredicateFormGroup.valueChanges.subscribe(() => {
this.updateModel(); this.updateModel();
@ -81,16 +83,13 @@ export class NumericFilterPredicateComponent implements ControlValueAccessor, On
writeValue(predicate: NumericFilterPredicate): void { writeValue(predicate: NumericFilterPredicate): void {
this.numericFilterPredicateFormGroup.get('operation').patchValue(predicate.operation, {emitEvent: false}); this.numericFilterPredicateFormGroup.get('operation').patchValue(predicate.operation, {emitEvent: false});
this.numericFilterPredicateFormGroup.get('value').patchValue(isDefined(predicate.value) ? predicate.value : 0, {emitEvent: false}); this.numericFilterPredicateFormGroup.get('value').patchValue(predicate.value, {emitEvent: false});
} }
private updateModel() { private updateModel() {
let predicate: NumericFilterPredicate = null; let predicate: NumericFilterPredicate = null;
if (this.numericFilterPredicateFormGroup.valid) { if (this.numericFilterPredicateFormGroup.valid) {
predicate = this.numericFilterPredicateFormGroup.getRawValue(); predicate = this.numericFilterPredicateFormGroup.getRawValue();
if (!predicate.value) {
predicate.value = 0;
}
predicate.type = FilterPredicateType.NUMERIC; predicate.type = FilterPredicateType.NUMERIC;
} }
this.propagateChange(predicate); this.propagateChange(predicate);

View File

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="stringFilterPredicateFormGroup"> <div fxFlex fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="8px" [formGroup]="stringFilterPredicateFormGroup">
<div fxFlex="40" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxFlex="40" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block"> <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
<mat-label></mat-label> <mat-label></mat-label>
@ -28,8 +28,8 @@
<mat-checkbox fxLayout="row" fxLayoutAlign="center" formControlName="ignoreCase" style="min-width: 70px;"> <mat-checkbox fxLayout="row" fxLayoutAlign="center" formControlName="ignoreCase" style="min-width: 70px;">
</mat-checkbox> </mat-checkbox>
</div> </div>
<mat-form-field floatLabel="always" hideRequiredMarker fxFlex="60" class="mat-block"> <tb-filter-predicate-value fxFlex="60"
<mat-label></mat-label> [valueType]="valueTypeEnum.STRING"
<input matInput formControlName="value" placeholder="{{'filter.value' | translate}}"> formControlName="value">
</mat-form-field> </tb-filter-predicate-value>
</div> </div>

View File

@ -17,6 +17,7 @@
import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { import {
EntityKeyValueType,
FilterPredicateType, FilterPredicateType,
StringFilterPredicate, StringFilterPredicate,
StringOperation, StringOperation,
@ -39,6 +40,8 @@ export class StringFilterPredicateComponent implements ControlValueAccessor, OnI
@Input() disabled: boolean; @Input() disabled: boolean;
valueTypeEnum = EntityKeyValueType;
stringFilterPredicateFormGroup: FormGroup; stringFilterPredicateFormGroup: FormGroup;
stringOperations = Object.keys(StringOperation); stringOperations = Object.keys(StringOperation);
@ -53,7 +56,7 @@ export class StringFilterPredicateComponent implements ControlValueAccessor, OnI
ngOnInit(): void { ngOnInit(): void {
this.stringFilterPredicateFormGroup = this.fb.group({ this.stringFilterPredicateFormGroup = this.fb.group({
operation: [StringOperation.STARTS_WITH, [Validators.required]], operation: [StringOperation.STARTS_WITH, [Validators.required]],
value: [''], value: [null, [Validators.required]],
ignoreCase: [false] ignoreCase: [false]
}); });
this.stringFilterPredicateFormGroup.valueChanges.subscribe(() => { this.stringFilterPredicateFormGroup.valueChanges.subscribe(() => {
@ -79,7 +82,7 @@ export class StringFilterPredicateComponent implements ControlValueAccessor, OnI
writeValue(predicate: StringFilterPredicate): void { writeValue(predicate: StringFilterPredicate): void {
this.stringFilterPredicateFormGroup.get('operation').patchValue(predicate.operation, {emitEvent: false}); this.stringFilterPredicateFormGroup.get('operation').patchValue(predicate.operation, {emitEvent: false});
this.stringFilterPredicateFormGroup.get('value').patchValue(predicate.value ? predicate.value : '', {emitEvent: false}); this.stringFilterPredicateFormGroup.get('value').patchValue(predicate.value, {emitEvent: false});
this.stringFilterPredicateFormGroup.get('ignoreCase').patchValue(predicate.ignoreCase, {emitEvent: false}); this.stringFilterPredicateFormGroup.get('ignoreCase').patchValue(predicate.ignoreCase, {emitEvent: false});
} }
@ -87,9 +90,6 @@ export class StringFilterPredicateComponent implements ControlValueAccessor, OnI
let predicate: StringFilterPredicate = null; let predicate: StringFilterPredicate = null;
if (this.stringFilterPredicateFormGroup.valid) { if (this.stringFilterPredicateFormGroup.valid) {
predicate = this.stringFilterPredicateFormGroup.getRawValue(); predicate = this.stringFilterPredicateFormGroup.getRawValue();
if (!predicate.value) {
predicate.value = '';
}
predicate.type = FilterPredicateType.STRING; predicate.type = FilterPredicateType.STRING;
} }
this.propagateChange(predicate); this.propagateChange(predicate);

View File

@ -84,12 +84,12 @@ export class UserFilterDialogComponent extends DialogComponent<UserFilterDialogC
const userInputControl = this.fb.group({ const userInputControl = this.fb.group({
label: [userInput.label], label: [userInput.label],
valueType: [userInput.valueType], valueType: [userInput.valueType],
value: [(userInput.info.keyFilterPredicate as any).value, value: [(userInput.info.keyFilterPredicate as any).value.defaultValue,
userInput.valueType === EntityKeyValueType.NUMERIC || userInput.valueType === EntityKeyValueType.NUMERIC ||
userInput.valueType === EntityKeyValueType.DATE_TIME ? [Validators.required] : []] userInput.valueType === EntityKeyValueType.DATE_TIME ? [Validators.required] : []]
}); });
userInputControl.get('value').valueChanges.subscribe(value => { userInputControl.get('value').valueChanges.subscribe(value => {
(userInput.info.keyFilterPredicate as any).value = value; (userInput.info.keyFilterPredicate as any).value.defaultValue = value;
}); });
return userInputControl; return userInputControl;
} }

View File

@ -83,6 +83,7 @@ import { FiltersEditPanelComponent } from '@home/components/filter/filters-edit-
import { UserFilterDialogComponent } from '@home/components/filter/user-filter-dialog.component'; import { UserFilterDialogComponent } from '@home/components/filter/user-filter-dialog.component';
import { FilterUserInfoComponent } from './filter/filter-user-info.component'; import { FilterUserInfoComponent } from './filter/filter-user-info.component';
import { FilterUserInfoDialogComponent } from './filter/filter-user-info-dialog.component'; import { FilterUserInfoDialogComponent } from './filter/filter-user-info-dialog.component';
import { FilterPredicateValueComponent } from './filter/filter-predicate-value.component';
@NgModule({ @NgModule({
declarations: declarations:
@ -148,7 +149,8 @@ import { FilterUserInfoDialogComponent } from './filter/filter-user-info-dialog.
FiltersEditPanelComponent, FiltersEditPanelComponent,
UserFilterDialogComponent, UserFilterDialogComponent,
FilterUserInfoComponent, FilterUserInfoComponent,
FilterUserInfoDialogComponent FilterUserInfoDialogComponent,
FilterPredicateValueComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,

View File

@ -138,16 +138,22 @@ export function createDefaultFilterPredicate(valueType: EntityKeyValueType, comp
switch (predicate.type) { switch (predicate.type) {
case FilterPredicateType.STRING: case FilterPredicateType.STRING:
predicate.operation = StringOperation.STARTS_WITH; predicate.operation = StringOperation.STARTS_WITH;
predicate.value = ''; predicate.value = {
defaultValue: ''
};
predicate.ignoreCase = false; predicate.ignoreCase = false;
break; break;
case FilterPredicateType.NUMERIC: case FilterPredicateType.NUMERIC:
predicate.operation = NumericOperation.EQUAL; predicate.operation = NumericOperation.EQUAL;
predicate.value = valueType === EntityKeyValueType.DATE_TIME ? Date.now() : 0; predicate.value = {
defaultValue: valueType === EntityKeyValueType.DATE_TIME ? Date.now() : 0
};
break; break;
case FilterPredicateType.BOOLEAN: case FilterPredicateType.BOOLEAN:
predicate.operation = BooleanOperation.EQUAL; predicate.operation = BooleanOperation.EQUAL;
predicate.value = false; predicate.value = {
defaultValue: false
};
break; break;
case FilterPredicateType.COMPLEX: case FilterPredicateType.COMPLEX:
predicate.operation = ComplexOperation.AND; predicate.operation = ComplexOperation.AND;
@ -228,23 +234,47 @@ export const complexOperationTranslationMap = new Map<ComplexOperation, string>(
] ]
); );
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 { export interface StringFilterPredicate {
type: FilterPredicateType.STRING, type: FilterPredicateType.STRING,
operation: StringOperation; operation: StringOperation;
value: string; value: FilterPredicateValue<string>;
ignoreCase: boolean; ignoreCase: boolean;
} }
export interface NumericFilterPredicate { export interface NumericFilterPredicate {
type: FilterPredicateType.NUMERIC, type: FilterPredicateType.NUMERIC,
operation: NumericOperation; operation: NumericOperation;
value: number; value: FilterPredicateValue<number>;
} }
export interface BooleanFilterPredicate { export interface BooleanFilterPredicate {
type: FilterPredicateType.BOOLEAN, type: FilterPredicateType.BOOLEAN,
operation: BooleanOperation; operation: BooleanOperation;
value: boolean; value: FilterPredicateValue<boolean>;
} }
export interface BaseComplexFilterPredicate<T extends KeyFilterPredicate | KeyFilterPredicateInfo> { export interface BaseComplexFilterPredicate<T extends KeyFilterPredicate | KeyFilterPredicateInfo> {

View File

@ -1227,7 +1227,10 @@
"remove-key-filter": "Remove key filter", "remove-key-filter": "Remove key filter",
"edit-key-filter": "Edit key filter", "edit-key-filter": "Edit key filter",
"date": "Date", "date": "Date",
"time": "Time" "time": "Time",
"current-tenant": "Current tenant",
"current-customer": "Current customer",
"current-user": "Current user"
}, },
"fullscreen": { "fullscreen": {
"expand": "Expand to fullscreen", "expand": "Expand to fullscreen",