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

View File

@ -26,9 +26,9 @@ import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@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.*Test",
"org.thingsboard.server.controller.sql.*Test",
})
public class ControllerSqlTestSuite {

View File

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

View File

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

View File

@ -440,7 +440,7 @@ public class EntityKeyMapping {
private String buildStringPredicateQuery(QueryContext ctx, String field, StringFilterPredicate stringFilterPredicate) {
String operationField = field;
String paramName = getNextParameterName(field);
String value = stringFilterPredicate.getValue();
String value = stringFilterPredicate.getValue().getValue();
String stringOperationQuery = "";
if (stringFilterPredicate.isIgnoreCase()) {
value = value.toLowerCase();
@ -476,7 +476,7 @@ public class EntityKeyMapping {
private String buildNumericPredicateQuery(QueryContext ctx, String field, NumericFilterPredicate numericFilterPredicate) {
String paramName = getNextParameterName(field);
ctx.addDoubleParameter(paramName, numericFilterPredicate.getValue());
ctx.addDoubleParameter(paramName, numericFilterPredicate.getValue().getValue());
String numericOperationQuery = "";
switch (numericFilterPredicate.getOperation()) {
case EQUAL:
@ -504,7 +504,7 @@ public class EntityKeyMapping {
private String buildBooleanPredicateQuery(QueryContext ctx, String field,
BooleanFilterPredicate booleanFilterPredicate) {
String paramName = getNextParameterName(field);
ctx.addBooleanParameter(paramName, booleanFilterPredicate.isValue());
ctx.addBooleanParameter(paramName, booleanFilterPredicate.getValue().getValue());
String booleanOperationQuery = "";
switch (booleanFilterPredicate.getOperation()) {
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.EntityKeyType;
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.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.RelationsQueryFilter;
@ -239,7 +240,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
KeyFilter highTemperatureFilter = new KeyFilter();
highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
NumericFilterPredicate predicate = new NumericFilterPredicate();
predicate.setValue(45);
predicate.setValue(FilterPredicateValue.fromDouble(45));
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperatureFilter.setPredicate(predicate);
List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter);
@ -311,7 +312,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
KeyFilter highTemperatureFilter = new KeyFilter();
highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
NumericFilterPredicate predicate = new NumericFilterPredicate();
predicate.setValue(45);
predicate.setValue(FilterPredicateValue.fromDouble(45));
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperatureFilter.setPredicate(predicate);
List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter);
@ -382,7 +383,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
KeyFilter highTemperatureFilter = new KeyFilter();
highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "consumption"));
NumericFilterPredicate predicate = new NumericFilterPredicate();
predicate.setValue(50);
predicate.setValue(FilterPredicateValue.fromDouble(50));
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperatureFilter.setPredicate(predicate);
List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter);
@ -584,7 +585,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
KeyFilter highTemperatureFilter = new KeyFilter();
highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
NumericFilterPredicate predicate = new NumericFilterPredicate();
predicate.setValue(45);
predicate.setValue(FilterPredicateValue.fromDouble(45));
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperatureFilter.setPredicate(predicate);
List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter);

View File

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

View File

@ -15,7 +15,7 @@
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-label></mat-label>
<mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
@ -24,7 +24,8 @@
</mat-option>
</mat-select>
</mat-form-field>
<mat-checkbox fxFlex="60" formControlName="value">
{{ (booleanFilterPredicateFormGroup.get('value').value ? 'value.true' : 'value.false') | translate }}
</mat-checkbox>
<tb-filter-predicate-value fxFlex="60"
[valueType]="valueTypeEnum.BOOLEAN"
formControlName="value">
</tb-filter-predicate-value>
</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 {
BooleanFilterPredicate,
BooleanOperation, booleanOperationTranslationMap,
BooleanOperation,
booleanOperationTranslationMap, EntityKeyValueType,
FilterPredicateType
} from '@shared/models/query/query.models';
import { isDefined } from '@core/utils';
@Component({
selector: 'tb-boolean-filter-predicate',
@ -39,6 +39,8 @@ export class BooleanFilterPredicateComponent implements ControlValueAccessor, On
@Input() disabled: boolean;
valueTypeEnum = EntityKeyValueType;
booleanFilterPredicateFormGroup: FormGroup;
booleanOperations = Object.keys(BooleanOperation);
@ -53,7 +55,7 @@ export class BooleanFilterPredicateComponent implements ControlValueAccessor, On
ngOnInit(): void {
this.booleanFilterPredicateFormGroup = this.fb.group({
operation: [BooleanOperation.EQUAL, [Validators.required]],
value: [false]
value: [null, [Validators.required]]
});
this.booleanFilterPredicateFormGroup.valueChanges.subscribe(() => {
this.updateModel();
@ -78,16 +80,13 @@ export class BooleanFilterPredicateComponent implements ControlValueAccessor, On
writeValue(predicate: BooleanFilterPredicate): void {
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() {
let predicate: BooleanFilterPredicate = null;
if (this.booleanFilterPredicateFormGroup.valid) {
predicate = this.booleanFilterPredicateFormGroup.getRawValue();
if (!isDefined(predicate.value)) {
predicate.value = false;
}
predicate.type = FilterPredicateType.BOOLEAN;
}
this.propagateChange(predicate);

View File

@ -39,7 +39,7 @@
</div>
<mat-divider></mat-divider>
<div class="predicate-list">
<div fxLayout="row" fxLayoutAlign="start center" style="height: 40px;"
<div fxLayout="row" fxLayoutAlign="start center" style="height: 45px;"
formArrayName="predicates"
*ngFor="let predicateControl of predicatesFormArray().controls; let $index = index">
<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
* limitations under the License.
*/
:host {
.tb-hint {
padding-bottom: 0;
}
}
:host ::ng-deep {
mat-form-field {
.mat-form-field-wrapper {

View File

@ -15,7 +15,7 @@
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-label></mat-label>
<mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
@ -24,19 +24,8 @@
</mat-option>
</mat-select>
</mat-form-field>
<div fxFlex="60" fxLayout="column" [ngSwitch]="valueType">
<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="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>
<tb-filter-predicate-value fxFlex="60"
[valueType]="valueType"
formControlName="value">
</tb-filter-predicate-value>
</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 {
EntityKeyValueType,
FilterPredicateType, NumericFilterPredicate, NumericOperation, numericOperationTranslationMap,
FilterPredicateType,
NumericFilterPredicate,
NumericOperation,
numericOperationTranslationMap,
} from '@shared/models/query/query.models';
import { isDefined } from '@core/utils';
@Component({
selector: 'tb-numeric-filter-predicate',
@ -56,7 +58,7 @@ export class NumericFilterPredicateComponent implements ControlValueAccessor, On
ngOnInit(): void {
this.numericFilterPredicateFormGroup = this.fb.group({
operation: [NumericOperation.EQUAL, [Validators.required]],
value: [0, [Validators.required]]
value: [null, [Validators.required]]
});
this.numericFilterPredicateFormGroup.valueChanges.subscribe(() => {
this.updateModel();
@ -81,16 +83,13 @@ export class NumericFilterPredicateComponent implements ControlValueAccessor, On
writeValue(predicate: NumericFilterPredicate): void {
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() {
let predicate: NumericFilterPredicate = null;
if (this.numericFilterPredicateFormGroup.valid) {
predicate = this.numericFilterPredicateFormGroup.getRawValue();
if (!predicate.value) {
predicate.value = 0;
}
predicate.type = FilterPredicateType.NUMERIC;
}
this.propagateChange(predicate);

View File

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

View File

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

View File

@ -84,12 +84,12 @@ export class UserFilterDialogComponent extends DialogComponent<UserFilterDialogC
const userInputControl = this.fb.group({
label: [userInput.label],
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.DATE_TIME ? [Validators.required] : []]
});
userInputControl.get('value').valueChanges.subscribe(value => {
(userInput.info.keyFilterPredicate as any).value = value;
(userInput.info.keyFilterPredicate as any).value.defaultValue = value;
});
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 { FilterUserInfoComponent } from './filter/filter-user-info.component';
import { FilterUserInfoDialogComponent } from './filter/filter-user-info-dialog.component';
import { FilterPredicateValueComponent } from './filter/filter-predicate-value.component';
@NgModule({
declarations:
@ -148,7 +149,8 @@ import { FilterUserInfoDialogComponent } from './filter/filter-user-info-dialog.
FiltersEditPanelComponent,
UserFilterDialogComponent,
FilterUserInfoComponent,
FilterUserInfoDialogComponent
FilterUserInfoDialogComponent,
FilterPredicateValueComponent
],
imports: [
CommonModule,

View File

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

View File

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