Skip to content

Commit f5964fd

Browse files
committed
feat: enhance visualization creation flow with SQL query support and validation
1 parent 5352d90 commit f5964fd

14 files changed

Lines changed: 262 additions & 220 deletions

File tree

frontend/src/app/graphic-builder/chart-builder/chart-builder.component.html

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,20 @@
11
<div class="container-fluid pr-3 pl-3 pt-2">
22
<div class="d-flex justify-content-between align-items-center">
3-
<h5 *ngIf="visualization && !mode"
4-
class="mb-0 label-header px-3 py-2">
5-
Visualization builder
3+
<h5 class="mb-0 label-header px-3 py-2">
4+
{{mode==='edit' ? visualization.name : 'Visualization builder'}}
65
</h5>
7-
<h5 *ngIf="visualization && mode==='edit'"
8-
class="card-title mb-0 text-uppercase label-header">
9-
{{visualization.name}}
10-
</h5>
11-
<div class="header-elements d-flex">
12-
<button (click)="saveVisualization()" [disabled]="!(metricNoErrors && bucketNoErrors)"
13-
class="btn utm-button utm-button-primary d-flex" style="background-color: red">
14-
<span [ngClass]="chartIconResolver()" class="mr-2 utm-icon-xs utm-icon-white d-flex"></span>
15-
Save visualization
16-
</button>
17-
</div>
186
</div>
197

208
<app-visualization-header class="d-flex bg-white"
219
(indexPatternSelected)="indexPatternSelected($event)"
2210
[chartType]="chart"
2311
(cancelled)="cancel()"
2412
(saved)="saveVisualization()"
13+
[sqlMode]="isSqlMode"
2514
(sqlModeToggled)="toggleSqlMode($event)"
2615
(indexPatternInitialized)="indexPatternLoaded($event)"
16+
[pattern]="visualization.pattern"
17+
*ngIf="!loading"
2718
>
2819
</app-visualization-header>
2920

@@ -71,7 +62,8 @@
7162

7263
<div class="metric-property-container">
7364
<div
74-
[hidden]="property!==headers[0].name || isSqlMode"
65+
*ngIf="!isSqlMode"
66+
[hidden]="property!==headers[0].name"
7567
class="pt-2 metric-data-container w-100 h-100">
7668
<app-metric-aggregation (metricDefChange)="onMetricChange($event)"
7769
[hidden]="chart ===chartTypeEnum.LIST_CHART"
@@ -100,12 +92,18 @@
10092
<div *ngIf="property === headers[0].name && isSqlMode"
10193
class="metric-data-container w-100 h-100">
10294
<app-code-editor
103-
[showHeader]="false"
95+
[showFullEditor]="false"
10496
[(ngModel)]="sqlQuery"
97+
(ngModelChange)="clearMessages()"
10598
[customKeywords]="loadFieldNames().concat(indexPatternNames)"
10699
[consoleOptions]="codeEditorOptions">
107100
</app-code-editor>
108101
</div>
102+
<div class="execution-info" *ngIf="errorMessage && isSqlMode">
103+
<div class="text-danger-800">
104+
<span>{{ errorMessage }}</span>
105+
</div>
106+
</div>
109107
<div *ngIf="visualization" [hidden]="property !== headers[1].name"
110108
class="pt-2 option-container w-100 h-100">
111109
<app-metric-properties-option (metricOptions)="onMetricOptionChange($event)"
@@ -201,7 +199,7 @@
201199
</div>
202200
</div>
203201
</div>
204-
<div class="col-lg-9 col-md-12 col-sm-12 position-relative pl-0 pt-5">
202+
<div class="col-lg-9 col-md-12 col-sm-12 position-relative pl-0 pt-5" *ngIf="(pattern && !isSqlMode) || isSqlMode">
205203
<div class="data-visualization d-flex
206204
justify-content-center align-items-start w-100 h-100 position-absolute">
207205
<app-utm-viewer (runned)="onRunVisualization($event)"

frontend/src/app/graphic-builder/chart-builder/chart-builder.component.scss

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,8 @@ app-utm-viewer {
5353
}
5454

5555
.label-header {
56-
font-size: 12px;
57-
color: #495057 !important;
58-
background-color: #fff;
59-
border: 1px #dee2e6 solid;
60-
border-bottom: 1px solid transparent;
6156
border-top-left-radius: .25rem;
6257
border-top-right-radius: .25rem;
63-
margin-bottom: -1px !important;
6458
font-weight: 500;
6559
}
6660

@@ -75,3 +69,7 @@ app-utm-viewer {
7569
border-bottom: none !important;
7670
}
7771
}
72+
73+
.container-fluid {
74+
overflow: auto;
75+
}

frontend/src/app/graphic-builder/chart-builder/chart-builder.component.ts

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {ChartTypeEnum} from '../../shared/enums/chart-type.enum';
2929
import {ElasticOperatorsEnum} from '../../shared/enums/elastic-operators.enum';
3030
import {DataNatureTypeEnum} from '../../shared/enums/nature-data.enum';
3131
import {RouteCallbackEnum} from '../../shared/enums/route-callback.enum';
32-
import {ElasticSearchIndexService} from '../../shared/services/elasticsearch/elasticsearch-index.service';
32+
import {SqlValidationService} from '../../shared/services/code-editor/sql-validation.service';
3333
import {FieldDataService} from '../../shared/services/elasticsearch/field-data.service';
3434
import {LocalFieldService} from '../../shared/services/elasticsearch/local-field.service';
3535
import {ElasticFilterType} from '../../shared/types/filter/elastic-filter.type';
@@ -38,7 +38,6 @@ import {RunVisualizationBehavior} from '../shared/behavior/run-visualization.beh
3838
import {VisualizationQueryParamsEnum} from '../shared/enums/visualization-query-params.enum';
3939
import {VisualizationService} from '../visualization/shared/services/visualization.service';
4040
import {VisualizationSaveComponent} from '../visualization/visualization-save/visualization-save.component';
41-
import {VisualizationBehavior} from './chart-property-builder/shared/behaviors/visualization.behavior';
4241

4342
import {DashboardStatusEnum} from '../dashboard-builder/shared/enums/dashboard-status.enum';
4443

@@ -75,24 +74,21 @@ export class ChartBuilderComponent implements OnInit, AfterViewChecked {
7574
sqlQuery = '';
7675
indexPatternNames: string[] = [];
7776
codeEditorOptions: ConsoleOptions = {lineNumbers: 'off'};
77+
loading = true;
7878

7979
constructor(private spinner: NgxSpinnerService,
8080
private route: ActivatedRoute,
8181
private modalService: NgbModal,
8282
private fieldDataBehavior: FieldDataService,
83-
private visualizationBehavior: VisualizationBehavior,
8483
private cdr: ChangeDetectorRef,
85-
private indexPatternFieldService: ElasticSearchIndexService,
8684
private visualizationService: VisualizationService,
8785
private runVisualizationBehavior: RunVisualizationBehavior,
8886
private location: Location,
8987
private router: Router,
90-
private localFieldService: LocalFieldService) {
88+
private localFieldService: LocalFieldService,
89+
private sqlValidationService: SqlValidationService) {
9190
route.queryParams.subscribe(params => {
92-
//TODO: ELENA Revisar
93-
console.log('chart', params[VisualizationQueryParamsEnum.CHART]);
94-
const chartParam = params[VisualizationQueryParamsEnum.CHART];
95-
this.chart = chartParam as ChartTypeEnum;
91+
this.chart = params[VisualizationQueryParamsEnum.CHART];
9692
this.mode = params[VisualizationQueryParamsEnum.MODE];
9793
if (params[VisualizationQueryParamsEnum.CALLBACK]) {
9894
this.callback = params[VisualizationQueryParamsEnum.CALLBACK];
@@ -108,8 +104,19 @@ export class ChartBuilderComponent implements OnInit, AfterViewChecked {
108104
if (this.mode === 'edit') {
109105
this.visualizationService.find(this.visualizationId).subscribe(vis => {
110106
this.visualization = vis.body;
107+
if (this.visualization.sqlQuery) {
108+
this.visualization.queryLanguage = ChartBuilderQueryLanguageEnum.SQL;
109+
this.sqlQuery = this.visualization.sqlQuery;
110+
this.isSqlMode = true;
111+
} else {
112+
this.visualization.queryLanguage = ChartBuilderQueryLanguageEnum.DSL;
113+
this.pattern = this.visualization.pattern.pattern;
114+
this.patternId = this.visualization.pattern.id;
115+
this.isSqlMode = false;
116+
}
111117
const defaultFilterTime = this.getDefaultFilterTimeFromVisualization(this.visualization.filterType);
112118
this.defaultTime = defaultFilterTime ? defaultFilterTime : new ElasticFilterDefaultTime('now-24h', 'now');
119+
this.loading = false;
113120
});
114121
} else {
115122
this.visualization = {
@@ -122,17 +129,15 @@ export class ChartBuilderComponent implements OnInit, AfterViewChecked {
122129
},
123130
chartAction: new ChartActionType(false),
124131
filterType: [{field: '@timestamp', operator: ElasticOperatorsEnum.IS_BETWEEN, value: ['now-24h', 'now']}],
125-
idPattern: this.patternId,
132+
idPattern: null,
126133
chartType: this.chart,
127134
eventType: this.type,
128135
userCreated: null,
129136
name: '',
130-
pattern: {
131-
id: this.patternId,
132-
pattern: this.pattern
133-
},
137+
pattern: null,
134138
queryLanguage: ChartBuilderQueryLanguageEnum.DSL
135139
};
140+
this.loading = false;
136141
}
137142
}
138143

@@ -148,16 +153,14 @@ export class ChartBuilderComponent implements OnInit, AfterViewChecked {
148153
}
149154

150155
viewProperty($event: string) {
151-
console.log('Ele', this.sqlQuery);
152156
this.property = $event;
153157
}
154158

155159
runVisualization() {
156160
this.running = true;
157161
if (this.isSqlMode) {
158-
const validationError = this.codeEditor.validateSqlQuery();
159-
if (validationError) {
160-
this.errorMessage = validationError;
162+
this.errorMessage = this.sqlValidationService.validateSqlQuery(this.sqlQuery);
163+
if (this.errorMessage) {
161164
this.running = false;
162165
return;
163166
}
@@ -171,15 +174,19 @@ export class ChartBuilderComponent implements OnInit, AfterViewChecked {
171174

172175
saveVisualization() {
173176
const modal = this.modalService.open(VisualizationSaveComponent, {centered: true});
177+
if (this.isSqlMode) {
178+
this.nullifyUnusedFields();
179+
this.visualization.queryLanguage = ChartBuilderQueryLanguageEnum.SQL;
180+
this.visualization.sqlQuery = this.sqlQuery;
181+
} else {
182+
this.visualization.queryLanguage = ChartBuilderQueryLanguageEnum.DSL;
183+
this.visualization.sqlQuery = '';
184+
}
174185
modal.componentInstance.visualization = this.visualization;
175186
modal.componentInstance.callback = this.callback;
176187
modal.componentInstance.mode = this.mode;
177188
}
178189

179-
chartIconResolver(): string {
180-
return UTM_CHART_ICONS[this.chart];
181-
}
182-
183190
onFilterChange($event: ElasticFilterType[]) {
184191
this.configChange = true;
185192
this.visualization.filterType = $event;
@@ -330,4 +337,16 @@ export class ChartBuilderComponent implements OnInit, AfterViewChecked {
330337
indexPatternLoaded(indexPatternNames: string[]) {
331338
this.indexPatternNames = indexPatternNames;
332339
}
340+
341+
nullifyUnusedFields() {
342+
this.visualization.aggregationType = null;
343+
this.visualization.pattern = null;
344+
this.visualization.idPattern = null;
345+
this.visualization.filterType = null;
346+
}
347+
348+
clearMessages(): void {
349+
this.errorMessage = '';
350+
}
351+
333352
}

frontend/src/app/graphic-builder/shared/components/viewer/chart-view/chart-view.component.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,8 @@
1-
import {
2-
ChangeDetectionStrategy,
3-
Component,
4-
EventEmitter,
5-
Input,
6-
OnChanges,
7-
OnDestroy,
8-
OnInit,
9-
Output, SimpleChanges
10-
} from '@angular/core';
11-
import { Observable, of, Subject} from 'rxjs';
1+
import {ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core';
2+
import {Observable, of, Subject} from 'rxjs';
123
import {catchError, filter, switchMap, takeUntil, tap} from 'rxjs/operators';
134
import {UtmToastService} from '../../../../../shared/alert/utm-toast.service';
145
import {DashboardBehavior} from '../../../../../shared/behaviors/dashboard.behavior';
15-
import EChartOption = echarts.EChartOption;
166
import {TimeFilterBehavior} from "../../../../../shared/behaviors/time-filter.behavior";
177
import {ChartFactory} from '../../../../../shared/chart/factories/echart-factory/chart-factory';
188
import {VisualizationType} from '../../../../../shared/chart/types/visualization.type';
@@ -30,6 +20,8 @@ import {RunVisualizationService} from '../../../services/run-visualization.servi
3020
import {UtmChartClickActionService} from '../../../services/utm-chart-click-action.service';
3121
import {rebuildVisualizationFilterTime} from '../../../util/chart-filter/chart-filter.util';
3222
import {resolveDefaultVisualizationTime} from '../../../util/visualization/visualization-render.util';
23+
import {ChartBuilderQueryLanguageEnum} from "../../../../../shared/enums/chart-builder-query-language.enum";
24+
import EChartOption = echarts.EChartOption;
3325
// @ts-ignore
3426
require('echarts-wordcloud');
3527

@@ -88,7 +80,7 @@ export class ChartViewComponent implements OnInit, OnDestroy {
8880
.subscribe(id => {
8981
if (id && this.chartId === id) {
9082
this.refreshService.sendRefresh(this.refreshType);
91-
this.defaultTime = resolveDefaultVisualizationTime(this.visualization);
83+
this.defaultTime = this.visualization.filterType ? resolveDefaultVisualizationTime(this.visualization) : null;
9284
}
9385
});
9486

@@ -130,7 +122,7 @@ export class ChartViewComponent implements OnInit, OnDestroy {
130122
});
131123

132124
if (!this.defaultTime) {
133-
this.defaultTime = resolveDefaultVisualizationTime(this.visualization);
125+
this.defaultTime = this.visualization.filterType ? resolveDefaultVisualizationTime(this.visualization) : null;
134126
this.refreshService.sendRefresh(this.refreshType);
135127
}
136128
}
@@ -159,6 +151,8 @@ export class ChartViewComponent implements OnInit, OnDestroy {
159151

160152
runVisualization() {
161153
this.loadingOption = true;
154+
this.visualization.queryLanguage = this.visualization.sqlQuery ? ChartBuilderQueryLanguageEnum.SQL
155+
: ChartBuilderQueryLanguageEnum.DSL;
162156
return this.runVisualizationService.run(this.visualization)
163157
.pipe(
164158
tap((data) => {

frontend/src/app/graphic-builder/visualization/visualization-header/visualization-header.component.html

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
<div class="w-100">
22
<div class="d-flex p-2 bg-white gap-2 header-border">
3-
<div *ngIf="!isSqlMode" class="d-flex align-items-center">
3+
<div *ngIf="!sqlMode" class="d-flex align-items-center">
44
<app-index-pattern-select
55
(indexPatternChange)="indexPatternChange($event)"
66
[template]="'log-explorer'"
7-
(indexPatternInitialized)="indexPatternLoaded($event)">
7+
(indexPatternInitialized)="indexPatternLoaded($event)"
8+
[pattern]="pattern">
89
</app-index-pattern-select>
910
</div>
1011
<div class="d-flex w-100">
1112
<div class="visualization-actions d-flex gap-2 ml-auto">
1213
<button class="btn utm-button utm-button-primary" (click)="toggleSqlMode()">
13-
{{ isSqlMode ? 'Switch to Classic' : 'Try SQL' }}
14+
{{ sqlMode ? 'DSL Builder' : 'SQL Builder' }}
1415
</button>
15-
<button [ngbTooltip]="'Save visualization'" class="btn utm-button utm-button-primary" (click)="save()">
16+
<button [ngbTooltip]="'Save visualization'" class="btn utm-button utm-button-primary d-flex align-items-center" (click)="save()">
1617
<span [ngClass]="chartIconResolver()" class="mr-2 utm-icon-xs utm-icon-white"></span>&nbsp;Save visualization
1718
</button>
1819
<button [ngbTooltip]="'Cancel'" class="btn utm-button utm-button-grey" (click)="cancel()">

frontend/src/app/graphic-builder/visualization/visualization-header/visualization-header.component.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
}
44

55
.header-border {
6-
border-inline: 1px solid #dee2e6;
6+
border: 1px solid #dee2e6;
7+
border-bottom: none;
78
}
89

910
.visualization-actions {

frontend/src/app/graphic-builder/visualization/visualization-header/visualization-header.component.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ export class VisualizationHeaderComponent implements OnInit {
1414
@Output() indexPatternSelected = new EventEmitter<UtmIndexPattern>();
1515
@Output() cancelled = new EventEmitter<void>();
1616
@Output() saved = new EventEmitter<void>();
17+
@Input() sqlMode = false;
1718
@Output() sqlModeToggled = new EventEmitter<boolean>();
18-
isSqlMode = false;
19-
pattern: UtmIndexPattern;
19+
@Input() pattern: UtmIndexPattern;
2020

2121
constructor(public activeModal: NgbActiveModal) {
2222
}
@@ -42,8 +42,8 @@ export class VisualizationHeaderComponent implements OnInit {
4242
}
4343

4444
toggleSqlMode() {
45-
this.isSqlMode = !this.isSqlMode;
46-
this.sqlModeToggled.emit(this.isSqlMode);
45+
this.sqlMode = !this.sqlMode;
46+
this.sqlModeToggled.emit(this.sqlMode);
4747
}
4848

4949
chartIconResolver(): string {

frontend/src/app/graphic-builder/visualization/visualization-list/visualization-list.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ <h5 class="card-title mb-0 text-uppercase label-header">Visualizations</h5>
117117
</td>
118118
<td>
119119
<span>
120-
{{vis.pattern.pattern}}
120+
{{vis.pattern? vis.pattern.pattern : ''}}
121121
</span>
122122
</td>
123123
<td>

frontend/src/app/graphic-builder/visualization/visualization-list/visualization-list.component.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@
1717
.min-h-0 {
1818
min-height: 0;
1919
}
20+
21+
.container-fluid{
22+
overflow: auto;
23+
}

frontend/src/app/graphic-builder/visualization/visualization-list/visualization-list.component.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,10 @@ export class VisualizationListComponent implements OnInit {
7575
editVisualization(vis: VisualizationType) {
7676
this.spinner.show('loadingSpinner');
7777
const queryParams = {};
78-
queryParams[VisualizationQueryParamsEnum.PATTERN_NAME] = vis.pattern.pattern;
79-
queryParams[VisualizationQueryParamsEnum.PATTERN_ID] = vis.pattern.id;
78+
if (vis.pattern) {
79+
queryParams[VisualizationQueryParamsEnum.PATTERN_NAME] = vis.pattern.pattern;
80+
queryParams[VisualizationQueryParamsEnum.PATTERN_ID] = vis.pattern.id;
81+
}
8082
queryParams[VisualizationQueryParamsEnum.CHART] = vis.chartType;
8183
queryParams[VisualizationQueryParamsEnum.MODE] = 'edit';
8284
queryParams[VisualizationQueryParamsEnum.VISUALIZATION_ID] = vis.id;

0 commit comments

Comments
 (0)