Skip to content

Commit cfbca2e

Browse files
committed
Pre-filter chart-specific
1 parent 27e47ed commit cfbca2e

9 files changed

Lines changed: 189 additions & 56 deletions

File tree

frontend/src/app/admin/design/components/counter/counter.component.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ export class CounterComponent implements OnInit {
8282
type: 'check',
8383
required: false,
8484
},
85+
{
86+
name: 'pre_filter',
87+
label: 'Pre-filter (JSON)',
88+
type: 'textarea',
89+
required: false,
90+
},
8591
];
8692

8793
@Input() configs;
@@ -110,6 +116,7 @@ export class CounterComponent implements OnInit {
110116
form_data: this.form_data,
111117
configs: this.configs,
112118
isCounter: true,
119+
skipPreview: true,
113120
},
114121
});
115122

frontend/src/app/admin/design/components/filter/filter.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ export class FilterComponent implements OnInit {
217217
dashboard_name,
218218
form_data: this.form_data,
219219
configs: this.configs,
220+
skipPreview: true,
220221
},
221222
});
222223

frontend/src/app/admin/design/components/form-dialog/form-dialog.component.html

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
<h1 mat-dialog-title>Settings</h1>
22
<div mat-dialog-content>
3-
@if (
4-
data?.configs?.component && data.configs.component !== 'MainListComponent'
5-
) {
3+
@if (!skipPreview) {
64
<div class="col-md-6 preview-container">
75
<div class="preview-content" [id]="liveConfigs?.componentConfigs?.id">
86
@if (previewChart) {
@@ -19,9 +17,9 @@ <h1 mat-dialog-title>Settings</h1>
1917
#f="ngForm"
2018
class="formd"
2119
[ngClass]="
22-
data?.configs?.component && data.configs.component !== 'MainListComponent'
23-
? 'col-md-6'
24-
: ''
20+
skipPreview
21+
? ''
22+
: 'col-md-6'
2523
"
2624
[formGroup]="form"
2725
>
@@ -206,7 +204,7 @@ <h1 mat-dialog-title>Settings</h1>
206204
(keyup.enter)="submit(f.value)"
207205
(click)="submit(f.value)"
208206
mat-raised-button
209-
[disabled]="f.invalid"
207+
[disabled]="f.invalid || !preFilterJsonValid"
210208
>
211209
Save <mat-icon>done</mat-icon>
212210
</button>

frontend/src/app/admin/design/components/form-dialog/form-dialog.component.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { debounceTime } from 'rxjs/operators';
3030
import { NgClass } from '@angular/common';
3131
import { ComponentFilterConfigs } from '../../../../explorer/configs/generalConfig.interface';
3232
import * as fromStore from '../../../../explorer/store';
33+
import { ToastrService } from 'ngx-toastr';
3334

3435
@Component({
3536
selector: 'app-form-dialog',
@@ -61,6 +62,7 @@ export class FormDialogComponent implements OnInit {
6162
data = inject(MAT_DIALOG_DATA);
6263
private store = inject<Store<AppState>>(Store);
6364
private mainBodyBuilderService = inject(MainBodyBuilderService);
65+
private toastr = inject(ToastrService);
6466

6567
controls = [];
6668
form: UntypedFormGroup = new UntypedFormGroup({
@@ -71,6 +73,8 @@ export class FormDialogComponent implements OnInit {
7173
metadata = [];
7274
previewChart = false;
7375
liveConfigs: any;
76+
skipPreview = true;
77+
preFilterJsonValid = true;
7478

7579
/** Inserted by Angular inject() migration for backwards compatibility */
7680
constructor(...args: unknown[]);
@@ -81,6 +85,17 @@ export class FormDialogComponent implements OnInit {
8185
this.dialogRef.close(false);
8286
}
8387
submit(value) {
88+
if (
89+
Object.hasOwn(this.form.value, 'pre_filter') &&
90+
this.form.value['pre_filter'] !== ''
91+
) {
92+
try {
93+
JSON.parse(this.form.value['pre_filter']);
94+
} catch (e) {
95+
this.toastr.error('Invalid JSON in the field "Pre-filter"');
96+
return;
97+
}
98+
}
8499
const names_exist: Array<string> = this.data.form_data.map((d) => d.name);
85100
Object.keys(this.form.controls).forEach((key) => {
86101
if (names_exist.indexOf(key) == -1) this.form.removeControl(key);
@@ -129,6 +144,14 @@ export class FormDialogComponent implements OnInit {
129144
return;
130145
}
131146

147+
// No preview for counters or main list
148+
this.skipPreview = this.data.skipPreview ||
149+
!this.form.value?.['component'] ||
150+
this.form.value['component'] === 'MainListComponent';
151+
if (this.skipPreview) {
152+
return;
153+
}
154+
132155
this.liveConfigs = this.prepareDashboardChart(
133156
this.form.value,
134157
this.data.gridRow,
@@ -200,6 +223,8 @@ export class FormDialogComponent implements OnInit {
200223
if (obj.metric) temp['metric'] = obj.metric;
201224
if (obj.metric_field) temp['metric_field'] = obj.metric_field;
202225

226+
if (obj.pre_filter) temp['pre_filter'] = obj.pre_filter;
227+
203228
if (obj.data_labels) temp['data_labels'] = obj.data_labels;
204229

205230
temp['data_labels_count'] = obj?.data_labels_count ?? false;

frontend/src/app/admin/design/components/structure/structure.component.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,13 @@ export class StructureComponent implements OnInit {
307307
required: false,
308308
});
309309
}
310+
311+
this.form_data.push({
312+
name: 'pre_filter',
313+
label: 'Pre-filter (JSON)',
314+
type: 'textarea',
315+
required: false,
316+
});
310317
}
311318

312319
/** Inserted by Angular inject() migration for backwards compatibility */
@@ -498,7 +505,7 @@ export class StructureComponent implements OnInit {
498505
}
499506

500507
this.dialogRef = this.dialog.open(FormDialogComponent, {
501-
width: '90vw',
508+
width: '95vw',
502509
data: {
503510
dashboard_name,
504511
form_data: Object.create(this.form_data),

frontend/src/app/admin/design/design.component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,8 @@ export class DesignComponent implements OnInit {
403403
if (obj.metric) temp['metric'] = obj.metric;
404404
if (obj.metric_field) temp['metric_field'] = obj.metric_field;
405405

406+
if (obj.pre_filter) temp['pre_filter'] = obj.pre_filter;
407+
406408
if (obj.data_labels) temp['data_labels'] = obj.data_labels;
407409

408410
if (obj.data_labels_count)
@@ -505,6 +507,8 @@ export class DesignComponent implements OnInit {
505507

506508
if (obj.description) temp['description'] = obj.description;
507509

510+
if (obj.pre_filter) temp['pre_filter'] = obj.pre_filter;
511+
508512
if (obj.source) {
509513
if (obj.type == 'cardinality')
510514
obj.source[0].field = obj.source[0].field + '.keyword';

frontend/src/app/explorer/filters/services/interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ export interface QueryBlock {
230230
is_related?: boolean;
231231
buckets: string;
232232
filter?: string;
233+
pre_filter?: string;
233234
size?: number;
234235
type?: string;
235236
sort?: boolean;

frontend/src/app/explorer/services/mainBodyBuilderService/builderUtilities.class.ts

Lines changed: 118 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export class BuilderUtilities {
121121
({
122122
id,
123123
filter,
124+
pre_filter,
124125
type,
125126
source,
126127
is_related,
@@ -131,6 +132,7 @@ export class BuilderUtilities {
131132
}: any) => {
132133
const qb: QueryBlock = {
133134
filter,
135+
pre_filter,
134136
type,
135137
size,
136138
is_related,
@@ -170,6 +172,7 @@ export class BuilderUtilities {
170172
: 10000,
171173
sort: componentConfigs.sort,
172174
metric: (componentConfigs as any).metric,
175+
pre_filter: (componentConfigs as any).pre_filter,
173176
metric_field: (componentConfigs as any).metric_field
174177
? (componentConfigs as any).metric_field.replace('.keyword', '')
175178
: undefined,
@@ -182,41 +185,66 @@ export class BuilderUtilities {
182185
qb: QueryBlock,
183186
b: bodybuilder.Bodybuilder,
184187
): void {
185-
const { filter, source, type } = qb; // filter comes from this.convertEnumToQueryBlock
188+
const { filter, pre_filter, source, type } = qb; // filter comes from this.convertEnumToQueryBlock
186189
const sourceString = source[0].field as string;
187-
if (!filter && type == 'cardinality' && sourceString != 'total.keyword') {
188-
b.aggregation(
189-
'cardinality',
190-
{
190+
let parsedPreFilter;
191+
try {
192+
parsedPreFilter = JSON.parse(pre_filter);
193+
} catch (e) {
194+
parsedPreFilter = null;
195+
}
196+
197+
const aggObject = {
198+
type: '',
199+
agg: null,
200+
name: '',
201+
};
202+
if (filter) {
203+
aggObject.type = 'filter';
204+
aggObject.agg = {
205+
term: {
206+
[sourceString]: filter,
207+
},
208+
};
209+
aggObject.name = `${sourceString}_${filter}`;
210+
} else if (type) {
211+
aggObject.type = type;
212+
aggObject.name = sourceString;
213+
if (type == 'cardinality') {
214+
aggObject.agg = {
191215
field: sourceString,
192216
precision_threshold: 40000,
193-
},
194-
sourceString,
195-
);
196-
} else if (
197-
!filter &&
198-
type &&
199-
type != 'cardinality' &&
200-
sourceString != 'total.keyword'
201-
) {
202-
b.aggregation(
203-
type,
204-
{
217+
};
218+
} else {
219+
aggObject.agg = {
205220
field: sourceString.replace('.keyword', ''),
206221
missing: 0,
207-
},
208-
sourceString,
209-
);
210-
} else if (filter && type && sourceString != 'total.keyword') {
211-
const obj = Object.create(null);
212-
obj[sourceString] = filter;
222+
};
223+
}
224+
} else {
225+
return;
226+
}
227+
228+
if (parsedPreFilter) {
213229
b.aggregation(
214230
'filter',
215231
{
216-
term: obj,
232+
bool: {
233+
filter: {
234+
bool: {
235+
must: parsedPreFilter,
236+
},
237+
},
238+
},
239+
},
240+
aggObject.name,
241+
(a1) => {
242+
a1.aggregation(aggObject.type, aggObject.agg, aggObject.name);
243+
return a1;
217244
},
218-
`${sourceString}_${filter}`,
219245
);
246+
} else {
247+
b.aggregation(aggObject.type, aggObject.agg, aggObject.name);
220248
}
221249
}
222250

@@ -239,27 +267,72 @@ export class BuilderUtilities {
239267
const current = sources[index];
240268
const isLast = index === sources.length - 1;
241269
const name = parentName ? parentName : `${current.field}_level_${index}`;
270+
let parsedPreFilter;
271+
try {
272+
parsedPreFilter = qb?.pre_filter ? JSON.parse(qb.pre_filter) : null;
273+
} catch (e) {
274+
parsedPreFilter = null;
275+
}
242276

243-
b.aggregation(
244-
'terms',
245-
this.buildTermRules(
246-
current.limit,
247-
current.field,
248-
false,
249-
current.order,
250-
!qb?.metric || qb.metric === 'count',
251-
),
252-
name,
253-
(a) => {
254-
if (qb.metric && qb.metric !== 'count') {
255-
a.aggregation(qb.metric, qb.metric_field, 'metric');
256-
}
257-
if (!isLast) {
258-
this.buildNestedAggs(a, sources, index + 1, qb);
259-
}
260-
return a;
261-
},
262-
);
277+
if (parsedPreFilter && index === 0) {
278+
b.aggregation(
279+
'filter',
280+
{
281+
bool: {
282+
filter: {
283+
bool: {
284+
must: parsedPreFilter,
285+
},
286+
},
287+
},
288+
},
289+
name,
290+
(a1) => {
291+
a1.aggregation(
292+
'terms',
293+
this.buildTermRules(
294+
current.limit,
295+
current.field,
296+
false,
297+
current.order,
298+
!qb?.metric || qb.metric === 'count',
299+
),
300+
name,
301+
(a) => {
302+
if (qb.metric && qb.metric !== 'count') {
303+
a.aggregation(qb.metric, qb.metric_field, 'metric');
304+
}
305+
if (!isLast) {
306+
this.buildNestedAggs(a, sources, index + 1, qb);
307+
}
308+
return a;
309+
},
310+
);
311+
return a1;
312+
},
313+
);
314+
} else {
315+
b.aggregation(
316+
'terms',
317+
this.buildTermRules(
318+
current.limit,
319+
current.field,
320+
false,
321+
current.order,
322+
!qb?.metric || qb.metric === 'count',
323+
),
324+
name,
325+
(a) => {
326+
if (qb.metric && qb.metric !== 'count') {
327+
a.aggregation(qb.metric, qb.metric_field, 'metric');
328+
}
329+
if (!isLast) {
330+
this.buildNestedAggs(a, sources, index + 1, qb);
331+
}
332+
return a;
333+
},
334+
);
335+
}
263336
}
264337

265338
private buildTermRules(

0 commit comments

Comments
 (0)