Browse Source

UI: Dashboard filters implementation

pull/3053/head
Igor Kulikov 6 years ago
parent
commit
25dae17671
  1. 24
      ui-ngx/src/app/core/api/alias-controller.ts
  2. 12
      ui-ngx/src/app/core/api/entity-data-subscription.ts
  3. 13
      ui-ngx/src/app/core/api/entity-data.service.ts
  4. 1
      ui-ngx/src/app/core/api/widget-api.models.ts
  5. 2
      ui-ngx/src/app/core/api/widget-subscription.ts
  6. 6
      ui-ngx/src/app/core/services/item-buffer.service.ts
  7. 12
      ui-ngx/src/app/modules/home/components/filter/boolean-filter-predicate.component.html
  8. 2
      ui-ngx/src/app/modules/home/components/filter/boolean-filter-predicate.component.ts
  9. 7
      ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html
  10. 5
      ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.ts
  11. 3
      ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.html
  12. 3
      ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.ts
  13. 34
      ui-ngx/src/app/modules/home/components/filter/filter-dialog.component.html
  14. 28
      ui-ngx/src/app/modules/home/components/filter/filter-dialog.component.scss
  15. 7
      ui-ngx/src/app/modules/home/components/filter/filter-dialog.component.ts
  16. 106
      ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.html
  17. 29
      ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.scss
  18. 12
      ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.ts
  19. 29
      ui-ngx/src/app/modules/home/components/filter/filter-predicate.scss
  20. 23
      ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html
  21. 8
      ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.scss
  22. 20
      ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.ts
  23. 33
      ui-ngx/src/app/modules/home/components/filter/filters-edit-panel.component.html
  24. 35
      ui-ngx/src/app/modules/home/components/filter/filters-edit-panel.component.scss
  25. 65
      ui-ngx/src/app/modules/home/components/filter/filters-edit-panel.component.ts
  26. 32
      ui-ngx/src/app/modules/home/components/filter/filters-edit.component.html
  27. 36
      ui-ngx/src/app/modules/home/components/filter/filters-edit.component.scss
  28. 154
      ui-ngx/src/app/modules/home/components/filter/filters-edit.component.ts
  29. 20
      ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html
  30. 26
      ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.scss
  31. 24
      ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts
  32. 95
      ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html
  33. 29
      ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.scss
  34. 6
      ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.ts
  35. 15
      ui-ngx/src/app/modules/home/components/filter/numeric-filter-predicate.component.html
  36. 2
      ui-ngx/src/app/modules/home/components/filter/numeric-filter-predicate.component.ts
  37. 31
      ui-ngx/src/app/modules/home/components/filter/string-filter-predicate.component.html
  38. 2
      ui-ngx/src/app/modules/home/components/filter/string-filter-predicate.component.ts
  39. 9
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  40. 2
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  41. 4
      ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html
  42. 9
      ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts
  43. 3
      ui-ngx/src/app/modules/home/pages/dashboard/dashboard-settings-dialog.component.html
  44. 1
      ui-ngx/src/app/modules/home/pages/dashboard/dashboard-settings-dialog.component.ts
  45. 1
      ui-ngx/src/app/shared/models/dashboard.models.ts
  46. 1
      ui-ngx/src/app/shared/models/query/query.models.ts
  47. 5
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  48. 3
      ui-ngx/src/styles.scss

24
ui-ngx/src/app/core/api/alias-controller.ts

@ -24,7 +24,7 @@ import { EntityAliases } from '@shared/models/alias.models';
import { EntityInfo } from '@shared/models/entity.models'; import { EntityInfo } from '@shared/models/entity.models';
import { map, mergeMap } from 'rxjs/operators'; import { map, mergeMap } from 'rxjs/operators';
import { import {
defaultEntityDataPageLink, FilterInfo, filterInfoToKeyFilters, Filters, KeyFilter, singleEntityDataPageLink, defaultEntityDataPageLink, Filter, FilterInfo, filterInfoToKeyFilters, Filters, KeyFilter, singleEntityDataPageLink,
updateDatasourceFromEntityInfo updateDatasourceFromEntityInfo
} from '@shared/models/query/query.models'; } from '@shared/models/query/query.models';
@ -41,6 +41,7 @@ export class AliasController implements IAliasController {
entityAliases: EntityAliases; entityAliases: EntityAliases;
filters: Filters; filters: Filters;
userFilters: Filters;
resolvedAliases: {[aliasId: string]: AliasInfo} = {}; resolvedAliases: {[aliasId: string]: AliasInfo} = {};
resolvedAliasesObservable: {[aliasId: string]: Observable<AliasInfo>} = {}; resolvedAliasesObservable: {[aliasId: string]: Observable<AliasInfo>} = {};
@ -54,6 +55,7 @@ export class AliasController implements IAliasController {
private origFilters: Filters) { private origFilters: Filters) {
this.entityAliases = deepClone(this.origEntityAliases); this.entityAliases = deepClone(this.origEntityAliases);
this.filters = deepClone(this.origFilters); this.filters = deepClone(this.origFilters);
this.userFilters = {};
} }
updateEntityAliases(newEntityAliases: EntityAliases) { updateEntityAliases(newEntityAliases: EntityAliases) {
@ -94,6 +96,9 @@ export class AliasController implements IAliasController {
} }
this.filters = deepClone(newFilters); this.filters = deepClone(newFilters);
if (changedFilterIds.length) { if (changedFilterIds.length) {
for (const filterId of changedFilterIds) {
delete this.userFilters[filterId];
}
this.filtersChangedSubject.next(changedFilterIds); this.filtersChangedSubject.next(changedFilterIds);
} }
} }
@ -146,7 +151,11 @@ export class AliasController implements IAliasController {
} }
getFilterInfo(filterId: string): FilterInfo { getFilterInfo(filterId: string): FilterInfo {
return this.filters[filterId]; if (this.userFilters[filterId]) {
return this.userFilters[filterId];
} else {
return this.filters[filterId];
}
} }
getKeyFilters(filterId: string): Array<KeyFilter> { getKeyFilters(filterId: string): Array<KeyFilter> {
@ -353,4 +362,15 @@ export class AliasController implements IAliasController {
} }
} }
} }
updateUserFilter(filter: Filter) {
let prevUserFilter = this.userFilters[filter.id];
if (!prevUserFilter) {
prevUserFilter = this.filters[filter.id];
}
if (prevUserFilter && !isEqual(prevUserFilter, filter)) {
this.userFilters[filter.id] = filter;
this.filtersChangedSubject.next([filter.id]);
}
}
} }

12
ui-ngx/src/app/core/api/entity-data-subscription.ts

@ -68,6 +68,7 @@ export interface EntityDataSubscriptionOptions {
isPaginatedDataSubscription?: boolean; isPaginatedDataSubscription?: boolean;
pageLink?: EntityDataPageLink; pageLink?: EntityDataPageLink;
keyFilters?: Array<KeyFilter>; keyFilters?: Array<KeyFilter>;
additionalKeyFilters?: Array<KeyFilter>;
subscriptionTimewindow?: SubscriptionTimewindow; subscriptionTimewindow?: SubscriptionTimewindow;
} }
@ -206,10 +207,19 @@ export class EntityDataSubscription {
this.subscriber = new TelemetrySubscriber(this.telemetryService); this.subscriber = new TelemetrySubscriber(this.telemetryService);
this.dataCommand = new EntityDataCmd(); this.dataCommand = new EntityDataCmd();
let keyFilters = this.entityDataSubscriptionOptions.keyFilters;
if (this.entityDataSubscriptionOptions.additionalKeyFilters) {
if (keyFilters) {
keyFilters = keyFilters.concat(this.entityDataSubscriptionOptions.additionalKeyFilters);
} else {
keyFilters = this.entityDataSubscriptionOptions.additionalKeyFilters;
}
}
this.dataCommand.query = { this.dataCommand.query = {
entityFilter: this.entityDataSubscriptionOptions.entityFilter, entityFilter: this.entityDataSubscriptionOptions.entityFilter,
pageLink: this.entityDataSubscriptionOptions.pageLink, pageLink: this.entityDataSubscriptionOptions.pageLink,
keyFilters: this.entityDataSubscriptionOptions.keyFilters, keyFilters,
entityFields, entityFields,
latestValues: this.latestValues latestValues: this.latestValues
}; };

13
ui-ngx/src/app/core/api/entity-data.service.ts

@ -65,7 +65,7 @@ export class EntityDataService {
return of(null); return of(null);
} }
listener.subscription = this.createSubscription(listener, listener.subscription = this.createSubscription(listener,
datasource.pageLink, datasource.keyFilters, datasource.pageLink, datasource.keyFilters, null,
false); false);
return listener.subscription.subscribe(); return listener.subscription.subscribe();
} }
@ -86,15 +86,8 @@ export class EntityDataService {
if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !pageLink)) { if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !pageLink)) {
return of(null); return of(null);
} }
if (datasource.keyFilters) {
if (keyFilters) {
keyFilters = keyFilters.concat(datasource.keyFilters);
} else {
keyFilters = datasource.keyFilters;
}
}
listener.subscription = this.createSubscription(listener, listener.subscription = this.createSubscription(listener,
pageLink, keyFilters, true); pageLink, datasource.keyFilters, keyFilters,true);
if (listener.subscriptionType === widgetType.timeseries) { if (listener.subscriptionType === widgetType.timeseries) {
listener.subscription.entityDataSubscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow); listener.subscription.entityDataSubscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow);
} }
@ -110,6 +103,7 @@ export class EntityDataService {
private createSubscription(listener: EntityDataListener, private createSubscription(listener: EntityDataListener,
pageLink: EntityDataPageLink, pageLink: EntityDataPageLink,
keyFilters: KeyFilter[], keyFilters: KeyFilter[],
additionalKeyFilters: KeyFilter[],
isPaginatedDataSubscription: boolean): EntityDataSubscription { isPaginatedDataSubscription: boolean): EntityDataSubscription {
const datasource = listener.configDatasource; const datasource = listener.configDatasource;
const subscriptionDataKeys: Array<SubscriptionDataKey> = []; const subscriptionDataKeys: Array<SubscriptionDataKey> = [];
@ -131,6 +125,7 @@ export class EntityDataService {
entityDataSubscriptionOptions.entityFilter = datasource.entityFilter; entityDataSubscriptionOptions.entityFilter = datasource.entityFilter;
entityDataSubscriptionOptions.pageLink = pageLink; entityDataSubscriptionOptions.pageLink = pageLink;
entityDataSubscriptionOptions.keyFilters = keyFilters; entityDataSubscriptionOptions.keyFilters = keyFilters;
entityDataSubscriptionOptions.additionalKeyFilters = additionalKeyFilters;
} }
entityDataSubscriptionOptions.isPaginatedDataSubscription = isPaginatedDataSubscription; entityDataSubscriptionOptions.isPaginatedDataSubscription = isPaginatedDataSubscription;
return new EntityDataSubscription(entityDataSubscriptionOptions, return new EntityDataSubscription(entityDataSubscriptionOptions,

1
ui-ngx/src/app/core/api/widget-api.models.ts

@ -112,6 +112,7 @@ export interface IAliasController {
getFilterInfo(filterId: string): FilterInfo; getFilterInfo(filterId: string): FilterInfo;
getKeyFilters(filterId: string): Array<KeyFilter>; getKeyFilters(filterId: string): Array<KeyFilter>;
updateCurrentAliasEntity(aliasId: string, currentEntity: EntityInfo); updateCurrentAliasEntity(aliasId: string, currentEntity: EntityInfo);
updateUserFilter(filter: Filter);
updateEntityAliases(entityAliases: EntityAliases); updateEntityAliases(entityAliases: EntityAliases);
updateFilters(filters: Filters); updateFilters(filters: Filters);
updateAliases(aliasIds?: Array<string>); updateAliases(aliasIds?: Array<string>);

2
ui-ngx/src/app/core/api/widget-subscription.ts

@ -1011,7 +1011,7 @@ export class WidgetSubscription implements IWidgetSubscription {
const entityDataListener = this.entityDataListeners[datasourceIndex]; const entityDataListener = this.entityDataListeners[datasourceIndex];
if (entityDataListener) { if (entityDataListener) {
const pageLink = entityDataListener.subscription.entityDataSubscriptionOptions.pageLink; const pageLink = entityDataListener.subscription.entityDataSubscriptionOptions.pageLink;
const keyFilters = entityDataListener.subscription.entityDataSubscriptionOptions.keyFilters; const keyFilters = entityDataListener.subscription.entityDataSubscriptionOptions.additionalKeyFilters;
this.subscribeForPaginatedData(datasourceIndex, pageLink, keyFilters); this.subscribeForPaginatedData(datasourceIndex, pageLink, keyFilters);
} }
} }

6
ui-ngx/src/app/core/services/item-buffer.service.ts

@ -410,7 +410,8 @@ export class ItemBufferService {
private prepareFilterInfo(filter: Filter): FilterInfo { private prepareFilterInfo(filter: Filter): FilterInfo {
return { return {
filter: filter.filter, filter: filter.filter,
keyFilters: filter.keyFilters keyFilters: filter.keyFilters,
editable: filter.editable
}; };
} }
@ -513,7 +514,8 @@ export class ItemBufferService {
if (!newFilterId) { if (!newFilterId) {
const newFilterName = this.createFilterName(filters, filterInfo.filter); const newFilterName = this.createFilterName(filters, filterInfo.filter);
newFilterId = this.utils.guid(); newFilterId = this.utils.guid();
filters[newFilterId] = {id: newFilterId, filter: newFilterName, keyFilters: filterInfo.keyFilters}; filters[newFilterId] = {id: newFilterId, filter: newFilterName,
keyFilters: filterInfo.keyFilters, editable: filterInfo.editable};
} }
return newFilterId; return newFilterId;
} }

12
ui-ngx/src/app/modules/home/components/filter/boolean-filter-predicate.component.html

@ -15,16 +15,16 @@
limitations under the License. limitations under the License.
--> -->
<div fxLayout="row" [formGroup]="booleanFilterPredicateFormGroup"> <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="booleanFilterPredicateFormGroup">
<mat-form-field class="mat-block"> <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
<mat-label translate>filter.operation.operation</mat-label> <mat-label></mat-label>
<mat-select required formControlName="operation"> <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
<mat-option *ngFor="let operation of booleanOperations" [value]="operation"> <mat-option *ngFor="let operation of booleanOperations" [value]="operation">
{{booleanOperationTranslations.get(booleanOperationEnum[operation]) | translate}} {{booleanOperationTranslations.get(booleanOperationEnum[operation]) | translate}}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-checkbox formControlName="value"> <mat-checkbox fxFlex formControlName="value">
{{ 'filter.value' | translate }} {{ (booleanFilterPredicateFormGroup.get('value').value ? 'value.true' : 'value.false') | translate }}
</mat-checkbox> </mat-checkbox>
</div> </div>

2
ui-ngx/src/app/modules/home/components/filter/boolean-filter-predicate.component.ts

@ -26,7 +26,7 @@ import { isDefined } from '@core/utils';
@Component({ @Component({
selector: 'tb-boolean-filter-predicate', selector: 'tb-boolean-filter-predicate',
templateUrl: './boolean-filter-predicate.component.html', templateUrl: './boolean-filter-predicate.component.html',
styleUrls: [], styleUrls: ['./filter-predicate.scss'],
providers: [ providers: [
{ {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,

7
ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.html

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<form [formGroup]="complexFilterFormGroup" (ngSubmit)="save()"> <form [formGroup]="complexFilterFormGroup" (ngSubmit)="save()" style="width: 700px;">
<mat-toolbar color="primary"> <mat-toolbar color="primary">
<h2 translate>filter.complex-filter</h2> <h2 translate>filter.complex-filter</h2>
<span fxFlex></span> <span fxFlex></span>
@ -38,6 +38,7 @@
<tb-filter-predicate-list <tb-filter-predicate-list
[userMode]="data.userMode" [userMode]="data.userMode"
[valueType]="data.valueType" [valueType]="data.valueType"
[operation]="complexFilterFormGroup.get('operation').value"
formControlName="predicates"> formControlName="predicates">
</tb-filter-predicate-list> </tb-filter-predicate-list>
</fieldset> </fieldset>
@ -45,8 +46,8 @@
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
type="submit" type="submit"
[disabled]="(isLoading$ | async) || complexFilterFormGroup.invalid"> [disabled]="(isLoading$ | async) || complexFilterFormGroup.invalid || !complexFilterFormGroup.dirty">
{{ 'action.update' | translate }} {{ (isAdd ? 'action.add' : 'action.update') | translate }}
</button> </button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"

5
ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate-dialog.component.ts

@ -33,6 +33,7 @@ export interface ComplexFilterPredicateDialogData {
complexPredicate: ComplexFilterPredicate; complexPredicate: ComplexFilterPredicate;
userMode: boolean; userMode: boolean;
disabled: boolean; disabled: boolean;
isAdd: boolean;
valueType: EntityKeyValueType; valueType: EntityKeyValueType;
} }
@ -52,6 +53,8 @@ export class ComplexFilterPredicateDialogComponent extends
complexOperationEnum = ComplexOperation; complexOperationEnum = ComplexOperation;
complexOperationTranslations = complexOperationTranslationMap; complexOperationTranslations = complexOperationTranslationMap;
isAdd: boolean;
submitted = false; submitted = false;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
@ -62,6 +65,8 @@ export class ComplexFilterPredicateDialogComponent extends
private fb: FormBuilder) { private fb: FormBuilder) {
super(store, router, dialogRef); super(store, router, dialogRef);
this.isAdd = this.data.isAdd;
this.complexFilterFormGroup = this.fb.group( this.complexFilterFormGroup = this.fb.group(
{ {
operation: [this.data.complexPredicate.operation, [Validators.required]], operation: [this.data.complexPredicate.operation, [Validators.required]],

3
ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.html

@ -15,9 +15,10 @@
limitations under the License. limitations under the License.
--> -->
<div fxLayout="row"> <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-label translate>filter.complex-filter</mat-label> <mat-label translate>filter.complex-filter</mat-label>
<button mat-icon-button color="primary" <button mat-icon-button color="primary"
class="tb-mat-32"
[fxShow]="!disabled" [fxShow]="!disabled"
type="button" type="button"
(click)="openComplexFilterDialog()" (click)="openComplexFilterDialog()"

3
ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.ts

@ -78,7 +78,8 @@ export class ComplexFilterPredicateComponent implements ControlValueAccessor, On
complexPredicate: deepClone(this.complexFilterPredicate), complexPredicate: deepClone(this.complexFilterPredicate),
disabled: this.disabled, disabled: this.disabled,
userMode: this.userMode, userMode: this.userMode,
valueType: this.valueType valueType: this.valueType,
isAdd: false
} }
}).afterClosed().subscribe( }).afterClosed().subscribe(
(result) => { (result) => {

34
ui-ngx/src/app/modules/home/components/filter/filter-dialog.component.html

@ -15,9 +15,9 @@
limitations under the License. limitations under the License.
--> -->
<form [formGroup]="filterFormGroup" (ngSubmit)="save()" style="min-width: 480px;"> <form [formGroup]="filterFormGroup" (ngSubmit)="save()" style="width: 700px;">
<mat-toolbar color="primary"> <mat-toolbar color="primary">
<h2>{{ (isAdd ? 'filter.add' : 'filter.edit') | translate }}</h2> <h2>{{ userMode ? filter.filter : ((isAdd ? 'filter.add' : 'filter.edit') | translate) }}</h2>
<span fxFlex></span> <span fxFlex></span>
<button mat-icon-button <button mat-icon-button
(click)="cancel()" (click)="cancel()"
@ -30,16 +30,24 @@
<div mat-dialog-content> <div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async"> <fieldset [disabled]="isLoading$ | async">
<div fxFlex fxLayout="column"> <div fxFlex fxLayout="column">
<mat-form-field fxFlex class="mat-block"> <div fxLayout="row" [fxShow]="!userMode">
<mat-label translate>filter.name</mat-label> <mat-form-field fxFlex class="mat-block">
<input matInput formControlName="filter" required> <mat-label translate>filter.name</mat-label>
<mat-error *ngIf="filterFormGroup.get('filter').hasError('required')"> <input matInput formControlName="filter" required>
{{ 'filter.name-required' | translate }} <mat-error *ngIf="filterFormGroup.get('filter').hasError('required')">
</mat-error> {{ 'filter.name-required' | translate }}
<mat-error *ngIf="filterFormGroup.get('filter').hasError('duplicateFilterName')"> </mat-error>
{{ 'filter.duplicate-filter' | translate }} <mat-error *ngIf="filterFormGroup.get('filter').hasError('duplicateFilterName')">
</mat-error> {{ 'filter.duplicate-filter' | translate }}
</mat-form-field> </mat-error>
</mat-form-field>
<section class="tb-editable-switch" fxLayout="column" fxLayoutAlign="start center">
<label class="tb-small editable-label" translate>filter.editable</label>
<mat-slide-toggle class="editable-switch"
formControlName="editable">
</mat-slide-toggle>
</section>
</div>
<tb-key-filter-list <tb-key-filter-list
formControlName="keyFilters" formControlName="keyFilters"
[userMode]="userMode"> [userMode]="userMode">
@ -51,7 +59,7 @@
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
type="submit" type="submit"
[disabled]="(isLoading$ | async) || filterFormGroup.invalid || !filterFormGroup.dirty"> [disabled]="(isLoading$ | async) || filterFormGroup.invalid || !filterFormGroup.dirty">
{{ (isAdd ? 'action.add' : 'action.update') | translate }} {{ (userMode ? 'action.update' : (isAdd ? 'action.add' : 'action.update')) | translate }}
</button> </button>
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"

28
ui-ngx/src/app/modules/home/components/filter/filter-dialog.component.scss

@ -0,0 +1,28 @@
/**
* 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.
*/
:host {
.tb-editable-switch {
padding-left: 10px;
.editable-switch {
margin: 0;
}
.editable-label {
margin: 5px 0;
}
}
}

7
ui-ngx/src/app/modules/home/components/filter/filter-dialog.component.ts

@ -45,7 +45,7 @@ export interface FilterDialogData {
selector: 'tb-filter-dialog', selector: 'tb-filter-dialog',
templateUrl: './filter-dialog.component.html', templateUrl: './filter-dialog.component.html',
providers: [{provide: ErrorStateMatcher, useExisting: FilterDialogComponent}], providers: [{provide: ErrorStateMatcher, useExisting: FilterDialogComponent}],
styleUrls: [] styleUrls: ['./filter-dialog.component.scss']
}) })
export class FilterDialogComponent extends DialogComponent<FilterDialogComponent, Filter> export class FilterDialogComponent extends DialogComponent<FilterDialogComponent, Filter>
implements OnInit, ErrorStateMatcher { implements OnInit, ErrorStateMatcher {
@ -83,7 +83,8 @@ export class FilterDialogComponent extends DialogComponent<FilterDialogComponent
this.filter = { this.filter = {
id: null, id: null,
filter: '', filter: '',
keyFilters: [] keyFilters: [],
editable: true
}; };
} else { } else {
this.filter = data.filter; this.filter = data.filter;
@ -91,6 +92,7 @@ export class FilterDialogComponent extends DialogComponent<FilterDialogComponent
this.filterFormGroup = this.fb.group({ this.filterFormGroup = this.fb.group({
filter: [this.filter.filter, [this.validateDuplicateFilterName(), Validators.required]], filter: [this.filter.filter, [this.validateDuplicateFilterName(), Validators.required]],
editable: [this.filter.editable],
keyFilters: [this.filter.keyFilters, Validators.required] keyFilters: [this.filter.keyFilters, Validators.required]
}); });
} }
@ -128,6 +130,7 @@ export class FilterDialogComponent extends DialogComponent<FilterDialogComponent
save(): void { save(): void {
this.submitted = true; this.submitted = true;
this.filter.filter = this.filterFormGroup.get('filter').value; this.filter.filter = this.filterFormGroup.get('filter').value;
this.filter.editable = this.filterFormGroup.get('editable').value;
this.filter.keyFilters = this.filterFormGroup.get('keyFilters').value; this.filter.keyFilters = this.filterFormGroup.get('keyFilters').value;
if (this.isAdd) { if (this.isAdd) {
this.filter.id = this.utils.guid(); this.filter.id = this.utils.guid();

106
ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.html

@ -16,42 +16,72 @@
--> -->
<section fxLayout="column" [formGroup]="filterListFormGroup"> <section fxLayout="column" [formGroup]="filterListFormGroup">
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" style="max-height: 40px;" <mat-expansion-panel [expanded]="true">
formArrayName="predicates" <mat-expansion-panel-header>
*ngFor="let predicateControl of predicatesFormArray().controls; let $index = index"> <mat-panel-title>
<tb-filter-predicate <div translate>filter.filters</div>
[userMode]="userMode" </mat-panel-title>
[valueType]="valueType" </mat-expansion-panel-header>
[formControl]="predicateControl"> <div fxLayout="row">
</tb-filter-predicate> <span fxFlex="8"></span>
<button mat-icon-button color="primary" <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" fxFlex="92">
[fxShow]="!disabled && !userMode" <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
type="button" <label fxFlex translate class="tb-title no-padding">filter.operation.operation</label>
(click)="removePredicate($index)" <label *ngIf="valueType === valueTypeEnum.STRING"
matTooltip="{{ 'filter.remove-filter' | translate }}" translate class="tb-title no-padding" style="min-width: 70px;">filter.ignore-case</label>
matTooltipPosition="above"> </div>
<mat-icon>close</mat-icon> <label fxFlex translate class="tb-title no-padding">filter.value</label>
</button> <span [fxShow]="!disabled && !userMode" style="min-width: 40px;">&nbsp;</span>
</div> </div>
<span [fxShow]="!predicatesFormArray().length" </div>
fxLayoutAlign="center center" [ngClass]="{'disabled': disabled}" <mat-divider></mat-divider>
class="no-data-found" translate>filter.no-filters</span> <div class="predicate-list">
<div style="margin-top: 8px;" fxLayout="row" fxLayoutGap="8px"> <div fxLayout="row" fxLayoutAlign="start center" style="height: 40px;"
<button mat-button mat-raised-button color="primary" formArrayName="predicates"
[fxShow]="!disabled && !userMode" *ngFor="let predicateControl of predicatesFormArray().controls; let $index = index">
(click)="addPredicate(false)" <div fxFlex="8" fxLayout="row" fxLayoutAlign="center" class="filters-operation">
type="button" <span *ngIf="$index > 0">{{ complexOperationTranslations.get(operation) | translate }}</span>
matTooltip="{{ 'filter.add-filter' | translate }}" </div>
matTooltipPosition="above"> <div fxLayout="column" fxFlex="92">
{{ 'action.add' | translate }} <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" fxFlex>
</button> <tb-filter-predicate
<button mat-button mat-raised-button color="primary" fxFlex
[fxShow]="!disabled && !userMode" [userMode]="userMode"
(click)="addPredicate(true)" [valueType]="valueType"
type="button" [formControl]="predicateControl">
matTooltip="{{ 'filter.add-complex-filter' | translate }}" </tb-filter-predicate>
matTooltipPosition="above"> <button mat-icon-button color="primary"
{{ 'filter.add-complex' | translate }} [fxShow]="!disabled && !userMode"
</button> type="button"
</div> (click)="removePredicate($index)"
matTooltip="{{ 'filter.remove-filter' | translate }}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
</div>
<span [fxShow]="!predicatesFormArray().length"
fxLayoutAlign="center center" [ngClass]="{'disabled': disabled}"
class="no-data-found" translate>filter.no-filters</span>
</div>
<div style="margin-top: 16px;" fxLayout="row" fxLayoutGap="8px">
<button mat-button mat-raised-button color="primary"
[fxShow]="!disabled && !userMode"
(click)="addPredicate(false)"
type="button"
matTooltip="{{ 'filter.add-filter' | translate }}"
matTooltipPosition="above">
{{ 'action.add' | translate }}
</button>
<button mat-button mat-raised-button color="primary"
[fxShow]="!disabled && !userMode"
(click)="addPredicate(true)"
type="button"
matTooltip="{{ 'filter.add-complex-filter' | translate }}"
matTooltipPosition="above">
{{ 'filter.add-complex' | translate }}
</button>
</div>
</mat-expansion-panel>
</section> </section>

29
ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.scss

@ -0,0 +1,29 @@
/**
* 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.
*/
:host {
.predicate-list {
overflow: auto;
max-height: 350px;
.no-data-found {
height: 50px;
}
}
.filters-operation {
margin-top: -40px;
color: #666;
font-weight: 500;
}
}

12
ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.ts

@ -27,6 +27,7 @@ import {
import { Observable, of, Subscription } from 'rxjs'; import { Observable, of, Subscription } from 'rxjs';
import { import {
ComplexFilterPredicate, ComplexFilterPredicate,
ComplexOperation, complexOperationTranslationMap,
createDefaultFilterPredicate, createDefaultFilterPredicate,
EntityKeyValueType, EntityKeyValueType,
KeyFilterPredicate KeyFilterPredicate
@ -40,7 +41,7 @@ import { MatDialog } from '@angular/material/dialog';
@Component({ @Component({
selector: 'tb-filter-predicate-list', selector: 'tb-filter-predicate-list',
templateUrl: './filter-predicate-list.component.html', templateUrl: './filter-predicate-list.component.html',
styleUrls: [], styleUrls: ['./filter-predicate-list.component.scss'],
providers: [ providers: [
{ {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
@ -57,8 +58,14 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni
@Input() valueType: EntityKeyValueType; @Input() valueType: EntityKeyValueType;
@Input() operation: ComplexOperation = ComplexOperation.AND;
filterListFormGroup: FormGroup; filterListFormGroup: FormGroup;
valueTypeEnum = EntityKeyValueType;
complexOperationTranslations = complexOperationTranslationMap;
private propagateChange = null; private propagateChange = null;
private valueChangeSubscription: Subscription = null; private valueChangeSubscription: Subscription = null;
@ -143,7 +150,8 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni
complexPredicate: predicate, complexPredicate: predicate,
disabled: this.disabled, disabled: this.disabled,
userMode: this.userMode, userMode: this.userMode,
valueType: this.valueType valueType: this.valueType,
isAdd: true
} }
}).afterClosed(); }).afterClosed();
} }

29
ui-ngx/src/app/modules/home/components/filter/filter-predicate.scss

@ -0,0 +1,29 @@
/**
* 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.
*/
:host ::ng-deep {
mat-form-field {
.mat-form-field-wrapper {
padding-bottom: 0;
.mat-form-field-infix {
border-top-width: 0.2em;
width: auto;
}
.mat-form-field-underline {
bottom: 0;
}
}
}
}

23
ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.html

@ -32,8 +32,11 @@
<div class="tb-filters-header" fxLayout="row" fxLayoutAlign="start center"> <div class="tb-filters-header" fxLayout="row" fxLayoutAlign="start center">
<span fxFlex="5"></span> <span fxFlex="5"></span>
<div fxFlex="95" fxLayout="row" fxLayoutAlign="start center"> <div fxFlex="95" fxLayout="row" fxLayoutAlign="start center">
<span class="tb-header-label" translate fxFlex>filter.filter</span> <div class="tb-header-label" translate fxFlex>filter.filter</div>
<span style="min-width: 80px;"></span> <div class="tb-header-label" translate fxFlex="120px"
fxLayout="column" fxLayoutAlign="center center"
style="padding-left: 30px;">filter.editable</div>
<div style="min-width: 80px;"></div>
</div> </div>
</div> </div>
<fieldset [disabled]="isLoading$ | async"> <fieldset [disabled]="isLoading$ | async">
@ -43,13 +46,15 @@
*ngFor="let filterControl of filtersFormArray().controls; let $index = index"> *ngFor="let filterControl of filtersFormArray().controls; let $index = index">
<span fxFlex="5">{{$index + 1}}.</span> <span fxFlex="5">{{$index + 1}}.</span>
<div class="mat-elevation-z4 tb-filter" fxFlex="95" fxLayout="row" fxLayoutAlign="start center"> <div class="mat-elevation-z4 tb-filter" fxFlex="95" fxLayout="row" fxLayoutAlign="start center">
<mat-form-field floatLabel="always" hideRequiredMarker class="mat-block" fxFlex> <mat-label fxFlex>{{filterControl.get('filter').value}}</mat-label>
<mat-label></mat-label> <section fxFlex="120px" style="padding-left: 10px;"
<input matInput [formControl]="filterControl.get('filter')" required placeholder="{{ 'filter.filter' | translate }}"> class="tb-editable-switch"
<mat-error *ngIf="filterControl.get('filter').hasError('required')"> fxLayout="column"
{{ 'filter.filter-required' | translate }} fxLayoutAlign="center center">
</mat-error> <mat-slide-toggle class="editable-switch"
</mat-form-field> [formControl]="filterControl.get('editable')">
</mat-slide-toggle>
</section>
<button [disabled]="isLoading$ | async" <button [disabled]="isLoading$ | async"
mat-icon-button color="primary" mat-icon-button color="primary"
style="min-width: 40px;" style="min-width: 40px;"

8
ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.scss

@ -33,6 +33,14 @@
.tb-filter { .tb-filter {
padding: 0 0 0 10px; padding: 0 0 0 10px;
margin: 5px; margin: 5px;
.tb-editable-switch {
padding-left: 10px;
.editable-switch {
margin: 0;
}
}
} }
} }

20
ui-ngx/src/app/modules/home/components/filter/filters-dialog.component.ts

@ -36,7 +36,7 @@ import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { ActionNotificationShow } from '@core/notification/notification.actions'; import { ActionNotificationShow } from '@core/notification/notification.actions';
import { DialogService } from '@core/services/dialog.service'; import { DialogService } from '@core/services/dialog.service';
import { deepClone } from '@core/utils'; import { deepClone, isUndefined } from '@core/utils';
import { Filter, Filters, KeyFilterInfo } from '@shared/models/query/query.models'; import { Filter, Filters, KeyFilterInfo } from '@shared/models/query/query.models';
import { FilterDialogComponent, FilterDialogData } from '@home/components/filter/filter-dialog.component'; import { FilterDialogComponent, FilterDialogData } from '@home/components/filter/filter-dialog.component';
@ -109,6 +109,9 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone
const filterControls: Array<AbstractControl> = []; const filterControls: Array<AbstractControl> = [];
for (const filterId of Object.keys(this.data.filters)) { for (const filterId of Object.keys(this.data.filters)) {
const filter = this.data.filters[filterId]; const filter = this.data.filters[filterId];
if (isUndefined(filter.editable)) {
filter.editable = true;
}
filterControls.push(this.createFilterFormControl(filterId, filter)); filterControls.push(this.createFilterFormControl(filterId, filter));
} }
@ -121,7 +124,8 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone
const filterFormControl = this.fb.group({ const filterFormControl = this.fb.group({
id: [filterId], id: [filterId],
filter: [filter ? filter.filter : null, [Validators.required]], filter: [filter ? filter.filter : null, [Validators.required]],
keyFilters: [filter ? filter.keyFilters : [], [Validators.required]] keyFilters: [filter ? filter.keyFilters : [], [Validators.required]],
editable: [filter ? filter.editable : true]
}); });
return filterFormControl; return filterFormControl;
} }
@ -148,9 +152,9 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone
for (const widgetTitle of widgetsTitleList) { for (const widgetTitle of widgetsTitleList) {
widgetsListHtml += '<br/>\'' + widgetTitle + '\''; widgetsListHtml += '<br/>\'' + widgetTitle + '\'';
} }
const message = this.translate.instant('entity.unable-delete-filter-text', const message = this.translate.instant('filter.unable-delete-filter-text',
{filter: filter.filter, widgetsList: widgetsListHtml}); {filter: filter.filter, widgetsList: widgetsListHtml});
this.dialogs.alert(this.translate.instant('entity.unable-delete-filter-title'), this.dialogs.alert(this.translate.instant('filter.unable-delete-filter-title'),
message, this.translate.instant('action.close'), true); message, this.translate.instant('action.close'), true);
} else { } else {
(this.filtersFormGroup.get('filters') as FormArray).removeAt(index); (this.filtersFormGroup.get('filters') as FormArray).removeAt(index);
@ -190,8 +194,9 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone
.push(this.createFilterFormControl(result.id, result)); .push(this.createFilterFormControl(result.id, result));
} else { } else {
const filterFormControl = (this.filtersFormGroup.get('filters') as FormArray).at(index); const filterFormControl = (this.filtersFormGroup.get('filters') as FormArray).at(index);
filterFormControl.get('filter').patchValue(filter.filter); filterFormControl.get('filter').patchValue(result.filter);
filterFormControl.get('keyFilters').patchValue(filter.keyFilters); filterFormControl.get('editable').patchValue(result.editable);
filterFormControl.get('keyFilters').patchValue(result.keyFilters);
} }
this.filtersFormGroup.markAsDirty(); this.filtersFormGroup.markAsDirty();
} }
@ -215,6 +220,7 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone
const filterId: string = filterValue.id; const filterId: string = filterValue.id;
const filter: string = filterValue.filter; const filter: string = filterValue.filter;
const keyFilters: Array<KeyFilterInfo> = filterValue.keyFilters; const keyFilters: Array<KeyFilterInfo> = filterValue.keyFilters;
const editable: boolean = filterValue.editable;
if (uniqueFilterList[filter]) { if (uniqueFilterList[filter]) {
valid = false; valid = false;
message = this.translate.instant('filter.duplicate-filter-error', {filter}); message = this.translate.instant('filter.duplicate-filter-error', {filter});
@ -225,7 +231,7 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone
break; break;
} else { } else {
uniqueFilterList[filter] = filter; uniqueFilterList[filter] = filter;
filters[filterId] = {id: filterId, filter, keyFilters}; filters[filterId] = {id: filterId, filter, keyFilters, editable};
} }
} }
if (valid) { if (valid) {

33
ui-ngx/src/app/modules/home/components/filter/filters-edit-panel.component.html

@ -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.
-->
<div fxLayout="column" class="mat-content mat-padding">
<div fxLayout="column" *ngFor="let filter of filtersInfo | keyvalue">
<div fxFlex fxLayout="row" fxLayoutAlign="start center">
<mat-label fxFlex>{{filter.value.filter}}</mat-label>
<button mat-icon-button color="primary"
style="min-width: 40px;"
type="button"
(click)="editFilter(filter.key, filter.value)"
matTooltip="{{ 'filter.edit' | translate }}"
matTooltipPosition="above">
<mat-icon>edit</mat-icon>
</button>
</div>
<mat-divider></mat-divider>
</div>
</div>

35
ui-ngx/src/app/modules/home/components/filter/filters-edit-panel.component.scss

@ -0,0 +1,35 @@
/**
* 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.
*/
:host {
min-width: 300px;
max-height: 150px;
overflow-x: hidden;
overflow-y: auto;
background: #fff;
border-radius: 4px;
box-shadow:
0 7px 8px -4px rgba(0, 0, 0, .2),
0 13px 19px 2px rgba(0, 0, 0, .14),
0 5px 24px 4px rgba(0, 0, 0, .12);
@media (min-height: 350px) {
max-height: 250px;
}
.mat-content {
background-color: #fff;
}
}

65
ui-ngx/src/app/modules/home/components/filter/filters-edit-panel.component.ts

@ -0,0 +1,65 @@
///
/// 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, Inject, InjectionToken } from '@angular/core';
import { IAliasController } from '@core/api/widget-api.models';
import { Filter, FilterInfo } from '@shared/models/query/query.models';
import { MatDialog } from '@angular/material/dialog';
import { FilterDialogComponent, FilterDialogData } from '@home/components/filter/filter-dialog.component';
import { deepClone } from '@core/utils';
export const FILTER_EDIT_PANEL_DATA = new InjectionToken<any>('FiltersEditPanelData');
export interface FiltersEditPanelData {
aliasController: IAliasController;
filtersInfo: {[filterId: string]: FilterInfo};
}
@Component({
selector: 'tb-filters-edit-panel',
templateUrl: './filters-edit-panel.component.html',
styleUrls: ['./filters-edit-panel.component.scss']
})
export class FiltersEditPanelComponent {
filtersInfo: {[filterId: string]: FilterInfo};
constructor(@Inject(FILTER_EDIT_PANEL_DATA) public data: FiltersEditPanelData,
private dialog: MatDialog) {
this.filtersInfo = this.data.filtersInfo;
}
public editFilter(filterId: string, filter: FilterInfo) {
const singleFilter: Filter = {id: filterId, ...deepClone(filter)};
this.dialog.open<FilterDialogComponent, FilterDialogData,
Filter>(FilterDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
isAdd: false,
filters: [],
filter: singleFilter,
userMode: true
}
}).afterClosed().subscribe(
(result) => {
if (result) {
this.filtersInfo[result.id] = result;
this.data.aliasController.updateUserFilter(result);
}
});
}
}

32
ui-ngx/src/app/modules/home/components/filter/filters-edit.component.html

@ -0,0 +1,32 @@
<!--
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.
-->
<section class="tb-filters-edit" fxLayout="row" fxLayoutAlign="start center" *ngIf="hasEditableFilters">
<button mat-icon-button
cdkOverlayOrigin #filtersEditPanelOrigin="cdkOverlayOrigin"
(click)="openEditMode()"
matTooltip="{{ 'filter.filters' | translate }}"
[matTooltipPosition]="tooltipPosition">
<mat-icon>filter_list</mat-icon>
</button>
<span fxHide.lt-lg
(click)="openEditMode()"
matTooltip="{{ 'filter.filters' | translate }}"
[matTooltipPosition]="tooltipPosition">
{{ 'filter.filters' | translate }}
</span>
</section>

36
ui-ngx/src/app/modules/home/components/filter/filters-edit.component.scss

@ -0,0 +1,36 @@
/**
* 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 "../../../../../scss/constants";
:host {
section.tb-filters-edit {
min-height: 32px;
padding: 0 6px;
@media #{$mat-lt-md} {
padding: 0;
}
span {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
pointer-events: all;
cursor: pointer;
}
}
}

154
ui-ngx/src/app/modules/home/components/filter/filters-edit.component.ts

@ -0,0 +1,154 @@
///
/// 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, Input, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
import { TooltipPosition } from '@angular/material/tooltip';
import { IAliasController } from '@core/api/widget-api.models';
import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs';
import { BreakpointObserver } from '@angular/cdk/layout';
import { deepClone } from '@core/utils';
import { FilterInfo } from '@shared/models/query/query.models';
import {
FILTER_EDIT_PANEL_DATA,
FiltersEditPanelComponent,
FiltersEditPanelData
} from '@home/components/filter/filters-edit-panel.component';
import { ComponentPortal, PortalInjector } from '@angular/cdk/portal';
@Component({
selector: 'tb-filters-edit',
templateUrl: './filters-edit.component.html',
styleUrls: ['./filters-edit.component.scss']
})
export class FiltersEditComponent implements OnInit, OnDestroy {
aliasControllerValue: IAliasController;
@Input()
set aliasController(aliasController: IAliasController) {
this.aliasControllerValue = aliasController;
this.setupAliasController(this.aliasControllerValue);
}
get aliasController(): IAliasController {
return this.aliasControllerValue;
}
@Input()
tooltipPosition: TooltipPosition = 'above';
@Input() disabled: boolean;
@ViewChild('filtersEditPanelOrigin') filtersEditPanelOrigin: CdkOverlayOrigin;
displayValue: string;
filtersInfo: {[filterId: string]: FilterInfo} = {};
hasEditableFilters = false;
private rxSubscriptions = new Array<Subscription>();
constructor(private translate: TranslateService,
private overlay: Overlay,
private breakpointObserver: BreakpointObserver,
private viewContainerRef: ViewContainerRef) {
}
private setupAliasController(aliasController: IAliasController) {
this.rxSubscriptions.forEach((subscription) => {
subscription.unsubscribe();
});
this.rxSubscriptions.length = 0;
if (aliasController) {
this.rxSubscriptions.push(aliasController.filtersChanged.subscribe(
() => {
setTimeout(() => {
this.updateFiltersInfo();
}, 0);
}
));
setTimeout(() => {
this.updateFiltersInfo();
}, 0);
}
}
ngOnInit(): void {
}
ngOnDestroy(): void {
this.rxSubscriptions.forEach((subscription) => {
subscription.unsubscribe();
});
this.rxSubscriptions.length = 0;
}
openEditMode() {
if (this.disabled || !this.hasEditableFilters) {
return;
}
const position = this.overlay.position();
const config = new OverlayConfig({
panelClass: 'tb-filters-edit-panel',
backdropClass: 'cdk-overlay-transparent-backdrop',
hasBackdrop: true,
});
const connectedPosition: ConnectedPosition = {
originX: 'start',
originY: 'bottom',
overlayX: 'start',
overlayY: 'top'
};
config.positionStrategy = position.flexibleConnectedTo(this.filtersEditPanelOrigin.elementRef)
.withPositions([connectedPosition]);
const overlayRef = this.overlay.create(config);
overlayRef.backdropClick().subscribe(() => {
overlayRef.dispose();
});
const injector = this._createFiltersEditPanelInjector(
overlayRef,
{
aliasController: this.aliasController,
filtersInfo: deepClone(this.filtersInfo)
}
);
overlayRef.attach(new ComponentPortal(FiltersEditPanelComponent, this.viewContainerRef, injector));
}
private _createFiltersEditPanelInjector(overlayRef: OverlayRef, data: FiltersEditPanelData): PortalInjector {
const injectionTokens = new WeakMap<any, any>([
[FILTER_EDIT_PANEL_DATA, data],
[OverlayRef, overlayRef]
]);
return new PortalInjector(this.viewContainerRef.injector, injectionTokens);
}
private updateFiltersInfo() {
const allFilters = this.aliasController.getFilters();
this.filtersInfo = {};
this.hasEditableFilters = false;
for (const filterId of Object.keys(allFilters)) {
const filterInfo = this.aliasController.getFilterInfo(filterId);
if (filterInfo && filterInfo.editable) {
this.filtersInfo[filterId] = deepClone(filterInfo);
this.hasEditableFilters = true;
}
}
}
}

20
ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html

@ -15,9 +15,9 @@
limitations under the License. limitations under the License.
--> -->
<form [formGroup]="keyFilterFormGroup" (ngSubmit)="save()"> <form [formGroup]="keyFilterFormGroup" (ngSubmit)="save()" style="width: 700px;">
<mat-toolbar color="primary"> <mat-toolbar color="primary">
<h2 translate>{{data.isAdd ? 'filter.add-key-filter' : 'filter.edit-key-filter'}}</h2> <h2>{{data.userMode ? data.keyFilter.key.key : ((data.isAdd ? 'filter.add-key-filter' : 'filter.edit-key-filter') | translate)}}</h2>
<span fxFlex></span> <span fxFlex></span>
<button mat-icon-button <button mat-icon-button
(click)="cancel()" (click)="cancel()"
@ -27,16 +27,16 @@
</mat-toolbar> </mat-toolbar>
<div mat-dialog-content> <div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async" fxLayout="column"> <fieldset [disabled]="isLoading$ | async" fxLayout="column">
<section fxLayout="row" fxLayoutGap="8px"> <section fxLayout="row" fxLayoutGap="8px" class="entity-key" [fxShow]="!data.userMode">
<section fxFlex="60" fxLayout="row" formGroupName="key" fxLayoutGap="8px"> <section fxFlex="70" fxLayout="row" formGroupName="key" fxLayoutGap="8px">
<mat-form-field fxFlex="40" class="mat-block"> <mat-form-field fxFlex="60" class="mat-block">
<mat-label translate>filter.key-name</mat-label> <mat-label translate>filter.key-name</mat-label>
<input matInput required formControlName="key"> <input matInput required formControlName="key">
<mat-error *ngIf="keyFilterFormGroup.get('key.key').hasError('required')"> <mat-error *ngIf="keyFilterFormGroup.get('key.key').hasError('required')">
{{ 'filter.key-name-required' | translate }} {{ 'filter.key-name-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field fxFlex="60" class="mat-block"> <mat-form-field fxFlex="40" class="mat-block">
<mat-label translate>filter.key-type.key-type</mat-label> <mat-label translate>filter.key-type.key-type</mat-label>
<mat-select required formControlName="type"> <mat-select required formControlName="type">
<mat-option *ngFor="let type of entityKeyTypes" [value]="type"> <mat-option *ngFor="let type of entityKeyTypes" [value]="type">
@ -45,15 +45,15 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</section> </section>
<mat-form-field fxFlex="40" class="mat-block"> <mat-form-field fxFlex="30" class="mat-block">
<mat-label translate>filter.value-type.value-type</mat-label> <mat-label translate>filter.value-type.value-type</mat-label>
<mat-select matInput formControlName="valueType"> <mat-select matInput formControlName="valueType">
<mat-select-trigger> <mat-select-trigger>
<mat-icon svgIcon="{{ entityKeyValueTypes.get(keyFilterFormGroup.get('valueType').value)?.icon }}"></mat-icon> <mat-icon class="tb-mat-18" svgIcon="{{ entityKeyValueTypes.get(keyFilterFormGroup.get('valueType').value)?.icon }}"></mat-icon>
<span>{{ entityKeyValueTypes.get(keyFilterFormGroup.get('valueType').value)?.name | translate }}</span> <span>{{ entityKeyValueTypes.get(keyFilterFormGroup.get('valueType').value)?.name | translate }}</span>
</mat-select-trigger> </mat-select-trigger>
<mat-option *ngFor="let valueType of entityKeyValueTypesKeys" [value]="valueType"> <mat-option *ngFor="let valueType of entityKeyValueTypesKeys" [value]="valueType">
<mat-icon svgIcon="{{ entityKeyValueTypes.get(entityKeyValueTypeEnum[valueType]).icon }}"></mat-icon> <mat-icon class="tb-mat-18" svgIcon="{{ entityKeyValueTypes.get(entityKeyValueTypeEnum[valueType]).icon }}"></mat-icon>
<span>{{ entityKeyValueTypes.get(entityKeyValueTypeEnum[valueType]).name | translate }}</span> <span>{{ entityKeyValueTypes.get(entityKeyValueTypeEnum[valueType]).name | translate }}</span>
</mat-option> </mat-option>
</mat-select> </mat-select>
@ -72,7 +72,7 @@
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
type="submit" type="submit"
[disabled]="(isLoading$ | async) || keyFilterFormGroup.invalid"> [disabled]="(isLoading$ | async) || keyFilterFormGroup.invalid || !keyFilterFormGroup.dirty">
{{ (data.isAdd ? 'action.add' : 'action.update') | translate }} {{ (data.isAdd ? 'action.add' : 'action.update') | translate }}
</button> </button>
<button mat-button color="primary" <button mat-button color="primary"

26
ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.scss

@ -0,0 +1,26 @@
/**
* 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.
*/
:host ::ng-deep {
.entity-key {
mat-form-field {
.mat-form-field-wrapper {
.mat-form-field-infix {
width: auto;
}
}
}
}
}

24
ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts

@ -27,8 +27,10 @@ import {
entityKeyTypeTranslationMap, entityKeyTypeTranslationMap,
EntityKeyValueType, EntityKeyValueType,
entityKeyValueTypesMap, entityKeyValueTypesMap,
KeyFilterInfo KeyFilterInfo, KeyFilterPredicate
} from '@shared/models/query/query.models'; } from '@shared/models/query/query.models';
import { DialogService } from '@core/services/dialog.service';
import { TranslateService } from '@ngx-translate/core';
export interface KeyFilterDialogData { export interface KeyFilterDialogData {
keyFilter: KeyFilterInfo; keyFilter: KeyFilterInfo;
@ -40,7 +42,7 @@ export interface KeyFilterDialogData {
selector: 'tb-key-filter-dialog', selector: 'tb-key-filter-dialog',
templateUrl: './key-filter-dialog.component.html', templateUrl: './key-filter-dialog.component.html',
providers: [{provide: ErrorStateMatcher, useExisting: KeyFilterDialogComponent}], providers: [{provide: ErrorStateMatcher, useExisting: KeyFilterDialogComponent}],
styleUrls: [] styleUrls: ['./key-filter-dialog.component.scss']
}) })
export class KeyFilterDialogComponent extends export class KeyFilterDialogComponent extends
DialogComponent<KeyFilterDialogComponent, KeyFilterInfo> DialogComponent<KeyFilterDialogComponent, KeyFilterInfo>
@ -65,6 +67,8 @@ export class KeyFilterDialogComponent extends
@Inject(MAT_DIALOG_DATA) public data: KeyFilterDialogData, @Inject(MAT_DIALOG_DATA) public data: KeyFilterDialogData,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<KeyFilterDialogComponent, KeyFilterInfo>, public dialogRef: MatDialogRef<KeyFilterDialogComponent, KeyFilterInfo>,
private dialogs: DialogService,
private translate: TranslateService,
private fb: FormBuilder) { private fb: FormBuilder) {
super(store, router, dialogRef); super(store, router, dialogRef);
@ -80,6 +84,22 @@ export class KeyFilterDialogComponent extends
predicates: [this.data.keyFilter.predicates, [Validators.required]] predicates: [this.data.keyFilter.predicates, [Validators.required]]
} }
); );
this.keyFilterFormGroup.get('valueType').valueChanges.subscribe((valueType: EntityKeyValueType) => {
const prevValue: EntityKeyValueType = this.keyFilterFormGroup.value.valueType;
const predicates: KeyFilterPredicate[] = this.keyFilterFormGroup.get('predicates').value;
if (prevValue && prevValue !== valueType && predicates && predicates.length) {
this.dialogs.confirm(this.translate.instant('filter.key-value-type-change-title'),
this.translate.instant('filter.key-value-type-change-message')).subscribe(
(result) => {
if (result) {
this.keyFilterFormGroup.get('predicates').setValue([]);
} else {
this.keyFilterFormGroup.get('valueType').setValue(prevValue, {emitEvent: false});
}
}
);
}
});
} }
ngOnInit(): void { ngOnInit(): void {

95
ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.html

@ -16,38 +16,65 @@
--> -->
<section fxLayout="column" [formGroup]="keyFilterListFormGroup"> <section fxLayout="column" [formGroup]="keyFilterListFormGroup">
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" style="max-height: 40px;" <mat-expansion-panel [expanded]="true">
formArrayName="keyFilters" <mat-expansion-panel-header>
*ngFor="let keyFilterControl of keyFiltersFormArray().controls; let $index = index"> <mat-panel-title>
<span>{{ keyFilterControl.value.key.key }}</span> <div translate>filter.key-filters</div>
<span>{{ keyFilterControl.value.key.type }}</span> </mat-panel-title>
<button mat-icon-button color="primary" </mat-expansion-panel-header>
type="button" <div fxLayout="row">
(click)="editKeyFilter($index)" <span fxFlex="8"></span>
matTooltip="{{ 'filter.edit-key-filter' | translate }}" <div fxLayout="row" fxLayoutAlign="start center" fxFlex="92">
matTooltipPosition="above"> <label fxFlex translate class="tb-title no-padding">filter.key-name</label>
<mat-icon>edit</mat-icon> <label fxFlex translate class="tb-title no-padding">filter.key-type.key-type</label>
</button> <span [fxShow]="!disabled && !userMode" style="min-width: 80px;">&nbsp;</span>
<button mat-icon-button color="primary" <span [fxShow]="disabled || userMode" style="min-width: 40px;">&nbsp;</span>
[fxShow]="!disabled && !userMode" </div>
type="button" </div>
(click)="removeKeyFilter($index)" <mat-divider></mat-divider>
matTooltip="{{ 'filter.remove-key-filter' | translate }}" <div class="key-filter-list">
matTooltipPosition="above"> <div fxLayout="row" fxLayoutAlign="start center" style="max-height: 40px;"
<mat-icon>close</mat-icon> formArrayName="keyFilters"
</button> *ngFor="let keyFilterControl of keyFiltersFormArray().controls; let $index = index">
</div> <div fxFlex="8" class="filters-operation">
<span [fxShow]="!keyFiltersFormArray().length" <span *ngIf="$index > 0" translate>filter.operation.and</span>
fxLayoutAlign="center center" [ngClass]="{'disabled': disabled}" </div>
class="no-data-found" translate>filter.no-key-filters</span> <div fxLayout="column" fxFlex="92">
<div style="margin-top: 8px;"> <div fxLayout="row" fxLayoutAlign="start center" fxFlex>
<button mat-button mat-raised-button color="primary" <div fxFlex>{{ keyFilterControl.value.key.key }}</div>
[fxShow]="!disabled && !userMode" <div fxFlex translate>{{ entityKeyTypeTranslations.get(keyFilterControl.value.key.type) }}</div>
(click)="addKeyFilter()" <button mat-icon-button color="primary"
type="button" type="button"
matTooltip="{{ 'filter.add-key-filter' | translate }}" (click)="editKeyFilter($index)"
matTooltipPosition="above"> matTooltip="{{ 'filter.edit-key-filter' | translate }}"
{{ 'filter.add-key-filter' | translate }} matTooltipPosition="above">
</button> <mat-icon>edit</mat-icon>
</div> </button>
<button mat-icon-button color="primary"
[fxShow]="!disabled && !userMode"
type="button"
(click)="removeKeyFilter($index)"
matTooltip="{{ 'filter.remove-key-filter' | translate }}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</div>
<mat-divider></mat-divider>
</div>
</div>
<span [fxShow]="!keyFiltersFormArray().length"
fxLayoutAlign="center center" [ngClass]="{'disabled': disabled}"
class="no-data-found" translate>filter.no-key-filters</span>
</div>
<div style="margin-top: 16px;">
<button mat-button mat-raised-button color="primary"
[fxShow]="!disabled && !userMode"
(click)="addKeyFilter()"
type="button"
matTooltip="{{ 'filter.add-key-filter' | translate }}"
matTooltipPosition="above">
{{ 'filter.add-key-filter' | translate }}
</button>
</div>
</mat-expansion-panel>
</section> </section>

29
ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.scss

@ -0,0 +1,29 @@
/**
* 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.
*/
:host {
.key-filter-list {
overflow: auto;
max-height: 300px;
.no-data-found {
height: 50px;
}
}
.filters-operation {
margin-top: -40px;
color: #666;
font-weight: 500;
}
}

6
ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.ts

@ -25,7 +25,7 @@ import {
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import { EntityKeyType, KeyFilterInfo } from '@shared/models/query/query.models'; import { EntityKeyType, entityKeyTypeTranslationMap, KeyFilterInfo } from '@shared/models/query/query.models';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { deepClone } from '@core/utils'; import { deepClone } from '@core/utils';
import { KeyFilterDialogComponent, KeyFilterDialogData } from '@home/components/filter/key-filter-dialog.component'; import { KeyFilterDialogComponent, KeyFilterDialogData } from '@home/components/filter/key-filter-dialog.component';
@ -33,7 +33,7 @@ import { KeyFilterDialogComponent, KeyFilterDialogData } from '@home/components/
@Component({ @Component({
selector: 'tb-key-filter-list', selector: 'tb-key-filter-list',
templateUrl: './key-filter-list.component.html', templateUrl: './key-filter-list.component.html',
styleUrls: [], styleUrls: ['./key-filter-list.component.scss'],
providers: [ providers: [
{ {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
@ -50,6 +50,8 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit {
keyFilterListFormGroup: FormGroup; keyFilterListFormGroup: FormGroup;
entityKeyTypeTranslations = entityKeyTypeTranslationMap;
private propagateChange = null; private propagateChange = null;
private valueChangeSubscription: Subscription = null; private valueChangeSubscription: Subscription = null;

15
ui-ngx/src/app/modules/home/components/filter/numeric-filter-predicate.component.html

@ -15,17 +15,18 @@
limitations under the License. limitations under the License.
--> -->
<div fxLayout="row" [formGroup]="numericFilterPredicateFormGroup"> <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="numericFilterPredicateFormGroup">
<mat-form-field class="mat-block"> <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
<mat-label translate>filter.operation.operation</mat-label> <mat-label></mat-label>
<mat-select required formControlName="operation"> <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
<mat-option *ngFor="let operation of numericOperations" [value]="operation"> <mat-option *ngFor="let operation of numericOperations" [value]="operation">
{{numericOperationTranslations.get(numericOperationEnum[operation]) | translate}} {{numericOperationTranslations.get(numericOperationEnum[operation]) | translate}}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
<mat-label translate>filter.value</mat-label> <mat-label></mat-label>
<input required type="number" matInput formControlName="value"> <input required type="number" matInput formControlName="value"
placeholder="{{'filter.value' | translate}}">
</mat-form-field> </mat-form-field>
</div> </div>

2
ui-ngx/src/app/modules/home/components/filter/numeric-filter-predicate.component.ts

@ -24,7 +24,7 @@ import { isDefined } from '@core/utils';
@Component({ @Component({
selector: 'tb-numeric-filter-predicate', selector: 'tb-numeric-filter-predicate',
templateUrl: './numeric-filter-predicate.component.html', templateUrl: './numeric-filter-predicate.component.html',
styleUrls: [], styleUrls: ['./filter-predicate.scss'],
providers: [ providers: [
{ {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,

31
ui-ngx/src/app/modules/home/components/filter/string-filter-predicate.component.html

@ -15,20 +15,21 @@
limitations under the License. limitations under the License.
--> -->
<div fxLayout="row" [formGroup]="stringFilterPredicateFormGroup"> <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="stringFilterPredicateFormGroup">
<mat-form-field class="mat-block"> <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-label translate>filter.operation.operation</mat-label> <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
<mat-select required formControlName="operation"> <mat-label></mat-label>
<mat-option *ngFor="let operation of stringOperations" [value]="operation"> <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
{{stringOperationTranslations.get(stringOperationEnum[operation]) | translate}} <mat-option *ngFor="let operation of stringOperations" [value]="operation">
</mat-option> {{stringOperationTranslations.get(stringOperationEnum[operation]) | translate}}
</mat-select> </mat-option>
</mat-form-field> </mat-select>
<mat-checkbox formControlName="ignoreCase"> </mat-form-field>
{{ 'filter.ignore-case' | translate }} <mat-checkbox fxLayout="row" fxLayoutAlign="center" formControlName="ignoreCase" style="min-width: 70px;">
</mat-checkbox> </mat-checkbox>
<mat-form-field class="mat-block"> </div>
<mat-label translate>filter.value</mat-label> <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
<input matInput formControlName="value"> <mat-label></mat-label>
<input matInput formControlName="value" placeholder="{{'filter.value' | translate}}">
</mat-form-field> </mat-form-field>
</div> </div>

2
ui-ngx/src/app/modules/home/components/filter/string-filter-predicate.component.ts

@ -26,7 +26,7 @@ import {
@Component({ @Component({
selector: 'tb-string-filter-predicate', selector: 'tb-string-filter-predicate',
templateUrl: './string-filter-predicate.component.html', templateUrl: './string-filter-predicate.component.html',
styleUrls: [], styleUrls: ['./filter-predicate.scss'],
providers: [ providers: [
{ {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,

9
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -78,6 +78,8 @@ import { KeyFilterDialogComponent } from '@home/components/filter/key-filter-dia
import { FiltersDialogComponent } from '@home/components/filter/filters-dialog.component'; import { FiltersDialogComponent } from '@home/components/filter/filters-dialog.component';
import { FilterDialogComponent } from '@home/components/filter/filter-dialog.component'; import { FilterDialogComponent } from '@home/components/filter/filter-dialog.component';
import { FilterSelectComponent } from './filter/filter-select.component'; import { FilterSelectComponent } from './filter/filter-select.component';
import { FiltersEditComponent } from '@home/components/filter/filters-edit.component';
import { FiltersEditPanelComponent } from '@home/components/filter/filters-edit-panel.component';
@NgModule({ @NgModule({
declarations: declarations:
@ -138,7 +140,9 @@ import { FilterSelectComponent } from './filter/filter-select.component';
KeyFilterDialogComponent, KeyFilterDialogComponent,
FilterDialogComponent, FilterDialogComponent,
FiltersDialogComponent, FiltersDialogComponent,
FilterSelectComponent FilterSelectComponent,
FiltersEditComponent,
FiltersEditPanelComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -192,7 +196,8 @@ import { FilterSelectComponent } from './filter/filter-select.component';
KeyFilterDialogComponent, KeyFilterDialogComponent,
FilterDialogComponent, FilterDialogComponent,
FiltersDialogComponent, FiltersDialogComponent,
FilterSelectComponent FilterSelectComponent,
FiltersEditComponent
], ],
providers: [ providers: [
WidgetComponentService, WidgetComponentService,

2
ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts

@ -721,7 +721,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
} }
private createFilter(filter: string): Observable<Filter> { private createFilter(filter: string): Observable<Filter> {
const singleFilter: Filter = {id: null, filter, keyFilters: []}; const singleFilter: Filter = {id: null, filter, keyFilters: [], editable: true};
return this.dialog.open<FilterDialogComponent, FilterDialogData, return this.dialog.open<FilterDialogComponent, FilterDialogData,
Filter>(FilterDialogComponent, { Filter>(FilterDialogComponent, {
disableClose: true, disableClose: true,

4
ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html

@ -91,6 +91,10 @@
aggregation="true" aggregation="true"
[(ngModel)]="dashboardCtx.dashboardTimewindow"> [(ngModel)]="dashboardCtx.dashboardTimewindow">
</tb-timewindow> </tb-timewindow>
<tb-filters-edit [fxShow]="!isEdit && displayFilters()"
tooltipPosition="below"
[aliasController]="dashboardCtx.aliasController">
</tb-filters-edit>
<tb-aliases-entity-select [fxShow]="!isEdit && displayEntitiesSelect()" <tb-aliases-entity-select [fxShow]="!isEdit && displayEntitiesSelect()"
tooltipPosition="below" tooltipPosition="below"
[aliasController]="dashboardCtx.aliasController"> [aliasController]="dashboardCtx.aliasController">

9
ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts

@ -410,6 +410,15 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
} }
} }
public displayFilters(): boolean {
if (this.dashboard.configuration.settings &&
isDefined(this.dashboard.configuration.settings.showFilters)) {
return this.dashboard.configuration.settings.showFilters;
} else {
return true;
}
}
public showRightLayoutSwitch(): boolean { public showRightLayoutSwitch(): boolean {
return this.isMobile && this.layouts.right.show; return this.isMobile && this.layouts.right.show;
} }

3
ui-ngx/src/app/modules/home/pages/dashboard/dashboard-settings-dialog.component.html

@ -59,6 +59,9 @@
<mat-checkbox fxFlex formControlName="showEntitiesSelect"> <mat-checkbox fxFlex formControlName="showEntitiesSelect">
{{ 'dashboard.display-entities-selection' | translate }} {{ 'dashboard.display-entities-selection' | translate }}
</mat-checkbox> </mat-checkbox>
<mat-checkbox fxFlex formControlName="showFilters">
{{ 'dashboard.display-filters' | translate }}
</mat-checkbox>
<mat-checkbox fxFlex formControlName="showDashboardTimewindow"> <mat-checkbox fxFlex formControlName="showDashboardTimewindow">
{{ 'dashboard.display-dashboard-timewindow' | translate }} {{ 'dashboard.display-dashboard-timewindow' | translate }}
</mat-checkbox> </mat-checkbox>

1
ui-ngx/src/app/modules/home/pages/dashboard/dashboard-settings-dialog.component.ts

@ -78,6 +78,7 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
titleColor: [isUndefined(this.settings.titleColor) ? 'rgba(0,0,0,0.870588)' : this.settings.titleColor, []], titleColor: [isUndefined(this.settings.titleColor) ? 'rgba(0,0,0,0.870588)' : this.settings.titleColor, []],
showDashboardsSelect: [isUndefined(this.settings.showDashboardsSelect) ? true : this.settings.showDashboardsSelect, []], showDashboardsSelect: [isUndefined(this.settings.showDashboardsSelect) ? true : this.settings.showDashboardsSelect, []],
showEntitiesSelect: [isUndefined(this.settings.showEntitiesSelect) ? true : this.settings.showEntitiesSelect, []], showEntitiesSelect: [isUndefined(this.settings.showEntitiesSelect) ? true : this.settings.showEntitiesSelect, []],
showFilters: [isUndefined(this.settings.showFilters) ? true : this.settings.showFilters, []],
showDashboardTimewindow: [isUndefined(this.settings.showDashboardTimewindow) ? true : this.settings.showDashboardTimewindow, []], showDashboardTimewindow: [isUndefined(this.settings.showDashboardTimewindow) ? true : this.settings.showDashboardTimewindow, []],
showDashboardExport: [isUndefined(this.settings.showDashboardExport) ? true : this.settings.showDashboardExport, []] showDashboardExport: [isUndefined(this.settings.showDashboardExport) ? true : this.settings.showDashboardExport, []]
}); });

1
ui-ngx/src/app/shared/models/dashboard.models.ts

@ -85,6 +85,7 @@ export interface DashboardSettings {
showTitle?: boolean; showTitle?: boolean;
showDashboardsSelect?: boolean; showDashboardsSelect?: boolean;
showEntitiesSelect?: boolean; showEntitiesSelect?: boolean;
showFilters?: boolean;
showDashboardTimewindow?: boolean; showDashboardTimewindow?: boolean;
showDashboardExport?: boolean; showDashboardExport?: boolean;
toolbarAlwaysOpen?: boolean; toolbarAlwaysOpen?: boolean;

1
ui-ngx/src/app/shared/models/query/query.models.ts

@ -248,6 +248,7 @@ export interface KeyFilterInfo {
export interface FilterInfo { export interface FilterInfo {
filter: string; filter: string;
editable: boolean;
keyFilters: Array<KeyFilterInfo>; keyFilters: Array<KeyFilterInfo>;
} }

5
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -549,6 +549,7 @@
"title-color": "Title color", "title-color": "Title color",
"display-dashboards-selection": "Display dashboards selection", "display-dashboards-selection": "Display dashboards selection",
"display-entities-selection": "Display entities selection", "display-entities-selection": "Display entities selection",
"display-filters": "Display filters",
"display-dashboard-timewindow": "Display timewindow", "display-dashboard-timewindow": "Display timewindow",
"display-dashboard-export": "Display export", "display-dashboard-export": "Display export",
"import": "Import dashboard", "import": "Import dashboard",
@ -1166,6 +1167,7 @@
"duplicate-filter-error": "Duplicate filter found '{{filter}}'.<br>Filters must be unique within the dashboard.", "duplicate-filter-error": "Duplicate filter found '{{filter}}'.<br>Filters must be unique within the dashboard.",
"missing-key-filters-error": "Key filters is missing for filter '{{filter}}'.", "missing-key-filters-error": "Key filters is missing for filter '{{filter}}'.",
"filter": "Filter", "filter": "Filter",
"editable": "Editable",
"no-filters-found": "No filters found.", "no-filters-found": "No filters found.",
"no-filter-matching": "'{{filter}}' not found.", "no-filter-matching": "'{{filter}}' not found.",
"create-new-filter": "Create a new one!", "create-new-filter": "Create a new one!",
@ -1195,6 +1197,7 @@
"complex-filter": "Complex filter", "complex-filter": "Complex filter",
"edit-complex-filter": "Edit complex filter", "edit-complex-filter": "Edit complex filter",
"key-filter": "Key filter", "key-filter": "Key filter",
"key-filters": "Key filters",
"key-name": "Key name", "key-name": "Key name",
"key-name-required": "Key name is required.", "key-name-required": "Key name is required.",
"key-type": { "key-type": {
@ -1210,6 +1213,8 @@
"boolean": "Boolean" "boolean": "Boolean"
}, },
"value-type-required": "Key value type is required.", "value-type-required": "Key value type is required.",
"key-value-type-change-title": "Are you sure you want to change key value type?",
"key-value-type-change-message": "If you confirm new value type all entered key filters will be removed.",
"no-key-filters": "No key filters configured", "no-key-filters": "No key filters configured",
"add-key-filter": "Add key filter", "add-key-filter": "Add key filter",
"remove-key-filter": "Remove key filter", "remove-key-filter": "Remove key filter",

3
ui-ngx/src/styles.scss

@ -750,6 +750,9 @@ mat-label {
&.tb-mat-16 { &.tb-mat-16 {
@include tb-mat-icon-size(16); @include tb-mat-icon-size(16);
} }
&.tb-mat-18 {
@include tb-mat-icon-size(18);
}
&.tb-mat-20 { &.tb-mat-20 {
@include tb-mat-icon-size(20); @include tb-mat-icon-size(20);
} }

Loading…
Cancel
Save