Commit 25dae17671511c1fd3a51183c3009b0138ce5e28
1 parent
9600cc04
UI: Dashboard filters implementation
Showing
48 changed files
with
856 additions
and
173 deletions
@@ -24,7 +24,7 @@ import { EntityAliases } from '@shared/models/alias.models'; | @@ -24,7 +24,7 @@ import { EntityAliases } from '@shared/models/alias.models'; | ||
24 | import { EntityInfo } from '@shared/models/entity.models'; | 24 | import { EntityInfo } from '@shared/models/entity.models'; |
25 | import { map, mergeMap } from 'rxjs/operators'; | 25 | import { map, mergeMap } from 'rxjs/operators'; |
26 | import { | 26 | import { |
27 | - defaultEntityDataPageLink, FilterInfo, filterInfoToKeyFilters, Filters, KeyFilter, singleEntityDataPageLink, | 27 | + defaultEntityDataPageLink, Filter, FilterInfo, filterInfoToKeyFilters, Filters, KeyFilter, singleEntityDataPageLink, |
28 | updateDatasourceFromEntityInfo | 28 | updateDatasourceFromEntityInfo |
29 | } from '@shared/models/query/query.models'; | 29 | } from '@shared/models/query/query.models'; |
30 | 30 | ||
@@ -41,6 +41,7 @@ export class AliasController implements IAliasController { | @@ -41,6 +41,7 @@ export class AliasController implements IAliasController { | ||
41 | 41 | ||
42 | entityAliases: EntityAliases; | 42 | entityAliases: EntityAliases; |
43 | filters: Filters; | 43 | filters: Filters; |
44 | + userFilters: Filters; | ||
44 | 45 | ||
45 | resolvedAliases: {[aliasId: string]: AliasInfo} = {}; | 46 | resolvedAliases: {[aliasId: string]: AliasInfo} = {}; |
46 | resolvedAliasesObservable: {[aliasId: string]: Observable<AliasInfo>} = {}; | 47 | resolvedAliasesObservable: {[aliasId: string]: Observable<AliasInfo>} = {}; |
@@ -54,6 +55,7 @@ export class AliasController implements IAliasController { | @@ -54,6 +55,7 @@ export class AliasController implements IAliasController { | ||
54 | private origFilters: Filters) { | 55 | private origFilters: Filters) { |
55 | this.entityAliases = deepClone(this.origEntityAliases); | 56 | this.entityAliases = deepClone(this.origEntityAliases); |
56 | this.filters = deepClone(this.origFilters); | 57 | this.filters = deepClone(this.origFilters); |
58 | + this.userFilters = {}; | ||
57 | } | 59 | } |
58 | 60 | ||
59 | updateEntityAliases(newEntityAliases: EntityAliases) { | 61 | updateEntityAliases(newEntityAliases: EntityAliases) { |
@@ -94,6 +96,9 @@ export class AliasController implements IAliasController { | @@ -94,6 +96,9 @@ export class AliasController implements IAliasController { | ||
94 | } | 96 | } |
95 | this.filters = deepClone(newFilters); | 97 | this.filters = deepClone(newFilters); |
96 | if (changedFilterIds.length) { | 98 | if (changedFilterIds.length) { |
99 | + for (const filterId of changedFilterIds) { | ||
100 | + delete this.userFilters[filterId]; | ||
101 | + } | ||
97 | this.filtersChangedSubject.next(changedFilterIds); | 102 | this.filtersChangedSubject.next(changedFilterIds); |
98 | } | 103 | } |
99 | } | 104 | } |
@@ -146,7 +151,11 @@ export class AliasController implements IAliasController { | @@ -146,7 +151,11 @@ export class AliasController implements IAliasController { | ||
146 | } | 151 | } |
147 | 152 | ||
148 | getFilterInfo(filterId: string): FilterInfo { | 153 | getFilterInfo(filterId: string): FilterInfo { |
149 | - return this.filters[filterId]; | 154 | + if (this.userFilters[filterId]) { |
155 | + return this.userFilters[filterId]; | ||
156 | + } else { | ||
157 | + return this.filters[filterId]; | ||
158 | + } | ||
150 | } | 159 | } |
151 | 160 | ||
152 | getKeyFilters(filterId: string): Array<KeyFilter> { | 161 | getKeyFilters(filterId: string): Array<KeyFilter> { |
@@ -353,4 +362,15 @@ export class AliasController implements IAliasController { | @@ -353,4 +362,15 @@ export class AliasController implements IAliasController { | ||
353 | } | 362 | } |
354 | } | 363 | } |
355 | } | 364 | } |
365 | + | ||
366 | + updateUserFilter(filter: Filter) { | ||
367 | + let prevUserFilter = this.userFilters[filter.id]; | ||
368 | + if (!prevUserFilter) { | ||
369 | + prevUserFilter = this.filters[filter.id]; | ||
370 | + } | ||
371 | + if (prevUserFilter && !isEqual(prevUserFilter, filter)) { | ||
372 | + this.userFilters[filter.id] = filter; | ||
373 | + this.filtersChangedSubject.next([filter.id]); | ||
374 | + } | ||
375 | + } | ||
356 | } | 376 | } |
@@ -68,6 +68,7 @@ export interface EntityDataSubscriptionOptions { | @@ -68,6 +68,7 @@ export interface EntityDataSubscriptionOptions { | ||
68 | isPaginatedDataSubscription?: boolean; | 68 | isPaginatedDataSubscription?: boolean; |
69 | pageLink?: EntityDataPageLink; | 69 | pageLink?: EntityDataPageLink; |
70 | keyFilters?: Array<KeyFilter>; | 70 | keyFilters?: Array<KeyFilter>; |
71 | + additionalKeyFilters?: Array<KeyFilter>; | ||
71 | subscriptionTimewindow?: SubscriptionTimewindow; | 72 | subscriptionTimewindow?: SubscriptionTimewindow; |
72 | } | 73 | } |
73 | 74 | ||
@@ -206,10 +207,19 @@ export class EntityDataSubscription { | @@ -206,10 +207,19 @@ export class EntityDataSubscription { | ||
206 | this.subscriber = new TelemetrySubscriber(this.telemetryService); | 207 | this.subscriber = new TelemetrySubscriber(this.telemetryService); |
207 | this.dataCommand = new EntityDataCmd(); | 208 | this.dataCommand = new EntityDataCmd(); |
208 | 209 | ||
210 | + let keyFilters = this.entityDataSubscriptionOptions.keyFilters; | ||
211 | + if (this.entityDataSubscriptionOptions.additionalKeyFilters) { | ||
212 | + if (keyFilters) { | ||
213 | + keyFilters = keyFilters.concat(this.entityDataSubscriptionOptions.additionalKeyFilters); | ||
214 | + } else { | ||
215 | + keyFilters = this.entityDataSubscriptionOptions.additionalKeyFilters; | ||
216 | + } | ||
217 | + } | ||
218 | + | ||
209 | this.dataCommand.query = { | 219 | this.dataCommand.query = { |
210 | entityFilter: this.entityDataSubscriptionOptions.entityFilter, | 220 | entityFilter: this.entityDataSubscriptionOptions.entityFilter, |
211 | pageLink: this.entityDataSubscriptionOptions.pageLink, | 221 | pageLink: this.entityDataSubscriptionOptions.pageLink, |
212 | - keyFilters: this.entityDataSubscriptionOptions.keyFilters, | 222 | + keyFilters, |
213 | entityFields, | 223 | entityFields, |
214 | latestValues: this.latestValues | 224 | latestValues: this.latestValues |
215 | }; | 225 | }; |
@@ -65,7 +65,7 @@ export class EntityDataService { | @@ -65,7 +65,7 @@ export class EntityDataService { | ||
65 | return of(null); | 65 | return of(null); |
66 | } | 66 | } |
67 | listener.subscription = this.createSubscription(listener, | 67 | listener.subscription = this.createSubscription(listener, |
68 | - datasource.pageLink, datasource.keyFilters, | 68 | + datasource.pageLink, datasource.keyFilters, null, |
69 | false); | 69 | false); |
70 | return listener.subscription.subscribe(); | 70 | return listener.subscription.subscribe(); |
71 | } | 71 | } |
@@ -86,15 +86,8 @@ export class EntityDataService { | @@ -86,15 +86,8 @@ export class EntityDataService { | ||
86 | if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !pageLink)) { | 86 | if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !pageLink)) { |
87 | return of(null); | 87 | return of(null); |
88 | } | 88 | } |
89 | - if (datasource.keyFilters) { | ||
90 | - if (keyFilters) { | ||
91 | - keyFilters = keyFilters.concat(datasource.keyFilters); | ||
92 | - } else { | ||
93 | - keyFilters = datasource.keyFilters; | ||
94 | - } | ||
95 | - } | ||
96 | listener.subscription = this.createSubscription(listener, | 89 | listener.subscription = this.createSubscription(listener, |
97 | - pageLink, keyFilters, true); | 90 | + pageLink, datasource.keyFilters, keyFilters,true); |
98 | if (listener.subscriptionType === widgetType.timeseries) { | 91 | if (listener.subscriptionType === widgetType.timeseries) { |
99 | listener.subscription.entityDataSubscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow); | 92 | listener.subscription.entityDataSubscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow); |
100 | } | 93 | } |
@@ -110,6 +103,7 @@ export class EntityDataService { | @@ -110,6 +103,7 @@ export class EntityDataService { | ||
110 | private createSubscription(listener: EntityDataListener, | 103 | private createSubscription(listener: EntityDataListener, |
111 | pageLink: EntityDataPageLink, | 104 | pageLink: EntityDataPageLink, |
112 | keyFilters: KeyFilter[], | 105 | keyFilters: KeyFilter[], |
106 | + additionalKeyFilters: KeyFilter[], | ||
113 | isPaginatedDataSubscription: boolean): EntityDataSubscription { | 107 | isPaginatedDataSubscription: boolean): EntityDataSubscription { |
114 | const datasource = listener.configDatasource; | 108 | const datasource = listener.configDatasource; |
115 | const subscriptionDataKeys: Array<SubscriptionDataKey> = []; | 109 | const subscriptionDataKeys: Array<SubscriptionDataKey> = []; |
@@ -131,6 +125,7 @@ export class EntityDataService { | @@ -131,6 +125,7 @@ export class EntityDataService { | ||
131 | entityDataSubscriptionOptions.entityFilter = datasource.entityFilter; | 125 | entityDataSubscriptionOptions.entityFilter = datasource.entityFilter; |
132 | entityDataSubscriptionOptions.pageLink = pageLink; | 126 | entityDataSubscriptionOptions.pageLink = pageLink; |
133 | entityDataSubscriptionOptions.keyFilters = keyFilters; | 127 | entityDataSubscriptionOptions.keyFilters = keyFilters; |
128 | + entityDataSubscriptionOptions.additionalKeyFilters = additionalKeyFilters; | ||
134 | } | 129 | } |
135 | entityDataSubscriptionOptions.isPaginatedDataSubscription = isPaginatedDataSubscription; | 130 | entityDataSubscriptionOptions.isPaginatedDataSubscription = isPaginatedDataSubscription; |
136 | return new EntityDataSubscription(entityDataSubscriptionOptions, | 131 | return new EntityDataSubscription(entityDataSubscriptionOptions, |
@@ -112,6 +112,7 @@ export interface IAliasController { | @@ -112,6 +112,7 @@ export interface IAliasController { | ||
112 | getFilterInfo(filterId: string): FilterInfo; | 112 | getFilterInfo(filterId: string): FilterInfo; |
113 | getKeyFilters(filterId: string): Array<KeyFilter>; | 113 | getKeyFilters(filterId: string): Array<KeyFilter>; |
114 | updateCurrentAliasEntity(aliasId: string, currentEntity: EntityInfo); | 114 | updateCurrentAliasEntity(aliasId: string, currentEntity: EntityInfo); |
115 | + updateUserFilter(filter: Filter); | ||
115 | updateEntityAliases(entityAliases: EntityAliases); | 116 | updateEntityAliases(entityAliases: EntityAliases); |
116 | updateFilters(filters: Filters); | 117 | updateFilters(filters: Filters); |
117 | updateAliases(aliasIds?: Array<string>); | 118 | updateAliases(aliasIds?: Array<string>); |
@@ -1011,7 +1011,7 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -1011,7 +1011,7 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
1011 | const entityDataListener = this.entityDataListeners[datasourceIndex]; | 1011 | const entityDataListener = this.entityDataListeners[datasourceIndex]; |
1012 | if (entityDataListener) { | 1012 | if (entityDataListener) { |
1013 | const pageLink = entityDataListener.subscription.entityDataSubscriptionOptions.pageLink; | 1013 | const pageLink = entityDataListener.subscription.entityDataSubscriptionOptions.pageLink; |
1014 | - const keyFilters = entityDataListener.subscription.entityDataSubscriptionOptions.keyFilters; | 1014 | + const keyFilters = entityDataListener.subscription.entityDataSubscriptionOptions.additionalKeyFilters; |
1015 | this.subscribeForPaginatedData(datasourceIndex, pageLink, keyFilters); | 1015 | this.subscribeForPaginatedData(datasourceIndex, pageLink, keyFilters); |
1016 | } | 1016 | } |
1017 | } | 1017 | } |
@@ -410,7 +410,8 @@ export class ItemBufferService { | @@ -410,7 +410,8 @@ export class ItemBufferService { | ||
410 | private prepareFilterInfo(filter: Filter): FilterInfo { | 410 | private prepareFilterInfo(filter: Filter): FilterInfo { |
411 | return { | 411 | return { |
412 | filter: filter.filter, | 412 | filter: filter.filter, |
413 | - keyFilters: filter.keyFilters | 413 | + keyFilters: filter.keyFilters, |
414 | + editable: filter.editable | ||
414 | }; | 415 | }; |
415 | } | 416 | } |
416 | 417 | ||
@@ -513,7 +514,8 @@ export class ItemBufferService { | @@ -513,7 +514,8 @@ export class ItemBufferService { | ||
513 | if (!newFilterId) { | 514 | if (!newFilterId) { |
514 | const newFilterName = this.createFilterName(filters, filterInfo.filter); | 515 | const newFilterName = this.createFilterName(filters, filterInfo.filter); |
515 | newFilterId = this.utils.guid(); | 516 | newFilterId = this.utils.guid(); |
516 | - filters[newFilterId] = {id: newFilterId, filter: newFilterName, keyFilters: filterInfo.keyFilters}; | 517 | + filters[newFilterId] = {id: newFilterId, filter: newFilterName, |
518 | + keyFilters: filterInfo.keyFilters, editable: filterInfo.editable}; | ||
517 | } | 519 | } |
518 | return newFilterId; | 520 | return newFilterId; |
519 | } | 521 | } |
@@ -15,16 +15,16 @@ | @@ -15,16 +15,16 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<div fxLayout="row" [formGroup]="booleanFilterPredicateFormGroup"> | ||
19 | - <mat-form-field class="mat-block"> | ||
20 | - <mat-label translate>filter.operation.operation</mat-label> | ||
21 | - <mat-select required formControlName="operation"> | 18 | +<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="booleanFilterPredicateFormGroup"> |
19 | + <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block"> | ||
20 | + <mat-label></mat-label> | ||
21 | + <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}"> | ||
22 | <mat-option *ngFor="let operation of booleanOperations" [value]="operation"> | 22 | <mat-option *ngFor="let operation of booleanOperations" [value]="operation"> |
23 | {{booleanOperationTranslations.get(booleanOperationEnum[operation]) | translate}} | 23 | {{booleanOperationTranslations.get(booleanOperationEnum[operation]) | translate}} |
24 | </mat-option> | 24 | </mat-option> |
25 | </mat-select> | 25 | </mat-select> |
26 | </mat-form-field> | 26 | </mat-form-field> |
27 | - <mat-checkbox formControlName="value"> | ||
28 | - {{ 'filter.value' | translate }} | 27 | + <mat-checkbox fxFlex formControlName="value"> |
28 | + {{ (booleanFilterPredicateFormGroup.get('value').value ? 'value.true' : 'value.false') | translate }} | ||
29 | </mat-checkbox> | 29 | </mat-checkbox> |
30 | </div> | 30 | </div> |
@@ -26,7 +26,7 @@ import { isDefined } from '@core/utils'; | @@ -26,7 +26,7 @@ import { isDefined } from '@core/utils'; | ||
26 | @Component({ | 26 | @Component({ |
27 | selector: 'tb-boolean-filter-predicate', | 27 | selector: 'tb-boolean-filter-predicate', |
28 | templateUrl: './boolean-filter-predicate.component.html', | 28 | templateUrl: './boolean-filter-predicate.component.html', |
29 | - styleUrls: [], | 29 | + styleUrls: ['./filter-predicate.scss'], |
30 | providers: [ | 30 | providers: [ |
31 | { | 31 | { |
32 | provide: NG_VALUE_ACCESSOR, | 32 | provide: NG_VALUE_ACCESSOR, |
@@ -15,7 +15,7 @@ | @@ -15,7 +15,7 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<form [formGroup]="complexFilterFormGroup" (ngSubmit)="save()"> | 18 | +<form [formGroup]="complexFilterFormGroup" (ngSubmit)="save()" style="width: 700px;"> |
19 | <mat-toolbar color="primary"> | 19 | <mat-toolbar color="primary"> |
20 | <h2 translate>filter.complex-filter</h2> | 20 | <h2 translate>filter.complex-filter</h2> |
21 | <span fxFlex></span> | 21 | <span fxFlex></span> |
@@ -38,6 +38,7 @@ | @@ -38,6 +38,7 @@ | ||
38 | <tb-filter-predicate-list | 38 | <tb-filter-predicate-list |
39 | [userMode]="data.userMode" | 39 | [userMode]="data.userMode" |
40 | [valueType]="data.valueType" | 40 | [valueType]="data.valueType" |
41 | + [operation]="complexFilterFormGroup.get('operation').value" | ||
41 | formControlName="predicates"> | 42 | formControlName="predicates"> |
42 | </tb-filter-predicate-list> | 43 | </tb-filter-predicate-list> |
43 | </fieldset> | 44 | </fieldset> |
@@ -45,8 +46,8 @@ | @@ -45,8 +46,8 @@ | ||
45 | <div mat-dialog-actions fxLayoutAlign="end center"> | 46 | <div mat-dialog-actions fxLayoutAlign="end center"> |
46 | <button mat-raised-button color="primary" | 47 | <button mat-raised-button color="primary" |
47 | type="submit" | 48 | type="submit" |
48 | - [disabled]="(isLoading$ | async) || complexFilterFormGroup.invalid"> | ||
49 | - {{ 'action.update' | translate }} | 49 | + [disabled]="(isLoading$ | async) || complexFilterFormGroup.invalid || !complexFilterFormGroup.dirty"> |
50 | + {{ (isAdd ? 'action.add' : 'action.update') | translate }} | ||
50 | </button> | 51 | </button> |
51 | <button mat-button color="primary" | 52 | <button mat-button color="primary" |
52 | type="button" | 53 | type="button" |
@@ -33,6 +33,7 @@ export interface ComplexFilterPredicateDialogData { | @@ -33,6 +33,7 @@ export interface ComplexFilterPredicateDialogData { | ||
33 | complexPredicate: ComplexFilterPredicate; | 33 | complexPredicate: ComplexFilterPredicate; |
34 | userMode: boolean; | 34 | userMode: boolean; |
35 | disabled: boolean; | 35 | disabled: boolean; |
36 | + isAdd: boolean; | ||
36 | valueType: EntityKeyValueType; | 37 | valueType: EntityKeyValueType; |
37 | } | 38 | } |
38 | 39 | ||
@@ -52,6 +53,8 @@ export class ComplexFilterPredicateDialogComponent extends | @@ -52,6 +53,8 @@ export class ComplexFilterPredicateDialogComponent extends | ||
52 | complexOperationEnum = ComplexOperation; | 53 | complexOperationEnum = ComplexOperation; |
53 | complexOperationTranslations = complexOperationTranslationMap; | 54 | complexOperationTranslations = complexOperationTranslationMap; |
54 | 55 | ||
56 | + isAdd: boolean; | ||
57 | + | ||
55 | submitted = false; | 58 | submitted = false; |
56 | 59 | ||
57 | constructor(protected store: Store<AppState>, | 60 | constructor(protected store: Store<AppState>, |
@@ -62,6 +65,8 @@ export class ComplexFilterPredicateDialogComponent extends | @@ -62,6 +65,8 @@ export class ComplexFilterPredicateDialogComponent extends | ||
62 | private fb: FormBuilder) { | 65 | private fb: FormBuilder) { |
63 | super(store, router, dialogRef); | 66 | super(store, router, dialogRef); |
64 | 67 | ||
68 | + this.isAdd = this.data.isAdd; | ||
69 | + | ||
65 | this.complexFilterFormGroup = this.fb.group( | 70 | this.complexFilterFormGroup = this.fb.group( |
66 | { | 71 | { |
67 | operation: [this.data.complexPredicate.operation, [Validators.required]], | 72 | operation: [this.data.complexPredicate.operation, [Validators.required]], |
@@ -15,9 +15,10 @@ | @@ -15,9 +15,10 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<div fxLayout="row"> | 18 | +<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
19 | <mat-label translate>filter.complex-filter</mat-label> | 19 | <mat-label translate>filter.complex-filter</mat-label> |
20 | <button mat-icon-button color="primary" | 20 | <button mat-icon-button color="primary" |
21 | + class="tb-mat-32" | ||
21 | [fxShow]="!disabled" | 22 | [fxShow]="!disabled" |
22 | type="button" | 23 | type="button" |
23 | (click)="openComplexFilterDialog()" | 24 | (click)="openComplexFilterDialog()" |
@@ -78,7 +78,8 @@ export class ComplexFilterPredicateComponent implements ControlValueAccessor, On | @@ -78,7 +78,8 @@ export class ComplexFilterPredicateComponent implements ControlValueAccessor, On | ||
78 | complexPredicate: deepClone(this.complexFilterPredicate), | 78 | complexPredicate: deepClone(this.complexFilterPredicate), |
79 | disabled: this.disabled, | 79 | disabled: this.disabled, |
80 | userMode: this.userMode, | 80 | userMode: this.userMode, |
81 | - valueType: this.valueType | 81 | + valueType: this.valueType, |
82 | + isAdd: false | ||
82 | } | 83 | } |
83 | }).afterClosed().subscribe( | 84 | }).afterClosed().subscribe( |
84 | (result) => { | 85 | (result) => { |
@@ -15,9 +15,9 @@ | @@ -15,9 +15,9 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<form [formGroup]="filterFormGroup" (ngSubmit)="save()" style="min-width: 480px;"> | 18 | +<form [formGroup]="filterFormGroup" (ngSubmit)="save()" style="width: 700px;"> |
19 | <mat-toolbar color="primary"> | 19 | <mat-toolbar color="primary"> |
20 | - <h2>{{ (isAdd ? 'filter.add' : 'filter.edit') | translate }}</h2> | 20 | + <h2>{{ userMode ? filter.filter : ((isAdd ? 'filter.add' : 'filter.edit') | translate) }}</h2> |
21 | <span fxFlex></span> | 21 | <span fxFlex></span> |
22 | <button mat-icon-button | 22 | <button mat-icon-button |
23 | (click)="cancel()" | 23 | (click)="cancel()" |
@@ -30,16 +30,24 @@ | @@ -30,16 +30,24 @@ | ||
30 | <div mat-dialog-content> | 30 | <div mat-dialog-content> |
31 | <fieldset [disabled]="isLoading$ | async"> | 31 | <fieldset [disabled]="isLoading$ | async"> |
32 | <div fxFlex fxLayout="column"> | 32 | <div fxFlex fxLayout="column"> |
33 | - <mat-form-field fxFlex class="mat-block"> | ||
34 | - <mat-label translate>filter.name</mat-label> | ||
35 | - <input matInput formControlName="filter" required> | ||
36 | - <mat-error *ngIf="filterFormGroup.get('filter').hasError('required')"> | ||
37 | - {{ 'filter.name-required' | translate }} | ||
38 | - </mat-error> | ||
39 | - <mat-error *ngIf="filterFormGroup.get('filter').hasError('duplicateFilterName')"> | ||
40 | - {{ 'filter.duplicate-filter' | translate }} | ||
41 | - </mat-error> | ||
42 | - </mat-form-field> | 33 | + <div fxLayout="row" [fxShow]="!userMode"> |
34 | + <mat-form-field fxFlex class="mat-block"> | ||
35 | + <mat-label translate>filter.name</mat-label> | ||
36 | + <input matInput formControlName="filter" required> | ||
37 | + <mat-error *ngIf="filterFormGroup.get('filter').hasError('required')"> | ||
38 | + {{ 'filter.name-required' | translate }} | ||
39 | + </mat-error> | ||
40 | + <mat-error *ngIf="filterFormGroup.get('filter').hasError('duplicateFilterName')"> | ||
41 | + {{ 'filter.duplicate-filter' | translate }} | ||
42 | + </mat-error> | ||
43 | + </mat-form-field> | ||
44 | + <section class="tb-editable-switch" fxLayout="column" fxLayoutAlign="start center"> | ||
45 | + <label class="tb-small editable-label" translate>filter.editable</label> | ||
46 | + <mat-slide-toggle class="editable-switch" | ||
47 | + formControlName="editable"> | ||
48 | + </mat-slide-toggle> | ||
49 | + </section> | ||
50 | + </div> | ||
43 | <tb-key-filter-list | 51 | <tb-key-filter-list |
44 | formControlName="keyFilters" | 52 | formControlName="keyFilters" |
45 | [userMode]="userMode"> | 53 | [userMode]="userMode"> |
@@ -51,7 +59,7 @@ | @@ -51,7 +59,7 @@ | ||
51 | <button mat-raised-button color="primary" | 59 | <button mat-raised-button color="primary" |
52 | type="submit" | 60 | type="submit" |
53 | [disabled]="(isLoading$ | async) || filterFormGroup.invalid || !filterFormGroup.dirty"> | 61 | [disabled]="(isLoading$ | async) || filterFormGroup.invalid || !filterFormGroup.dirty"> |
54 | - {{ (isAdd ? 'action.add' : 'action.update') | translate }} | 62 | + {{ (userMode ? 'action.update' : (isAdd ? 'action.add' : 'action.update')) | translate }} |
55 | </button> | 63 | </button> |
56 | <button mat-button color="primary" | 64 | <button mat-button color="primary" |
57 | type="button" | 65 | type="button" |
1 | +/** | ||
2 | + * Copyright © 2016-2020 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +:host { | ||
17 | + .tb-editable-switch { | ||
18 | + padding-left: 10px; | ||
19 | + | ||
20 | + .editable-switch { | ||
21 | + margin: 0; | ||
22 | + } | ||
23 | + | ||
24 | + .editable-label { | ||
25 | + margin: 5px 0; | ||
26 | + } | ||
27 | + } | ||
28 | +} |
@@ -45,7 +45,7 @@ export interface FilterDialogData { | @@ -45,7 +45,7 @@ export interface FilterDialogData { | ||
45 | selector: 'tb-filter-dialog', | 45 | selector: 'tb-filter-dialog', |
46 | templateUrl: './filter-dialog.component.html', | 46 | templateUrl: './filter-dialog.component.html', |
47 | providers: [{provide: ErrorStateMatcher, useExisting: FilterDialogComponent}], | 47 | providers: [{provide: ErrorStateMatcher, useExisting: FilterDialogComponent}], |
48 | - styleUrls: [] | 48 | + styleUrls: ['./filter-dialog.component.scss'] |
49 | }) | 49 | }) |
50 | export class FilterDialogComponent extends DialogComponent<FilterDialogComponent, Filter> | 50 | export class FilterDialogComponent extends DialogComponent<FilterDialogComponent, Filter> |
51 | implements OnInit, ErrorStateMatcher { | 51 | implements OnInit, ErrorStateMatcher { |
@@ -83,7 +83,8 @@ export class FilterDialogComponent extends DialogComponent<FilterDialogComponent | @@ -83,7 +83,8 @@ export class FilterDialogComponent extends DialogComponent<FilterDialogComponent | ||
83 | this.filter = { | 83 | this.filter = { |
84 | id: null, | 84 | id: null, |
85 | filter: '', | 85 | filter: '', |
86 | - keyFilters: [] | 86 | + keyFilters: [], |
87 | + editable: true | ||
87 | }; | 88 | }; |
88 | } else { | 89 | } else { |
89 | this.filter = data.filter; | 90 | this.filter = data.filter; |
@@ -91,6 +92,7 @@ export class FilterDialogComponent extends DialogComponent<FilterDialogComponent | @@ -91,6 +92,7 @@ export class FilterDialogComponent extends DialogComponent<FilterDialogComponent | ||
91 | 92 | ||
92 | this.filterFormGroup = this.fb.group({ | 93 | this.filterFormGroup = this.fb.group({ |
93 | filter: [this.filter.filter, [this.validateDuplicateFilterName(), Validators.required]], | 94 | filter: [this.filter.filter, [this.validateDuplicateFilterName(), Validators.required]], |
95 | + editable: [this.filter.editable], | ||
94 | keyFilters: [this.filter.keyFilters, Validators.required] | 96 | keyFilters: [this.filter.keyFilters, Validators.required] |
95 | }); | 97 | }); |
96 | } | 98 | } |
@@ -128,6 +130,7 @@ export class FilterDialogComponent extends DialogComponent<FilterDialogComponent | @@ -128,6 +130,7 @@ export class FilterDialogComponent extends DialogComponent<FilterDialogComponent | ||
128 | save(): void { | 130 | save(): void { |
129 | this.submitted = true; | 131 | this.submitted = true; |
130 | this.filter.filter = this.filterFormGroup.get('filter').value; | 132 | this.filter.filter = this.filterFormGroup.get('filter').value; |
133 | + this.filter.editable = this.filterFormGroup.get('editable').value; | ||
131 | this.filter.keyFilters = this.filterFormGroup.get('keyFilters').value; | 134 | this.filter.keyFilters = this.filterFormGroup.get('keyFilters').value; |
132 | if (this.isAdd) { | 135 | if (this.isAdd) { |
133 | this.filter.id = this.utils.guid(); | 136 | this.filter.id = this.utils.guid(); |
@@ -16,42 +16,72 @@ | @@ -16,42 +16,72 @@ | ||
16 | 16 | ||
17 | --> | 17 | --> |
18 | <section fxLayout="column" [formGroup]="filterListFormGroup"> | 18 | <section fxLayout="column" [formGroup]="filterListFormGroup"> |
19 | - <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" style="max-height: 40px;" | ||
20 | - formArrayName="predicates" | ||
21 | - *ngFor="let predicateControl of predicatesFormArray().controls; let $index = index"> | ||
22 | - <tb-filter-predicate | ||
23 | - [userMode]="userMode" | ||
24 | - [valueType]="valueType" | ||
25 | - [formControl]="predicateControl"> | ||
26 | - </tb-filter-predicate> | ||
27 | - <button mat-icon-button color="primary" | ||
28 | - [fxShow]="!disabled && !userMode" | ||
29 | - type="button" | ||
30 | - (click)="removePredicate($index)" | ||
31 | - matTooltip="{{ 'filter.remove-filter' | translate }}" | ||
32 | - matTooltipPosition="above"> | ||
33 | - <mat-icon>close</mat-icon> | ||
34 | - </button> | ||
35 | - </div> | ||
36 | - <span [fxShow]="!predicatesFormArray().length" | ||
37 | - fxLayoutAlign="center center" [ngClass]="{'disabled': disabled}" | ||
38 | - class="no-data-found" translate>filter.no-filters</span> | ||
39 | - <div style="margin-top: 8px;" fxLayout="row" fxLayoutGap="8px"> | ||
40 | - <button mat-button mat-raised-button color="primary" | ||
41 | - [fxShow]="!disabled && !userMode" | ||
42 | - (click)="addPredicate(false)" | ||
43 | - type="button" | ||
44 | - matTooltip="{{ 'filter.add-filter' | translate }}" | ||
45 | - matTooltipPosition="above"> | ||
46 | - {{ 'action.add' | translate }} | ||
47 | - </button> | ||
48 | - <button mat-button mat-raised-button color="primary" | ||
49 | - [fxShow]="!disabled && !userMode" | ||
50 | - (click)="addPredicate(true)" | ||
51 | - type="button" | ||
52 | - matTooltip="{{ 'filter.add-complex-filter' | translate }}" | ||
53 | - matTooltipPosition="above"> | ||
54 | - {{ 'filter.add-complex' | translate }} | ||
55 | - </button> | ||
56 | - </div> | 19 | + <mat-expansion-panel [expanded]="true"> |
20 | + <mat-expansion-panel-header> | ||
21 | + <mat-panel-title> | ||
22 | + <div translate>filter.filters</div> | ||
23 | + </mat-panel-title> | ||
24 | + </mat-expansion-panel-header> | ||
25 | + <div fxLayout="row"> | ||
26 | + <span fxFlex="8"></span> | ||
27 | + <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" fxFlex="92"> | ||
28 | + <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> | ||
29 | + <label fxFlex translate class="tb-title no-padding">filter.operation.operation</label> | ||
30 | + <label *ngIf="valueType === valueTypeEnum.STRING" | ||
31 | + translate class="tb-title no-padding" style="min-width: 70px;">filter.ignore-case</label> | ||
32 | + </div> | ||
33 | + <label fxFlex translate class="tb-title no-padding">filter.value</label> | ||
34 | + <span [fxShow]="!disabled && !userMode" style="min-width: 40px;"> </span> | ||
35 | + </div> | ||
36 | + </div> | ||
37 | + <mat-divider></mat-divider> | ||
38 | + <div class="predicate-list"> | ||
39 | + <div fxLayout="row" fxLayoutAlign="start center" style="height: 40px;" | ||
40 | + formArrayName="predicates" | ||
41 | + *ngFor="let predicateControl of predicatesFormArray().controls; let $index = index"> | ||
42 | + <div fxFlex="8" fxLayout="row" fxLayoutAlign="center" class="filters-operation"> | ||
43 | + <span *ngIf="$index > 0">{{ complexOperationTranslations.get(operation) | translate }}</span> | ||
44 | + </div> | ||
45 | + <div fxLayout="column" fxFlex="92"> | ||
46 | + <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" fxFlex> | ||
47 | + <tb-filter-predicate | ||
48 | + fxFlex | ||
49 | + [userMode]="userMode" | ||
50 | + [valueType]="valueType" | ||
51 | + [formControl]="predicateControl"> | ||
52 | + </tb-filter-predicate> | ||
53 | + <button mat-icon-button color="primary" | ||
54 | + [fxShow]="!disabled && !userMode" | ||
55 | + type="button" | ||
56 | + (click)="removePredicate($index)" | ||
57 | + matTooltip="{{ 'filter.remove-filter' | translate }}" | ||
58 | + matTooltipPosition="above"> | ||
59 | + <mat-icon>close</mat-icon> | ||
60 | + </button> | ||
61 | + </div> | ||
62 | + </div> | ||
63 | + </div> | ||
64 | + <span [fxShow]="!predicatesFormArray().length" | ||
65 | + fxLayoutAlign="center center" [ngClass]="{'disabled': disabled}" | ||
66 | + class="no-data-found" translate>filter.no-filters</span> | ||
67 | + </div> | ||
68 | + <div style="margin-top: 16px;" fxLayout="row" fxLayoutGap="8px"> | ||
69 | + <button mat-button mat-raised-button color="primary" | ||
70 | + [fxShow]="!disabled && !userMode" | ||
71 | + (click)="addPredicate(false)" | ||
72 | + type="button" | ||
73 | + matTooltip="{{ 'filter.add-filter' | translate }}" | ||
74 | + matTooltipPosition="above"> | ||
75 | + {{ 'action.add' | translate }} | ||
76 | + </button> | ||
77 | + <button mat-button mat-raised-button color="primary" | ||
78 | + [fxShow]="!disabled && !userMode" | ||
79 | + (click)="addPredicate(true)" | ||
80 | + type="button" | ||
81 | + matTooltip="{{ 'filter.add-complex-filter' | translate }}" | ||
82 | + matTooltipPosition="above"> | ||
83 | + {{ 'filter.add-complex' | translate }} | ||
84 | + </button> | ||
85 | + </div> | ||
86 | + </mat-expansion-panel> | ||
57 | </section> | 87 | </section> |
1 | +/** | ||
2 | + * Copyright © 2016-2020 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +:host { | ||
17 | + .predicate-list { | ||
18 | + overflow: auto; | ||
19 | + max-height: 350px; | ||
20 | + .no-data-found { | ||
21 | + height: 50px; | ||
22 | + } | ||
23 | + } | ||
24 | + .filters-operation { | ||
25 | + margin-top: -40px; | ||
26 | + color: #666; | ||
27 | + font-weight: 500; | ||
28 | + } | ||
29 | +} |
@@ -27,6 +27,7 @@ import { | @@ -27,6 +27,7 @@ import { | ||
27 | import { Observable, of, Subscription } from 'rxjs'; | 27 | import { Observable, of, Subscription } from 'rxjs'; |
28 | import { | 28 | import { |
29 | ComplexFilterPredicate, | 29 | ComplexFilterPredicate, |
30 | + ComplexOperation, complexOperationTranslationMap, | ||
30 | createDefaultFilterPredicate, | 31 | createDefaultFilterPredicate, |
31 | EntityKeyValueType, | 32 | EntityKeyValueType, |
32 | KeyFilterPredicate | 33 | KeyFilterPredicate |
@@ -40,7 +41,7 @@ import { MatDialog } from '@angular/material/dialog'; | @@ -40,7 +41,7 @@ import { MatDialog } from '@angular/material/dialog'; | ||
40 | @Component({ | 41 | @Component({ |
41 | selector: 'tb-filter-predicate-list', | 42 | selector: 'tb-filter-predicate-list', |
42 | templateUrl: './filter-predicate-list.component.html', | 43 | templateUrl: './filter-predicate-list.component.html', |
43 | - styleUrls: [], | 44 | + styleUrls: ['./filter-predicate-list.component.scss'], |
44 | providers: [ | 45 | providers: [ |
45 | { | 46 | { |
46 | provide: NG_VALUE_ACCESSOR, | 47 | provide: NG_VALUE_ACCESSOR, |
@@ -57,8 +58,14 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni | @@ -57,8 +58,14 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni | ||
57 | 58 | ||
58 | @Input() valueType: EntityKeyValueType; | 59 | @Input() valueType: EntityKeyValueType; |
59 | 60 | ||
61 | + @Input() operation: ComplexOperation = ComplexOperation.AND; | ||
62 | + | ||
60 | filterListFormGroup: FormGroup; | 63 | filterListFormGroup: FormGroup; |
61 | 64 | ||
65 | + valueTypeEnum = EntityKeyValueType; | ||
66 | + | ||
67 | + complexOperationTranslations = complexOperationTranslationMap; | ||
68 | + | ||
62 | private propagateChange = null; | 69 | private propagateChange = null; |
63 | 70 | ||
64 | private valueChangeSubscription: Subscription = null; | 71 | private valueChangeSubscription: Subscription = null; |
@@ -143,7 +150,8 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni | @@ -143,7 +150,8 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni | ||
143 | complexPredicate: predicate, | 150 | complexPredicate: predicate, |
144 | disabled: this.disabled, | 151 | disabled: this.disabled, |
145 | userMode: this.userMode, | 152 | userMode: this.userMode, |
146 | - valueType: this.valueType | 153 | + valueType: this.valueType, |
154 | + isAdd: true | ||
147 | } | 155 | } |
148 | }).afterClosed(); | 156 | }).afterClosed(); |
149 | } | 157 | } |
1 | +/** | ||
2 | + * Copyright © 2016-2020 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +:host ::ng-deep { | ||
17 | + mat-form-field { | ||
18 | + .mat-form-field-wrapper { | ||
19 | + padding-bottom: 0; | ||
20 | + .mat-form-field-infix { | ||
21 | + border-top-width: 0.2em; | ||
22 | + width: auto; | ||
23 | + } | ||
24 | + .mat-form-field-underline { | ||
25 | + bottom: 0; | ||
26 | + } | ||
27 | + } | ||
28 | + } | ||
29 | +} |
@@ -32,8 +32,11 @@ | @@ -32,8 +32,11 @@ | ||
32 | <div class="tb-filters-header" fxLayout="row" fxLayoutAlign="start center"> | 32 | <div class="tb-filters-header" fxLayout="row" fxLayoutAlign="start center"> |
33 | <span fxFlex="5"></span> | 33 | <span fxFlex="5"></span> |
34 | <div fxFlex="95" fxLayout="row" fxLayoutAlign="start center"> | 34 | <div fxFlex="95" fxLayout="row" fxLayoutAlign="start center"> |
35 | - <span class="tb-header-label" translate fxFlex>filter.filter</span> | ||
36 | - <span style="min-width: 80px;"></span> | 35 | + <div class="tb-header-label" translate fxFlex>filter.filter</div> |
36 | + <div class="tb-header-label" translate fxFlex="120px" | ||
37 | + fxLayout="column" fxLayoutAlign="center center" | ||
38 | + style="padding-left: 30px;">filter.editable</div> | ||
39 | + <div style="min-width: 80px;"></div> | ||
37 | </div> | 40 | </div> |
38 | </div> | 41 | </div> |
39 | <fieldset [disabled]="isLoading$ | async"> | 42 | <fieldset [disabled]="isLoading$ | async"> |
@@ -43,13 +46,15 @@ | @@ -43,13 +46,15 @@ | ||
43 | *ngFor="let filterControl of filtersFormArray().controls; let $index = index"> | 46 | *ngFor="let filterControl of filtersFormArray().controls; let $index = index"> |
44 | <span fxFlex="5">{{$index + 1}}.</span> | 47 | <span fxFlex="5">{{$index + 1}}.</span> |
45 | <div class="mat-elevation-z4 tb-filter" fxFlex="95" fxLayout="row" fxLayoutAlign="start center"> | 48 | <div class="mat-elevation-z4 tb-filter" fxFlex="95" fxLayout="row" fxLayoutAlign="start center"> |
46 | - <mat-form-field floatLabel="always" hideRequiredMarker class="mat-block" fxFlex> | ||
47 | - <mat-label></mat-label> | ||
48 | - <input matInput [formControl]="filterControl.get('filter')" required placeholder="{{ 'filter.filter' | translate }}"> | ||
49 | - <mat-error *ngIf="filterControl.get('filter').hasError('required')"> | ||
50 | - {{ 'filter.filter-required' | translate }} | ||
51 | - </mat-error> | ||
52 | - </mat-form-field> | 49 | + <mat-label fxFlex>{{filterControl.get('filter').value}}</mat-label> |
50 | + <section fxFlex="120px" style="padding-left: 10px;" | ||
51 | + class="tb-editable-switch" | ||
52 | + fxLayout="column" | ||
53 | + fxLayoutAlign="center center"> | ||
54 | + <mat-slide-toggle class="editable-switch" | ||
55 | + [formControl]="filterControl.get('editable')"> | ||
56 | + </mat-slide-toggle> | ||
57 | + </section> | ||
53 | <button [disabled]="isLoading$ | async" | 58 | <button [disabled]="isLoading$ | async" |
54 | mat-icon-button color="primary" | 59 | mat-icon-button color="primary" |
55 | style="min-width: 40px;" | 60 | style="min-width: 40px;" |
@@ -36,7 +36,7 @@ import { UtilsService } from '@core/services/utils.service'; | @@ -36,7 +36,7 @@ import { UtilsService } from '@core/services/utils.service'; | ||
36 | import { TranslateService } from '@ngx-translate/core'; | 36 | import { TranslateService } from '@ngx-translate/core'; |
37 | import { ActionNotificationShow } from '@core/notification/notification.actions'; | 37 | import { ActionNotificationShow } from '@core/notification/notification.actions'; |
38 | import { DialogService } from '@core/services/dialog.service'; | 38 | import { DialogService } from '@core/services/dialog.service'; |
39 | -import { deepClone } from '@core/utils'; | 39 | +import { deepClone, isUndefined } from '@core/utils'; |
40 | import { Filter, Filters, KeyFilterInfo } from '@shared/models/query/query.models'; | 40 | import { Filter, Filters, KeyFilterInfo } from '@shared/models/query/query.models'; |
41 | import { FilterDialogComponent, FilterDialogData } from '@home/components/filter/filter-dialog.component'; | 41 | import { FilterDialogComponent, FilterDialogData } from '@home/components/filter/filter-dialog.component'; |
42 | 42 | ||
@@ -109,6 +109,9 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone | @@ -109,6 +109,9 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone | ||
109 | const filterControls: Array<AbstractControl> = []; | 109 | const filterControls: Array<AbstractControl> = []; |
110 | for (const filterId of Object.keys(this.data.filters)) { | 110 | for (const filterId of Object.keys(this.data.filters)) { |
111 | const filter = this.data.filters[filterId]; | 111 | const filter = this.data.filters[filterId]; |
112 | + if (isUndefined(filter.editable)) { | ||
113 | + filter.editable = true; | ||
114 | + } | ||
112 | filterControls.push(this.createFilterFormControl(filterId, filter)); | 115 | filterControls.push(this.createFilterFormControl(filterId, filter)); |
113 | } | 116 | } |
114 | 117 | ||
@@ -121,7 +124,8 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone | @@ -121,7 +124,8 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone | ||
121 | const filterFormControl = this.fb.group({ | 124 | const filterFormControl = this.fb.group({ |
122 | id: [filterId], | 125 | id: [filterId], |
123 | filter: [filter ? filter.filter : null, [Validators.required]], | 126 | filter: [filter ? filter.filter : null, [Validators.required]], |
124 | - keyFilters: [filter ? filter.keyFilters : [], [Validators.required]] | 127 | + keyFilters: [filter ? filter.keyFilters : [], [Validators.required]], |
128 | + editable: [filter ? filter.editable : true] | ||
125 | }); | 129 | }); |
126 | return filterFormControl; | 130 | return filterFormControl; |
127 | } | 131 | } |
@@ -148,9 +152,9 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone | @@ -148,9 +152,9 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone | ||
148 | for (const widgetTitle of widgetsTitleList) { | 152 | for (const widgetTitle of widgetsTitleList) { |
149 | widgetsListHtml += '<br/>\'' + widgetTitle + '\''; | 153 | widgetsListHtml += '<br/>\'' + widgetTitle + '\''; |
150 | } | 154 | } |
151 | - const message = this.translate.instant('entity.unable-delete-filter-text', | 155 | + const message = this.translate.instant('filter.unable-delete-filter-text', |
152 | {filter: filter.filter, widgetsList: widgetsListHtml}); | 156 | {filter: filter.filter, widgetsList: widgetsListHtml}); |
153 | - this.dialogs.alert(this.translate.instant('entity.unable-delete-filter-title'), | 157 | + this.dialogs.alert(this.translate.instant('filter.unable-delete-filter-title'), |
154 | message, this.translate.instant('action.close'), true); | 158 | message, this.translate.instant('action.close'), true); |
155 | } else { | 159 | } else { |
156 | (this.filtersFormGroup.get('filters') as FormArray).removeAt(index); | 160 | (this.filtersFormGroup.get('filters') as FormArray).removeAt(index); |
@@ -190,8 +194,9 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone | @@ -190,8 +194,9 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone | ||
190 | .push(this.createFilterFormControl(result.id, result)); | 194 | .push(this.createFilterFormControl(result.id, result)); |
191 | } else { | 195 | } else { |
192 | const filterFormControl = (this.filtersFormGroup.get('filters') as FormArray).at(index); | 196 | const filterFormControl = (this.filtersFormGroup.get('filters') as FormArray).at(index); |
193 | - filterFormControl.get('filter').patchValue(filter.filter); | ||
194 | - filterFormControl.get('keyFilters').patchValue(filter.keyFilters); | 197 | + filterFormControl.get('filter').patchValue(result.filter); |
198 | + filterFormControl.get('editable').patchValue(result.editable); | ||
199 | + filterFormControl.get('keyFilters').patchValue(result.keyFilters); | ||
195 | } | 200 | } |
196 | this.filtersFormGroup.markAsDirty(); | 201 | this.filtersFormGroup.markAsDirty(); |
197 | } | 202 | } |
@@ -215,6 +220,7 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone | @@ -215,6 +220,7 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone | ||
215 | const filterId: string = filterValue.id; | 220 | const filterId: string = filterValue.id; |
216 | const filter: string = filterValue.filter; | 221 | const filter: string = filterValue.filter; |
217 | const keyFilters: Array<KeyFilterInfo> = filterValue.keyFilters; | 222 | const keyFilters: Array<KeyFilterInfo> = filterValue.keyFilters; |
223 | + const editable: boolean = filterValue.editable; | ||
218 | if (uniqueFilterList[filter]) { | 224 | if (uniqueFilterList[filter]) { |
219 | valid = false; | 225 | valid = false; |
220 | message = this.translate.instant('filter.duplicate-filter-error', {filter}); | 226 | message = this.translate.instant('filter.duplicate-filter-error', {filter}); |
@@ -225,7 +231,7 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone | @@ -225,7 +231,7 @@ export class FiltersDialogComponent extends DialogComponent<FiltersDialogCompone | ||
225 | break; | 231 | break; |
226 | } else { | 232 | } else { |
227 | uniqueFilterList[filter] = filter; | 233 | uniqueFilterList[filter] = filter; |
228 | - filters[filterId] = {id: filterId, filter, keyFilters}; | 234 | + filters[filterId] = {id: filterId, filter, keyFilters, editable}; |
229 | } | 235 | } |
230 | } | 236 | } |
231 | if (valid) { | 237 | if (valid) { |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2020 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<div fxLayout="column" class="mat-content mat-padding"> | ||
19 | + <div fxLayout="column" *ngFor="let filter of filtersInfo | keyvalue"> | ||
20 | + <div fxFlex fxLayout="row" fxLayoutAlign="start center"> | ||
21 | + <mat-label fxFlex>{{filter.value.filter}}</mat-label> | ||
22 | + <button mat-icon-button color="primary" | ||
23 | + style="min-width: 40px;" | ||
24 | + type="button" | ||
25 | + (click)="editFilter(filter.key, filter.value)" | ||
26 | + matTooltip="{{ 'filter.edit' | translate }}" | ||
27 | + matTooltipPosition="above"> | ||
28 | + <mat-icon>edit</mat-icon> | ||
29 | + </button> | ||
30 | + </div> | ||
31 | + <mat-divider></mat-divider> | ||
32 | + </div> | ||
33 | +</div> |
1 | +/** | ||
2 | + * Copyright © 2016-2020 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +:host { | ||
17 | + min-width: 300px; | ||
18 | + max-height: 150px; | ||
19 | + overflow-x: hidden; | ||
20 | + overflow-y: auto; | ||
21 | + background: #fff; | ||
22 | + border-radius: 4px; | ||
23 | + box-shadow: | ||
24 | + 0 7px 8px -4px rgba(0, 0, 0, .2), | ||
25 | + 0 13px 19px 2px rgba(0, 0, 0, .14), | ||
26 | + 0 5px 24px 4px rgba(0, 0, 0, .12); | ||
27 | + | ||
28 | + @media (min-height: 350px) { | ||
29 | + max-height: 250px; | ||
30 | + } | ||
31 | + | ||
32 | + .mat-content { | ||
33 | + background-color: #fff; | ||
34 | + } | ||
35 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2020 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Component, Inject, InjectionToken } from '@angular/core'; | ||
18 | +import { IAliasController } from '@core/api/widget-api.models'; | ||
19 | +import { Filter, FilterInfo } from '@shared/models/query/query.models'; | ||
20 | +import { MatDialog } from '@angular/material/dialog'; | ||
21 | +import { FilterDialogComponent, FilterDialogData } from '@home/components/filter/filter-dialog.component'; | ||
22 | +import { deepClone } from '@core/utils'; | ||
23 | + | ||
24 | +export const FILTER_EDIT_PANEL_DATA = new InjectionToken<any>('FiltersEditPanelData'); | ||
25 | + | ||
26 | +export interface FiltersEditPanelData { | ||
27 | + aliasController: IAliasController; | ||
28 | + filtersInfo: {[filterId: string]: FilterInfo}; | ||
29 | +} | ||
30 | + | ||
31 | +@Component({ | ||
32 | + selector: 'tb-filters-edit-panel', | ||
33 | + templateUrl: './filters-edit-panel.component.html', | ||
34 | + styleUrls: ['./filters-edit-panel.component.scss'] | ||
35 | +}) | ||
36 | +export class FiltersEditPanelComponent { | ||
37 | + | ||
38 | + filtersInfo: {[filterId: string]: FilterInfo}; | ||
39 | + | ||
40 | + constructor(@Inject(FILTER_EDIT_PANEL_DATA) public data: FiltersEditPanelData, | ||
41 | + private dialog: MatDialog) { | ||
42 | + this.filtersInfo = this.data.filtersInfo; | ||
43 | + } | ||
44 | + | ||
45 | + public editFilter(filterId: string, filter: FilterInfo) { | ||
46 | + const singleFilter: Filter = {id: filterId, ...deepClone(filter)}; | ||
47 | + this.dialog.open<FilterDialogComponent, FilterDialogData, | ||
48 | + Filter>(FilterDialogComponent, { | ||
49 | + disableClose: true, | ||
50 | + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], | ||
51 | + data: { | ||
52 | + isAdd: false, | ||
53 | + filters: [], | ||
54 | + filter: singleFilter, | ||
55 | + userMode: true | ||
56 | + } | ||
57 | + }).afterClosed().subscribe( | ||
58 | + (result) => { | ||
59 | + if (result) { | ||
60 | + this.filtersInfo[result.id] = result; | ||
61 | + this.data.aliasController.updateUserFilter(result); | ||
62 | + } | ||
63 | + }); | ||
64 | + } | ||
65 | +} |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2020 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<section class="tb-filters-edit" fxLayout="row" fxLayoutAlign="start center" *ngIf="hasEditableFilters"> | ||
19 | + <button mat-icon-button | ||
20 | + cdkOverlayOrigin #filtersEditPanelOrigin="cdkOverlayOrigin" | ||
21 | + (click)="openEditMode()" | ||
22 | + matTooltip="{{ 'filter.filters' | translate }}" | ||
23 | + [matTooltipPosition]="tooltipPosition"> | ||
24 | + <mat-icon>filter_list</mat-icon> | ||
25 | + </button> | ||
26 | + <span fxHide.lt-lg | ||
27 | + (click)="openEditMode()" | ||
28 | + matTooltip="{{ 'filter.filters' | translate }}" | ||
29 | + [matTooltipPosition]="tooltipPosition"> | ||
30 | + {{ 'filter.filters' | translate }} | ||
31 | + </span> | ||
32 | +</section> |
1 | +/** | ||
2 | + * Copyright © 2016-2020 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +@import "../../../../../scss/constants"; | ||
17 | + | ||
18 | +:host { | ||
19 | + section.tb-filters-edit { | ||
20 | + min-height: 32px; | ||
21 | + padding: 0 6px; | ||
22 | + | ||
23 | + @media #{$mat-lt-md} { | ||
24 | + padding: 0; | ||
25 | + } | ||
26 | + | ||
27 | + span { | ||
28 | + max-width: 200px; | ||
29 | + overflow: hidden; | ||
30 | + text-overflow: ellipsis; | ||
31 | + white-space: nowrap; | ||
32 | + pointer-events: all; | ||
33 | + cursor: pointer; | ||
34 | + } | ||
35 | + } | ||
36 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2020 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Component, Input, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; | ||
18 | +import { TooltipPosition } from '@angular/material/tooltip'; | ||
19 | +import { IAliasController } from '@core/api/widget-api.models'; | ||
20 | +import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; | ||
21 | +import { TranslateService } from '@ngx-translate/core'; | ||
22 | +import { Subscription } from 'rxjs'; | ||
23 | +import { BreakpointObserver } from '@angular/cdk/layout'; | ||
24 | +import { deepClone } from '@core/utils'; | ||
25 | +import { FilterInfo } from '@shared/models/query/query.models'; | ||
26 | +import { | ||
27 | + FILTER_EDIT_PANEL_DATA, | ||
28 | + FiltersEditPanelComponent, | ||
29 | + FiltersEditPanelData | ||
30 | +} from '@home/components/filter/filters-edit-panel.component'; | ||
31 | +import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; | ||
32 | + | ||
33 | +@Component({ | ||
34 | + selector: 'tb-filters-edit', | ||
35 | + templateUrl: './filters-edit.component.html', | ||
36 | + styleUrls: ['./filters-edit.component.scss'] | ||
37 | +}) | ||
38 | +export class FiltersEditComponent implements OnInit, OnDestroy { | ||
39 | + | ||
40 | + aliasControllerValue: IAliasController; | ||
41 | + | ||
42 | + @Input() | ||
43 | + set aliasController(aliasController: IAliasController) { | ||
44 | + this.aliasControllerValue = aliasController; | ||
45 | + this.setupAliasController(this.aliasControllerValue); | ||
46 | + } | ||
47 | + | ||
48 | + get aliasController(): IAliasController { | ||
49 | + return this.aliasControllerValue; | ||
50 | + } | ||
51 | + | ||
52 | + @Input() | ||
53 | + tooltipPosition: TooltipPosition = 'above'; | ||
54 | + | ||
55 | + @Input() disabled: boolean; | ||
56 | + | ||
57 | + @ViewChild('filtersEditPanelOrigin') filtersEditPanelOrigin: CdkOverlayOrigin; | ||
58 | + | ||
59 | + displayValue: string; | ||
60 | + filtersInfo: {[filterId: string]: FilterInfo} = {}; | ||
61 | + hasEditableFilters = false; | ||
62 | + | ||
63 | + private rxSubscriptions = new Array<Subscription>(); | ||
64 | + | ||
65 | + constructor(private translate: TranslateService, | ||
66 | + private overlay: Overlay, | ||
67 | + private breakpointObserver: BreakpointObserver, | ||
68 | + private viewContainerRef: ViewContainerRef) { | ||
69 | + } | ||
70 | + | ||
71 | + private setupAliasController(aliasController: IAliasController) { | ||
72 | + this.rxSubscriptions.forEach((subscription) => { | ||
73 | + subscription.unsubscribe(); | ||
74 | + }); | ||
75 | + this.rxSubscriptions.length = 0; | ||
76 | + if (aliasController) { | ||
77 | + this.rxSubscriptions.push(aliasController.filtersChanged.subscribe( | ||
78 | + () => { | ||
79 | + setTimeout(() => { | ||
80 | + this.updateFiltersInfo(); | ||
81 | + }, 0); | ||
82 | + } | ||
83 | + )); | ||
84 | + setTimeout(() => { | ||
85 | + this.updateFiltersInfo(); | ||
86 | + }, 0); | ||
87 | + } | ||
88 | + } | ||
89 | + | ||
90 | + ngOnInit(): void { | ||
91 | + } | ||
92 | + | ||
93 | + ngOnDestroy(): void { | ||
94 | + this.rxSubscriptions.forEach((subscription) => { | ||
95 | + subscription.unsubscribe(); | ||
96 | + }); | ||
97 | + this.rxSubscriptions.length = 0; | ||
98 | + } | ||
99 | + | ||
100 | + openEditMode() { | ||
101 | + if (this.disabled || !this.hasEditableFilters) { | ||
102 | + return; | ||
103 | + } | ||
104 | + const position = this.overlay.position(); | ||
105 | + const config = new OverlayConfig({ | ||
106 | + panelClass: 'tb-filters-edit-panel', | ||
107 | + backdropClass: 'cdk-overlay-transparent-backdrop', | ||
108 | + hasBackdrop: true, | ||
109 | + }); | ||
110 | + const connectedPosition: ConnectedPosition = { | ||
111 | + originX: 'start', | ||
112 | + originY: 'bottom', | ||
113 | + overlayX: 'start', | ||
114 | + overlayY: 'top' | ||
115 | + }; | ||
116 | + config.positionStrategy = position.flexibleConnectedTo(this.filtersEditPanelOrigin.elementRef) | ||
117 | + .withPositions([connectedPosition]); | ||
118 | + const overlayRef = this.overlay.create(config); | ||
119 | + overlayRef.backdropClick().subscribe(() => { | ||
120 | + overlayRef.dispose(); | ||
121 | + }); | ||
122 | + | ||
123 | + const injector = this._createFiltersEditPanelInjector( | ||
124 | + overlayRef, | ||
125 | + { | ||
126 | + aliasController: this.aliasController, | ||
127 | + filtersInfo: deepClone(this.filtersInfo) | ||
128 | + } | ||
129 | + ); | ||
130 | + overlayRef.attach(new ComponentPortal(FiltersEditPanelComponent, this.viewContainerRef, injector)); | ||
131 | + } | ||
132 | + | ||
133 | + private _createFiltersEditPanelInjector(overlayRef: OverlayRef, data: FiltersEditPanelData): PortalInjector { | ||
134 | + const injectionTokens = new WeakMap<any, any>([ | ||
135 | + [FILTER_EDIT_PANEL_DATA, data], | ||
136 | + [OverlayRef, overlayRef] | ||
137 | + ]); | ||
138 | + return new PortalInjector(this.viewContainerRef.injector, injectionTokens); | ||
139 | + } | ||
140 | + | ||
141 | + private updateFiltersInfo() { | ||
142 | + const allFilters = this.aliasController.getFilters(); | ||
143 | + this.filtersInfo = {}; | ||
144 | + this.hasEditableFilters = false; | ||
145 | + for (const filterId of Object.keys(allFilters)) { | ||
146 | + const filterInfo = this.aliasController.getFilterInfo(filterId); | ||
147 | + if (filterInfo && filterInfo.editable) { | ||
148 | + this.filtersInfo[filterId] = deepClone(filterInfo); | ||
149 | + this.hasEditableFilters = true; | ||
150 | + } | ||
151 | + } | ||
152 | + } | ||
153 | + | ||
154 | +} |
@@ -15,9 +15,9 @@ | @@ -15,9 +15,9 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<form [formGroup]="keyFilterFormGroup" (ngSubmit)="save()"> | 18 | +<form [formGroup]="keyFilterFormGroup" (ngSubmit)="save()" style="width: 700px;"> |
19 | <mat-toolbar color="primary"> | 19 | <mat-toolbar color="primary"> |
20 | - <h2 translate>{{data.isAdd ? 'filter.add-key-filter' : 'filter.edit-key-filter'}}</h2> | 20 | + <h2>{{data.userMode ? data.keyFilter.key.key : ((data.isAdd ? 'filter.add-key-filter' : 'filter.edit-key-filter') | translate)}}</h2> |
21 | <span fxFlex></span> | 21 | <span fxFlex></span> |
22 | <button mat-icon-button | 22 | <button mat-icon-button |
23 | (click)="cancel()" | 23 | (click)="cancel()" |
@@ -27,16 +27,16 @@ | @@ -27,16 +27,16 @@ | ||
27 | </mat-toolbar> | 27 | </mat-toolbar> |
28 | <div mat-dialog-content> | 28 | <div mat-dialog-content> |
29 | <fieldset [disabled]="isLoading$ | async" fxLayout="column"> | 29 | <fieldset [disabled]="isLoading$ | async" fxLayout="column"> |
30 | - <section fxLayout="row" fxLayoutGap="8px"> | ||
31 | - <section fxFlex="60" fxLayout="row" formGroupName="key" fxLayoutGap="8px"> | ||
32 | - <mat-form-field fxFlex="40" class="mat-block"> | 30 | + <section fxLayout="row" fxLayoutGap="8px" class="entity-key" [fxShow]="!data.userMode"> |
31 | + <section fxFlex="70" fxLayout="row" formGroupName="key" fxLayoutGap="8px"> | ||
32 | + <mat-form-field fxFlex="60" class="mat-block"> | ||
33 | <mat-label translate>filter.key-name</mat-label> | 33 | <mat-label translate>filter.key-name</mat-label> |
34 | <input matInput required formControlName="key"> | 34 | <input matInput required formControlName="key"> |
35 | <mat-error *ngIf="keyFilterFormGroup.get('key.key').hasError('required')"> | 35 | <mat-error *ngIf="keyFilterFormGroup.get('key.key').hasError('required')"> |
36 | {{ 'filter.key-name-required' | translate }} | 36 | {{ 'filter.key-name-required' | translate }} |
37 | </mat-error> | 37 | </mat-error> |
38 | </mat-form-field> | 38 | </mat-form-field> |
39 | - <mat-form-field fxFlex="60" class="mat-block"> | 39 | + <mat-form-field fxFlex="40" class="mat-block"> |
40 | <mat-label translate>filter.key-type.key-type</mat-label> | 40 | <mat-label translate>filter.key-type.key-type</mat-label> |
41 | <mat-select required formControlName="type"> | 41 | <mat-select required formControlName="type"> |
42 | <mat-option *ngFor="let type of entityKeyTypes" [value]="type"> | 42 | <mat-option *ngFor="let type of entityKeyTypes" [value]="type"> |
@@ -45,15 +45,15 @@ | @@ -45,15 +45,15 @@ | ||
45 | </mat-select> | 45 | </mat-select> |
46 | </mat-form-field> | 46 | </mat-form-field> |
47 | </section> | 47 | </section> |
48 | - <mat-form-field fxFlex="40" class="mat-block"> | 48 | + <mat-form-field fxFlex="30" class="mat-block"> |
49 | <mat-label translate>filter.value-type.value-type</mat-label> | 49 | <mat-label translate>filter.value-type.value-type</mat-label> |
50 | <mat-select matInput formControlName="valueType"> | 50 | <mat-select matInput formControlName="valueType"> |
51 | <mat-select-trigger> | 51 | <mat-select-trigger> |
52 | - <mat-icon svgIcon="{{ entityKeyValueTypes.get(keyFilterFormGroup.get('valueType').value)?.icon }}"></mat-icon> | 52 | + <mat-icon class="tb-mat-18" svgIcon="{{ entityKeyValueTypes.get(keyFilterFormGroup.get('valueType').value)?.icon }}"></mat-icon> |
53 | <span>{{ entityKeyValueTypes.get(keyFilterFormGroup.get('valueType').value)?.name | translate }}</span> | 53 | <span>{{ entityKeyValueTypes.get(keyFilterFormGroup.get('valueType').value)?.name | translate }}</span> |
54 | </mat-select-trigger> | 54 | </mat-select-trigger> |
55 | <mat-option *ngFor="let valueType of entityKeyValueTypesKeys" [value]="valueType"> | 55 | <mat-option *ngFor="let valueType of entityKeyValueTypesKeys" [value]="valueType"> |
56 | - <mat-icon svgIcon="{{ entityKeyValueTypes.get(entityKeyValueTypeEnum[valueType]).icon }}"></mat-icon> | 56 | + <mat-icon class="tb-mat-18" svgIcon="{{ entityKeyValueTypes.get(entityKeyValueTypeEnum[valueType]).icon }}"></mat-icon> |
57 | <span>{{ entityKeyValueTypes.get(entityKeyValueTypeEnum[valueType]).name | translate }}</span> | 57 | <span>{{ entityKeyValueTypes.get(entityKeyValueTypeEnum[valueType]).name | translate }}</span> |
58 | </mat-option> | 58 | </mat-option> |
59 | </mat-select> | 59 | </mat-select> |
@@ -72,7 +72,7 @@ | @@ -72,7 +72,7 @@ | ||
72 | <div mat-dialog-actions fxLayoutAlign="end center"> | 72 | <div mat-dialog-actions fxLayoutAlign="end center"> |
73 | <button mat-raised-button color="primary" | 73 | <button mat-raised-button color="primary" |
74 | type="submit" | 74 | type="submit" |
75 | - [disabled]="(isLoading$ | async) || keyFilterFormGroup.invalid"> | 75 | + [disabled]="(isLoading$ | async) || keyFilterFormGroup.invalid || !keyFilterFormGroup.dirty"> |
76 | {{ (data.isAdd ? 'action.add' : 'action.update') | translate }} | 76 | {{ (data.isAdd ? 'action.add' : 'action.update') | translate }} |
77 | </button> | 77 | </button> |
78 | <button mat-button color="primary" | 78 | <button mat-button color="primary" |
1 | +/** | ||
2 | + * Copyright © 2016-2020 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +:host ::ng-deep { | ||
17 | + .entity-key { | ||
18 | + mat-form-field { | ||
19 | + .mat-form-field-wrapper { | ||
20 | + .mat-form-field-infix { | ||
21 | + width: auto; | ||
22 | + } | ||
23 | + } | ||
24 | + } | ||
25 | + } | ||
26 | +} |
@@ -27,8 +27,10 @@ import { | @@ -27,8 +27,10 @@ import { | ||
27 | entityKeyTypeTranslationMap, | 27 | entityKeyTypeTranslationMap, |
28 | EntityKeyValueType, | 28 | EntityKeyValueType, |
29 | entityKeyValueTypesMap, | 29 | entityKeyValueTypesMap, |
30 | - KeyFilterInfo | 30 | + KeyFilterInfo, KeyFilterPredicate |
31 | } from '@shared/models/query/query.models'; | 31 | } from '@shared/models/query/query.models'; |
32 | +import { DialogService } from '@core/services/dialog.service'; | ||
33 | +import { TranslateService } from '@ngx-translate/core'; | ||
32 | 34 | ||
33 | export interface KeyFilterDialogData { | 35 | export interface KeyFilterDialogData { |
34 | keyFilter: KeyFilterInfo; | 36 | keyFilter: KeyFilterInfo; |
@@ -40,7 +42,7 @@ export interface KeyFilterDialogData { | @@ -40,7 +42,7 @@ export interface KeyFilterDialogData { | ||
40 | selector: 'tb-key-filter-dialog', | 42 | selector: 'tb-key-filter-dialog', |
41 | templateUrl: './key-filter-dialog.component.html', | 43 | templateUrl: './key-filter-dialog.component.html', |
42 | providers: [{provide: ErrorStateMatcher, useExisting: KeyFilterDialogComponent}], | 44 | providers: [{provide: ErrorStateMatcher, useExisting: KeyFilterDialogComponent}], |
43 | - styleUrls: [] | 45 | + styleUrls: ['./key-filter-dialog.component.scss'] |
44 | }) | 46 | }) |
45 | export class KeyFilterDialogComponent extends | 47 | export class KeyFilterDialogComponent extends |
46 | DialogComponent<KeyFilterDialogComponent, KeyFilterInfo> | 48 | DialogComponent<KeyFilterDialogComponent, KeyFilterInfo> |
@@ -65,6 +67,8 @@ export class KeyFilterDialogComponent extends | @@ -65,6 +67,8 @@ export class KeyFilterDialogComponent extends | ||
65 | @Inject(MAT_DIALOG_DATA) public data: KeyFilterDialogData, | 67 | @Inject(MAT_DIALOG_DATA) public data: KeyFilterDialogData, |
66 | @SkipSelf() private errorStateMatcher: ErrorStateMatcher, | 68 | @SkipSelf() private errorStateMatcher: ErrorStateMatcher, |
67 | public dialogRef: MatDialogRef<KeyFilterDialogComponent, KeyFilterInfo>, | 69 | public dialogRef: MatDialogRef<KeyFilterDialogComponent, KeyFilterInfo>, |
70 | + private dialogs: DialogService, | ||
71 | + private translate: TranslateService, | ||
68 | private fb: FormBuilder) { | 72 | private fb: FormBuilder) { |
69 | super(store, router, dialogRef); | 73 | super(store, router, dialogRef); |
70 | 74 | ||
@@ -80,6 +84,22 @@ export class KeyFilterDialogComponent extends | @@ -80,6 +84,22 @@ export class KeyFilterDialogComponent extends | ||
80 | predicates: [this.data.keyFilter.predicates, [Validators.required]] | 84 | predicates: [this.data.keyFilter.predicates, [Validators.required]] |
81 | } | 85 | } |
82 | ); | 86 | ); |
87 | + this.keyFilterFormGroup.get('valueType').valueChanges.subscribe((valueType: EntityKeyValueType) => { | ||
88 | + const prevValue: EntityKeyValueType = this.keyFilterFormGroup.value.valueType; | ||
89 | + const predicates: KeyFilterPredicate[] = this.keyFilterFormGroup.get('predicates').value; | ||
90 | + if (prevValue && prevValue !== valueType && predicates && predicates.length) { | ||
91 | + this.dialogs.confirm(this.translate.instant('filter.key-value-type-change-title'), | ||
92 | + this.translate.instant('filter.key-value-type-change-message')).subscribe( | ||
93 | + (result) => { | ||
94 | + if (result) { | ||
95 | + this.keyFilterFormGroup.get('predicates').setValue([]); | ||
96 | + } else { | ||
97 | + this.keyFilterFormGroup.get('valueType').setValue(prevValue, {emitEvent: false}); | ||
98 | + } | ||
99 | + } | ||
100 | + ); | ||
101 | + } | ||
102 | + }); | ||
83 | } | 103 | } |
84 | 104 | ||
85 | ngOnInit(): void { | 105 | ngOnInit(): void { |
@@ -16,38 +16,65 @@ | @@ -16,38 +16,65 @@ | ||
16 | 16 | ||
17 | --> | 17 | --> |
18 | <section fxLayout="column" [formGroup]="keyFilterListFormGroup"> | 18 | <section fxLayout="column" [formGroup]="keyFilterListFormGroup"> |
19 | - <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" style="max-height: 40px;" | ||
20 | - formArrayName="keyFilters" | ||
21 | - *ngFor="let keyFilterControl of keyFiltersFormArray().controls; let $index = index"> | ||
22 | - <span>{{ keyFilterControl.value.key.key }}</span> | ||
23 | - <span>{{ keyFilterControl.value.key.type }}</span> | ||
24 | - <button mat-icon-button color="primary" | ||
25 | - type="button" | ||
26 | - (click)="editKeyFilter($index)" | ||
27 | - matTooltip="{{ 'filter.edit-key-filter' | translate }}" | ||
28 | - matTooltipPosition="above"> | ||
29 | - <mat-icon>edit</mat-icon> | ||
30 | - </button> | ||
31 | - <button mat-icon-button color="primary" | ||
32 | - [fxShow]="!disabled && !userMode" | ||
33 | - type="button" | ||
34 | - (click)="removeKeyFilter($index)" | ||
35 | - matTooltip="{{ 'filter.remove-key-filter' | translate }}" | ||
36 | - matTooltipPosition="above"> | ||
37 | - <mat-icon>close</mat-icon> | ||
38 | - </button> | ||
39 | - </div> | ||
40 | - <span [fxShow]="!keyFiltersFormArray().length" | ||
41 | - fxLayoutAlign="center center" [ngClass]="{'disabled': disabled}" | ||
42 | - class="no-data-found" translate>filter.no-key-filters</span> | ||
43 | - <div style="margin-top: 8px;"> | ||
44 | - <button mat-button mat-raised-button color="primary" | ||
45 | - [fxShow]="!disabled && !userMode" | ||
46 | - (click)="addKeyFilter()" | ||
47 | - type="button" | ||
48 | - matTooltip="{{ 'filter.add-key-filter' | translate }}" | ||
49 | - matTooltipPosition="above"> | ||
50 | - {{ 'filter.add-key-filter' | translate }} | ||
51 | - </button> | ||
52 | - </div> | 19 | + <mat-expansion-panel [expanded]="true"> |
20 | + <mat-expansion-panel-header> | ||
21 | + <mat-panel-title> | ||
22 | + <div translate>filter.key-filters</div> | ||
23 | + </mat-panel-title> | ||
24 | + </mat-expansion-panel-header> | ||
25 | + <div fxLayout="row"> | ||
26 | + <span fxFlex="8"></span> | ||
27 | + <div fxLayout="row" fxLayoutAlign="start center" fxFlex="92"> | ||
28 | + <label fxFlex translate class="tb-title no-padding">filter.key-name</label> | ||
29 | + <label fxFlex translate class="tb-title no-padding">filter.key-type.key-type</label> | ||
30 | + <span [fxShow]="!disabled && !userMode" style="min-width: 80px;"> </span> | ||
31 | + <span [fxShow]="disabled || userMode" style="min-width: 40px;"> </span> | ||
32 | + </div> | ||
33 | + </div> | ||
34 | + <mat-divider></mat-divider> | ||
35 | + <div class="key-filter-list"> | ||
36 | + <div fxLayout="row" fxLayoutAlign="start center" style="max-height: 40px;" | ||
37 | + formArrayName="keyFilters" | ||
38 | + *ngFor="let keyFilterControl of keyFiltersFormArray().controls; let $index = index"> | ||
39 | + <div fxFlex="8" class="filters-operation"> | ||
40 | + <span *ngIf="$index > 0" translate>filter.operation.and</span> | ||
41 | + </div> | ||
42 | + <div fxLayout="column" fxFlex="92"> | ||
43 | + <div fxLayout="row" fxLayoutAlign="start center" fxFlex> | ||
44 | + <div fxFlex>{{ keyFilterControl.value.key.key }}</div> | ||
45 | + <div fxFlex translate>{{ entityKeyTypeTranslations.get(keyFilterControl.value.key.type) }}</div> | ||
46 | + <button mat-icon-button color="primary" | ||
47 | + type="button" | ||
48 | + (click)="editKeyFilter($index)" | ||
49 | + matTooltip="{{ 'filter.edit-key-filter' | translate }}" | ||
50 | + matTooltipPosition="above"> | ||
51 | + <mat-icon>edit</mat-icon> | ||
52 | + </button> | ||
53 | + <button mat-icon-button color="primary" | ||
54 | + [fxShow]="!disabled && !userMode" | ||
55 | + type="button" | ||
56 | + (click)="removeKeyFilter($index)" | ||
57 | + matTooltip="{{ 'filter.remove-key-filter' | translate }}" | ||
58 | + matTooltipPosition="above"> | ||
59 | + <mat-icon>close</mat-icon> | ||
60 | + </button> | ||
61 | + </div> | ||
62 | + <mat-divider></mat-divider> | ||
63 | + </div> | ||
64 | + </div> | ||
65 | + <span [fxShow]="!keyFiltersFormArray().length" | ||
66 | + fxLayoutAlign="center center" [ngClass]="{'disabled': disabled}" | ||
67 | + class="no-data-found" translate>filter.no-key-filters</span> | ||
68 | + </div> | ||
69 | + <div style="margin-top: 16px;"> | ||
70 | + <button mat-button mat-raised-button color="primary" | ||
71 | + [fxShow]="!disabled && !userMode" | ||
72 | + (click)="addKeyFilter()" | ||
73 | + type="button" | ||
74 | + matTooltip="{{ 'filter.add-key-filter' | translate }}" | ||
75 | + matTooltipPosition="above"> | ||
76 | + {{ 'filter.add-key-filter' | translate }} | ||
77 | + </button> | ||
78 | + </div> | ||
79 | + </mat-expansion-panel> | ||
53 | </section> | 80 | </section> |
1 | +/** | ||
2 | + * Copyright © 2016-2020 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +:host { | ||
17 | + .key-filter-list { | ||
18 | + overflow: auto; | ||
19 | + max-height: 300px; | ||
20 | + .no-data-found { | ||
21 | + height: 50px; | ||
22 | + } | ||
23 | + } | ||
24 | + .filters-operation { | ||
25 | + margin-top: -40px; | ||
26 | + color: #666; | ||
27 | + font-weight: 500; | ||
28 | + } | ||
29 | +} |
@@ -25,7 +25,7 @@ import { | @@ -25,7 +25,7 @@ import { | ||
25 | Validators | 25 | Validators |
26 | } from '@angular/forms'; | 26 | } from '@angular/forms'; |
27 | import { Observable, Subscription } from 'rxjs'; | 27 | import { Observable, Subscription } from 'rxjs'; |
28 | -import { EntityKeyType, KeyFilterInfo } from '@shared/models/query/query.models'; | 28 | +import { EntityKeyType, entityKeyTypeTranslationMap, KeyFilterInfo } from '@shared/models/query/query.models'; |
29 | import { MatDialog } from '@angular/material/dialog'; | 29 | import { MatDialog } from '@angular/material/dialog'; |
30 | import { deepClone } from '@core/utils'; | 30 | import { deepClone } from '@core/utils'; |
31 | import { KeyFilterDialogComponent, KeyFilterDialogData } from '@home/components/filter/key-filter-dialog.component'; | 31 | import { KeyFilterDialogComponent, KeyFilterDialogData } from '@home/components/filter/key-filter-dialog.component'; |
@@ -33,7 +33,7 @@ import { KeyFilterDialogComponent, KeyFilterDialogData } from '@home/components/ | @@ -33,7 +33,7 @@ import { KeyFilterDialogComponent, KeyFilterDialogData } from '@home/components/ | ||
33 | @Component({ | 33 | @Component({ |
34 | selector: 'tb-key-filter-list', | 34 | selector: 'tb-key-filter-list', |
35 | templateUrl: './key-filter-list.component.html', | 35 | templateUrl: './key-filter-list.component.html', |
36 | - styleUrls: [], | 36 | + styleUrls: ['./key-filter-list.component.scss'], |
37 | providers: [ | 37 | providers: [ |
38 | { | 38 | { |
39 | provide: NG_VALUE_ACCESSOR, | 39 | provide: NG_VALUE_ACCESSOR, |
@@ -50,6 +50,8 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit { | @@ -50,6 +50,8 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit { | ||
50 | 50 | ||
51 | keyFilterListFormGroup: FormGroup; | 51 | keyFilterListFormGroup: FormGroup; |
52 | 52 | ||
53 | + entityKeyTypeTranslations = entityKeyTypeTranslationMap; | ||
54 | + | ||
53 | private propagateChange = null; | 55 | private propagateChange = null; |
54 | 56 | ||
55 | private valueChangeSubscription: Subscription = null; | 57 | private valueChangeSubscription: Subscription = null; |
@@ -15,17 +15,18 @@ | @@ -15,17 +15,18 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<div fxLayout="row" [formGroup]="numericFilterPredicateFormGroup"> | ||
19 | - <mat-form-field class="mat-block"> | ||
20 | - <mat-label translate>filter.operation.operation</mat-label> | ||
21 | - <mat-select required formControlName="operation"> | 18 | +<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="numericFilterPredicateFormGroup"> |
19 | + <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block"> | ||
20 | + <mat-label></mat-label> | ||
21 | + <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}"> | ||
22 | <mat-option *ngFor="let operation of numericOperations" [value]="operation"> | 22 | <mat-option *ngFor="let operation of numericOperations" [value]="operation"> |
23 | {{numericOperationTranslations.get(numericOperationEnum[operation]) | translate}} | 23 | {{numericOperationTranslations.get(numericOperationEnum[operation]) | translate}} |
24 | </mat-option> | 24 | </mat-option> |
25 | </mat-select> | 25 | </mat-select> |
26 | </mat-form-field> | 26 | </mat-form-field> |
27 | - <mat-form-field class="mat-block"> | ||
28 | - <mat-label translate>filter.value</mat-label> | ||
29 | - <input required type="number" matInput formControlName="value"> | 27 | + <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block"> |
28 | + <mat-label></mat-label> | ||
29 | + <input required type="number" matInput formControlName="value" | ||
30 | + placeholder="{{'filter.value' | translate}}"> | ||
30 | </mat-form-field> | 31 | </mat-form-field> |
31 | </div> | 32 | </div> |
@@ -24,7 +24,7 @@ import { isDefined } from '@core/utils'; | @@ -24,7 +24,7 @@ import { isDefined } from '@core/utils'; | ||
24 | @Component({ | 24 | @Component({ |
25 | selector: 'tb-numeric-filter-predicate', | 25 | selector: 'tb-numeric-filter-predicate', |
26 | templateUrl: './numeric-filter-predicate.component.html', | 26 | templateUrl: './numeric-filter-predicate.component.html', |
27 | - styleUrls: [], | 27 | + styleUrls: ['./filter-predicate.scss'], |
28 | providers: [ | 28 | providers: [ |
29 | { | 29 | { |
30 | provide: NG_VALUE_ACCESSOR, | 30 | provide: NG_VALUE_ACCESSOR, |
@@ -15,20 +15,21 @@ | @@ -15,20 +15,21 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<div fxLayout="row" [formGroup]="stringFilterPredicateFormGroup"> | ||
19 | - <mat-form-field class="mat-block"> | ||
20 | - <mat-label translate>filter.operation.operation</mat-label> | ||
21 | - <mat-select required formControlName="operation"> | ||
22 | - <mat-option *ngFor="let operation of stringOperations" [value]="operation"> | ||
23 | - {{stringOperationTranslations.get(stringOperationEnum[operation]) | translate}} | ||
24 | - </mat-option> | ||
25 | - </mat-select> | ||
26 | - </mat-form-field> | ||
27 | - <mat-checkbox formControlName="ignoreCase"> | ||
28 | - {{ 'filter.ignore-case' | translate }} | ||
29 | - </mat-checkbox> | ||
30 | - <mat-form-field class="mat-block"> | ||
31 | - <mat-label translate>filter.value</mat-label> | ||
32 | - <input matInput formControlName="value"> | 18 | +<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="stringFilterPredicateFormGroup"> |
19 | + <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> | ||
20 | + <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block"> | ||
21 | + <mat-label></mat-label> | ||
22 | + <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}"> | ||
23 | + <mat-option *ngFor="let operation of stringOperations" [value]="operation"> | ||
24 | + {{stringOperationTranslations.get(stringOperationEnum[operation]) | translate}} | ||
25 | + </mat-option> | ||
26 | + </mat-select> | ||
27 | + </mat-form-field> | ||
28 | + <mat-checkbox fxLayout="row" fxLayoutAlign="center" formControlName="ignoreCase" style="min-width: 70px;"> | ||
29 | + </mat-checkbox> | ||
30 | + </div> | ||
31 | + <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block"> | ||
32 | + <mat-label></mat-label> | ||
33 | + <input matInput formControlName="value" placeholder="{{'filter.value' | translate}}"> | ||
33 | </mat-form-field> | 34 | </mat-form-field> |
34 | </div> | 35 | </div> |
@@ -26,7 +26,7 @@ import { | @@ -26,7 +26,7 @@ import { | ||
26 | @Component({ | 26 | @Component({ |
27 | selector: 'tb-string-filter-predicate', | 27 | selector: 'tb-string-filter-predicate', |
28 | templateUrl: './string-filter-predicate.component.html', | 28 | templateUrl: './string-filter-predicate.component.html', |
29 | - styleUrls: [], | 29 | + styleUrls: ['./filter-predicate.scss'], |
30 | providers: [ | 30 | providers: [ |
31 | { | 31 | { |
32 | provide: NG_VALUE_ACCESSOR, | 32 | provide: NG_VALUE_ACCESSOR, |
@@ -78,6 +78,8 @@ import { KeyFilterDialogComponent } from '@home/components/filter/key-filter-dia | @@ -78,6 +78,8 @@ import { KeyFilterDialogComponent } from '@home/components/filter/key-filter-dia | ||
78 | import { FiltersDialogComponent } from '@home/components/filter/filters-dialog.component'; | 78 | import { FiltersDialogComponent } from '@home/components/filter/filters-dialog.component'; |
79 | import { FilterDialogComponent } from '@home/components/filter/filter-dialog.component'; | 79 | import { FilterDialogComponent } from '@home/components/filter/filter-dialog.component'; |
80 | import { FilterSelectComponent } from './filter/filter-select.component'; | 80 | import { FilterSelectComponent } from './filter/filter-select.component'; |
81 | +import { FiltersEditComponent } from '@home/components/filter/filters-edit.component'; | ||
82 | +import { FiltersEditPanelComponent } from '@home/components/filter/filters-edit-panel.component'; | ||
81 | 83 | ||
82 | @NgModule({ | 84 | @NgModule({ |
83 | declarations: | 85 | declarations: |
@@ -138,7 +140,9 @@ import { FilterSelectComponent } from './filter/filter-select.component'; | @@ -138,7 +140,9 @@ import { FilterSelectComponent } from './filter/filter-select.component'; | ||
138 | KeyFilterDialogComponent, | 140 | KeyFilterDialogComponent, |
139 | FilterDialogComponent, | 141 | FilterDialogComponent, |
140 | FiltersDialogComponent, | 142 | FiltersDialogComponent, |
141 | - FilterSelectComponent | 143 | + FilterSelectComponent, |
144 | + FiltersEditComponent, | ||
145 | + FiltersEditPanelComponent | ||
142 | ], | 146 | ], |
143 | imports: [ | 147 | imports: [ |
144 | CommonModule, | 148 | CommonModule, |
@@ -192,7 +196,8 @@ import { FilterSelectComponent } from './filter/filter-select.component'; | @@ -192,7 +196,8 @@ import { FilterSelectComponent } from './filter/filter-select.component'; | ||
192 | KeyFilterDialogComponent, | 196 | KeyFilterDialogComponent, |
193 | FilterDialogComponent, | 197 | FilterDialogComponent, |
194 | FiltersDialogComponent, | 198 | FiltersDialogComponent, |
195 | - FilterSelectComponent | 199 | + FilterSelectComponent, |
200 | + FiltersEditComponent | ||
196 | ], | 201 | ], |
197 | providers: [ | 202 | providers: [ |
198 | WidgetComponentService, | 203 | WidgetComponentService, |
@@ -721,7 +721,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | @@ -721,7 +721,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | ||
721 | } | 721 | } |
722 | 722 | ||
723 | private createFilter(filter: string): Observable<Filter> { | 723 | private createFilter(filter: string): Observable<Filter> { |
724 | - const singleFilter: Filter = {id: null, filter, keyFilters: []}; | 724 | + const singleFilter: Filter = {id: null, filter, keyFilters: [], editable: true}; |
725 | return this.dialog.open<FilterDialogComponent, FilterDialogData, | 725 | return this.dialog.open<FilterDialogComponent, FilterDialogData, |
726 | Filter>(FilterDialogComponent, { | 726 | Filter>(FilterDialogComponent, { |
727 | disableClose: true, | 727 | disableClose: true, |
@@ -91,6 +91,10 @@ | @@ -91,6 +91,10 @@ | ||
91 | aggregation="true" | 91 | aggregation="true" |
92 | [(ngModel)]="dashboardCtx.dashboardTimewindow"> | 92 | [(ngModel)]="dashboardCtx.dashboardTimewindow"> |
93 | </tb-timewindow> | 93 | </tb-timewindow> |
94 | + <tb-filters-edit [fxShow]="!isEdit && displayFilters()" | ||
95 | + tooltipPosition="below" | ||
96 | + [aliasController]="dashboardCtx.aliasController"> | ||
97 | + </tb-filters-edit> | ||
94 | <tb-aliases-entity-select [fxShow]="!isEdit && displayEntitiesSelect()" | 98 | <tb-aliases-entity-select [fxShow]="!isEdit && displayEntitiesSelect()" |
95 | tooltipPosition="below" | 99 | tooltipPosition="below" |
96 | [aliasController]="dashboardCtx.aliasController"> | 100 | [aliasController]="dashboardCtx.aliasController"> |
@@ -410,6 +410,15 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC | @@ -410,6 +410,15 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC | ||
410 | } | 410 | } |
411 | } | 411 | } |
412 | 412 | ||
413 | + public displayFilters(): boolean { | ||
414 | + if (this.dashboard.configuration.settings && | ||
415 | + isDefined(this.dashboard.configuration.settings.showFilters)) { | ||
416 | + return this.dashboard.configuration.settings.showFilters; | ||
417 | + } else { | ||
418 | + return true; | ||
419 | + } | ||
420 | + } | ||
421 | + | ||
413 | public showRightLayoutSwitch(): boolean { | 422 | public showRightLayoutSwitch(): boolean { |
414 | return this.isMobile && this.layouts.right.show; | 423 | return this.isMobile && this.layouts.right.show; |
415 | } | 424 | } |
@@ -59,6 +59,9 @@ | @@ -59,6 +59,9 @@ | ||
59 | <mat-checkbox fxFlex formControlName="showEntitiesSelect"> | 59 | <mat-checkbox fxFlex formControlName="showEntitiesSelect"> |
60 | {{ 'dashboard.display-entities-selection' | translate }} | 60 | {{ 'dashboard.display-entities-selection' | translate }} |
61 | </mat-checkbox> | 61 | </mat-checkbox> |
62 | + <mat-checkbox fxFlex formControlName="showFilters"> | ||
63 | + {{ 'dashboard.display-filters' | translate }} | ||
64 | + </mat-checkbox> | ||
62 | <mat-checkbox fxFlex formControlName="showDashboardTimewindow"> | 65 | <mat-checkbox fxFlex formControlName="showDashboardTimewindow"> |
63 | {{ 'dashboard.display-dashboard-timewindow' | translate }} | 66 | {{ 'dashboard.display-dashboard-timewindow' | translate }} |
64 | </mat-checkbox> | 67 | </mat-checkbox> |
@@ -78,6 +78,7 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS | @@ -78,6 +78,7 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS | ||
78 | titleColor: [isUndefined(this.settings.titleColor) ? 'rgba(0,0,0,0.870588)' : this.settings.titleColor, []], | 78 | titleColor: [isUndefined(this.settings.titleColor) ? 'rgba(0,0,0,0.870588)' : this.settings.titleColor, []], |
79 | showDashboardsSelect: [isUndefined(this.settings.showDashboardsSelect) ? true : this.settings.showDashboardsSelect, []], | 79 | showDashboardsSelect: [isUndefined(this.settings.showDashboardsSelect) ? true : this.settings.showDashboardsSelect, []], |
80 | showEntitiesSelect: [isUndefined(this.settings.showEntitiesSelect) ? true : this.settings.showEntitiesSelect, []], | 80 | showEntitiesSelect: [isUndefined(this.settings.showEntitiesSelect) ? true : this.settings.showEntitiesSelect, []], |
81 | + showFilters: [isUndefined(this.settings.showFilters) ? true : this.settings.showFilters, []], | ||
81 | showDashboardTimewindow: [isUndefined(this.settings.showDashboardTimewindow) ? true : this.settings.showDashboardTimewindow, []], | 82 | showDashboardTimewindow: [isUndefined(this.settings.showDashboardTimewindow) ? true : this.settings.showDashboardTimewindow, []], |
82 | showDashboardExport: [isUndefined(this.settings.showDashboardExport) ? true : this.settings.showDashboardExport, []] | 83 | showDashboardExport: [isUndefined(this.settings.showDashboardExport) ? true : this.settings.showDashboardExport, []] |
83 | }); | 84 | }); |
@@ -85,6 +85,7 @@ export interface DashboardSettings { | @@ -85,6 +85,7 @@ export interface DashboardSettings { | ||
85 | showTitle?: boolean; | 85 | showTitle?: boolean; |
86 | showDashboardsSelect?: boolean; | 86 | showDashboardsSelect?: boolean; |
87 | showEntitiesSelect?: boolean; | 87 | showEntitiesSelect?: boolean; |
88 | + showFilters?: boolean; | ||
88 | showDashboardTimewindow?: boolean; | 89 | showDashboardTimewindow?: boolean; |
89 | showDashboardExport?: boolean; | 90 | showDashboardExport?: boolean; |
90 | toolbarAlwaysOpen?: boolean; | 91 | toolbarAlwaysOpen?: boolean; |
@@ -248,6 +248,7 @@ export interface KeyFilterInfo { | @@ -248,6 +248,7 @@ export interface KeyFilterInfo { | ||
248 | 248 | ||
249 | export interface FilterInfo { | 249 | export interface FilterInfo { |
250 | filter: string; | 250 | filter: string; |
251 | + editable: boolean; | ||
251 | keyFilters: Array<KeyFilterInfo>; | 252 | keyFilters: Array<KeyFilterInfo>; |
252 | } | 253 | } |
253 | 254 |
@@ -549,6 +549,7 @@ | @@ -549,6 +549,7 @@ | ||
549 | "title-color": "Title color", | 549 | "title-color": "Title color", |
550 | "display-dashboards-selection": "Display dashboards selection", | 550 | "display-dashboards-selection": "Display dashboards selection", |
551 | "display-entities-selection": "Display entities selection", | 551 | "display-entities-selection": "Display entities selection", |
552 | + "display-filters": "Display filters", | ||
552 | "display-dashboard-timewindow": "Display timewindow", | 553 | "display-dashboard-timewindow": "Display timewindow", |
553 | "display-dashboard-export": "Display export", | 554 | "display-dashboard-export": "Display export", |
554 | "import": "Import dashboard", | 555 | "import": "Import dashboard", |
@@ -1166,6 +1167,7 @@ | @@ -1166,6 +1167,7 @@ | ||
1166 | "duplicate-filter-error": "Duplicate filter found '{{filter}}'.<br>Filters must be unique within the dashboard.", | 1167 | "duplicate-filter-error": "Duplicate filter found '{{filter}}'.<br>Filters must be unique within the dashboard.", |
1167 | "missing-key-filters-error": "Key filters is missing for filter '{{filter}}'.", | 1168 | "missing-key-filters-error": "Key filters is missing for filter '{{filter}}'.", |
1168 | "filter": "Filter", | 1169 | "filter": "Filter", |
1170 | + "editable": "Editable", | ||
1169 | "no-filters-found": "No filters found.", | 1171 | "no-filters-found": "No filters found.", |
1170 | "no-filter-matching": "'{{filter}}' not found.", | 1172 | "no-filter-matching": "'{{filter}}' not found.", |
1171 | "create-new-filter": "Create a new one!", | 1173 | "create-new-filter": "Create a new one!", |
@@ -1195,6 +1197,7 @@ | @@ -1195,6 +1197,7 @@ | ||
1195 | "complex-filter": "Complex filter", | 1197 | "complex-filter": "Complex filter", |
1196 | "edit-complex-filter": "Edit complex filter", | 1198 | "edit-complex-filter": "Edit complex filter", |
1197 | "key-filter": "Key filter", | 1199 | "key-filter": "Key filter", |
1200 | + "key-filters": "Key filters", | ||
1198 | "key-name": "Key name", | 1201 | "key-name": "Key name", |
1199 | "key-name-required": "Key name is required.", | 1202 | "key-name-required": "Key name is required.", |
1200 | "key-type": { | 1203 | "key-type": { |
@@ -1210,6 +1213,8 @@ | @@ -1210,6 +1213,8 @@ | ||
1210 | "boolean": "Boolean" | 1213 | "boolean": "Boolean" |
1211 | }, | 1214 | }, |
1212 | "value-type-required": "Key value type is required.", | 1215 | "value-type-required": "Key value type is required.", |
1216 | + "key-value-type-change-title": "Are you sure you want to change key value type?", | ||
1217 | + "key-value-type-change-message": "If you confirm new value type all entered key filters will be removed.", | ||
1213 | "no-key-filters": "No key filters configured", | 1218 | "no-key-filters": "No key filters configured", |
1214 | "add-key-filter": "Add key filter", | 1219 | "add-key-filter": "Add key filter", |
1215 | "remove-key-filter": "Remove key filter", | 1220 | "remove-key-filter": "Remove key filter", |
@@ -750,6 +750,9 @@ mat-label { | @@ -750,6 +750,9 @@ mat-label { | ||
750 | &.tb-mat-16 { | 750 | &.tb-mat-16 { |
751 | @include tb-mat-icon-size(16); | 751 | @include tb-mat-icon-size(16); |
752 | } | 752 | } |
753 | + &.tb-mat-18 { | ||
754 | + @include tb-mat-icon-size(18); | ||
755 | + } | ||
753 | &.tb-mat-20 { | 756 | &.tb-mat-20 { |
754 | @include tb-mat-icon-size(20); | 757 | @include tb-mat-icon-size(20); |
755 | } | 758 | } |