From 5497e5ad041e4582b755bb9698911c889012e4d2 Mon Sep 17 00:00:00 2001 From: Nikita Nazarov Date: Tue, 13 May 2025 01:30:35 +0300 Subject: [PATCH 01/11] feat(datacat-ui): add icons for Prometheus, Jaeger, Elastic Search and Keycloak for data sources select; fix alerts counts widget --- .../public/assets/icons/auth/keycloak.png | Bin 0 -> 151706 bytes .../assets/icons/datasource/elasticsearch.png | Bin 0 -> 6360 bytes .../public/assets/icons/datasource/jaeger.png | Bin 0 -> 3913 bytes .../assets/icons/datasource/prometheus.png | Bin 0 -> 47555 bytes .../alerts-counts-by-status.component.ts | 16 +++++++----- .../data-source-select.component.html | 24 +++++++++++++++--- .../data-source-select.component.scss | 4 +++ .../data-source-select.component.ts | 1 + 8 files changed, 34 insertions(+), 11 deletions(-) create mode 100644 frontend/datacat-ui/public/assets/icons/auth/keycloak.png create mode 100644 frontend/datacat-ui/public/assets/icons/datasource/elasticsearch.png create mode 100644 frontend/datacat-ui/public/assets/icons/datasource/jaeger.png create mode 100644 frontend/datacat-ui/public/assets/icons/datasource/prometheus.png create mode 100644 frontend/datacat-ui/src/shared/ui/data-source-select/data-source-select.component.scss diff --git a/frontend/datacat-ui/public/assets/icons/auth/keycloak.png b/frontend/datacat-ui/public/assets/icons/auth/keycloak.png new file mode 100644 index 0000000000000000000000000000000000000000..4af3e332dd30ef189fa9d5e963609031dc10e254 GIT binary patch literal 151706 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

nELaXfZf@(_tgRV%iFffmMz@+ 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

{f9SWZ#am>w5hOu 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 { - this.alertsCountsByStatus = alertsCountsByStatus; - }, - error: () => { - // TODO + this.apiService.getApiV1AlertGetCounters().subscribe({ + next: (data) => { + this.alertsCountsByStatus = new Map(); + data.forEach((d) => { + this.alertsCountsByStatus?.set(d.status as AlertStatus, d.count || 0); + }); }, }); } diff --git a/frontend/datacat-ui/src/shared/ui/data-source-select/data-source-select.component.html b/frontend/datacat-ui/src/shared/ui/data-source-select/data-source-select.component.html index 630eedd..5a4e6d7 100644 --- a/frontend/datacat-ui/src/shared/ui/data-source-select/data-source-select.component.html +++ b/frontend/datacat-ui/src/shared/ui/data-source-select/data-source-select.component.html @@ -9,19 +9,35 @@ [loading]="isLoading" (onChange)="onSelect($event.value)" [(ngModel)]="selectedDataSource" + appendTo="body" > -

+
@switch (dataSource.driver) { @case (DataSourceDriver.PROMETHEUS) { - P +
+ +
} @case (DataSourceDriver.ELASTIC_SEARCH) { - E +
+ +
} @case (DataSourceDriver.JAEGER) { - J +
+ +
} }
diff --git a/frontend/datacat-ui/src/shared/ui/data-source-select/data-source-select.component.scss b/frontend/datacat-ui/src/shared/ui/data-source-select/data-source-select.component.scss new file mode 100644 index 0000000..93e1784 --- /dev/null +++ b/frontend/datacat-ui/src/shared/ui/data-source-select/data-source-select.component.scss @@ -0,0 +1,4 @@ +.option { + display: flex; + gap: var(--p-padding-sm); +} diff --git a/frontend/datacat-ui/src/shared/ui/data-source-select/data-source-select.component.ts b/frontend/datacat-ui/src/shared/ui/data-source-select/data-source-select.component.ts index 303281f..737de65 100644 --- a/frontend/datacat-ui/src/shared/ui/data-source-select/data-source-select.component.ts +++ b/frontend/datacat-ui/src/shared/ui/data-source-select/data-source-select.component.ts @@ -17,6 +17,7 @@ import { convertToApiFilters } from '../../../entities/data-sources'; standalone: true, selector: 'datacat-data-source-select', templateUrl: './data-source-select.component.html', + styleUrl: './data-source-select.component.scss', imports: [SelectModule, FormsModule], providers: [ { From eaa12a87c2bb0e831c56b5ced2bd8d296e276479 Mon Sep 17 00:00:00 2001 From: Nikita Nazarov Date: Thu, 22 May 2025 09:21:46 +0300 Subject: [PATCH 02/11] feat(datacat-ui): fix visualization type switch bug; add ability to save dashboard layout --- .../src/entities/dashboards/panel.types.ts | 5 ++ .../edit-panel/edit-panel.component.ts | 24 +++++--- .../panel-in-grid/panel-in-grid.component.ts | 34 ++++++++++- .../panels-grid/panels-grid.component.ts | 59 +++++++++++++------ .../panel-visualization-options.component.ts | 6 +- .../panel-visualization.component.ts | 25 +++----- 6 files changed, 104 insertions(+), 49 deletions(-) diff --git a/frontend/datacat-ui/src/entities/dashboards/panel.types.ts b/frontend/datacat-ui/src/entities/dashboards/panel.types.ts index f4edfc9..9173542 100644 --- a/frontend/datacat-ui/src/entities/dashboards/panel.types.ts +++ b/frontend/datacat-ui/src/entities/dashboards/panel.types.ts @@ -1,5 +1,10 @@ import { DataSource } from '../alerting'; +export type PanelType = { + id: number; + type: VisualizationType; +}; + export enum VisualizationType { LINE = 'line', BAR = 'bar', diff --git a/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.ts b/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.ts index fa28606..0de0332 100644 --- a/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.ts @@ -25,6 +25,7 @@ import { ApiService } from '../../../shared/services/datacat-generated-client'; import { ToastLoggerService } from '../../../shared/services/toast-logger.service'; import { ButtonModule } from 'primeng/button'; import { finalize } from 'rxjs'; +import { DataPoints } from '../../../entities/dashboards/data.types'; @Component({ standalone: true, @@ -52,15 +53,20 @@ export class EditPanelComponent { protected panel?: Panel; - protected data: any = { - labels: ['1', '2', '3', '4', '5', '6', '7'], - datasets: [ - { - label: 'First Dataset', - data: [65, 59, 80, 81, 56, 55, 40], - }, - ], - }; + protected data: DataPoints = [ + { + timestamp: '1', + value: 0, + }, + { + timestamp: '2', + value: 5, + }, + { + timestamp: '3', + value: 3, + }, + ]; protected visualizationType?: VisualizationType; protected visualizationSettings?: VisualizationSettings; diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts index 90a1d03..d2b37e6 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts @@ -5,6 +5,9 @@ import { DataSourceDriver, decodeLayout, decodeVisualizationSettings, + encodeLayout, + encodeVisualizationSettings, + Layout, Panel, VisualizationType, } from '../../../../entities'; @@ -15,6 +18,8 @@ import { Router } from '@angular/router'; import * as urls from '../../../../shared/common/urls'; import { DataPoints } from '../../../../entities/dashboards/data.types'; import { DatePipe } from '@angular/common'; +import { Observable, of } from 'rxjs'; +import { GridsterItem } from 'angular-gridster2'; @Component({ standalone: true, @@ -26,11 +31,15 @@ import { DatePipe } from '@angular/common'; export class PanelInGridComponent { private _panelId?: string; - @Input() set panelId(id: string) { + @Input() set panelId(id: string | undefined) { this._panelId = id; this.refresh(); } + get panelId() { + return this._panelId; + } + protected data: DataPoints = []; protected panel?: Panel; @@ -90,4 +99,27 @@ export class PanelInGridComponent { }, ]; } + + public saveLayout(): Observable { + if (this.panel) { + const request = { + title: this.panel.title, + type: 1, // this.panel.visualizationType, + rawQuery: this.panel.query, + dataSourceId: this.panel.dataSource?.id, + layout: encodeLayout(this.panel.layout), + styleConfiguration: encodeVisualizationSettings( + this.panel.visualizationSettings, + ), + } as any; + return this.apiService.putApiV1PanelUpdate(this.panel.id, request); + } + return of(undefined); + } + + public updateLayout(layout: Layout) { + if (this.panel) { + this.panel.layout = layout; + } + } } diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts index 80dd4bb..4068308 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts @@ -5,6 +5,8 @@ import { encodeLayout, encodeVisualizationSettings, Panel, + PanelType, + VisualizationType, } from '../../../entities'; import { ApiService } from '../../../shared/services/datacat-generated-client'; import { ToastLoggerService } from '../../../shared/services/toast-logger.service'; @@ -18,6 +20,7 @@ import { DisplayGrid, GridsterConfig, GridsterItem, + GridsterItemComponentInterface, GridsterModule, GridType, } from 'angular-gridster2'; @@ -73,6 +76,7 @@ export class PanelsGridComponent { this._dashboardId = id; this.refreshDashboard(); this.refreshDashboardVariables(); + this.refreshPanelTypes(); } protected isSaving = false; @@ -93,6 +97,8 @@ export class PanelsGridComponent { protected panels: Panel[] = []; + protected panelTypes: PanelType[] = []; + protected gridsterItems: GridsterItem[] = []; protected gridsterOptions: GridsterConfig = { gridType: GridType.Fixed, @@ -108,8 +114,25 @@ export class PanelsGridComponent { enabled: true, }, enableBoundaryControl: true, + itemChangeCallback: this.handleGridsterItemChange.bind(this), }; + protected refreshPanelTypes() { + this.apiService.getApiV1PanelTypes().subscribe({ + next: (data) => { + this.panelTypes = data.map((d) => { + return { + id: d.id || 0, + type: d.name as VisualizationType, + }; + }); + }, + error: (e) => { + this.loggerService.error(e); + }, + }); + } + protected refreshDashboard() { if (!this._dashboardId) return; @@ -131,6 +154,7 @@ export class PanelsGridComponent { id: item.id || '', title: item.title || '', query: item.query || '', + visualizationType: item.panelType as VisualizationType, layout: decodeLayout(item.layout), }; }) || []; @@ -209,25 +233,7 @@ export class PanelsGridComponent { protected saveLayout() { this.isSaving = true; - const observables = this.gridsterItems.map((item) => { - const panel = this.panels.filter((p) => p.id == item['panelId']).at(0); - const request: any = { - title: panel?.title, - type: panel?.visualizationType, - rawQuery: panel?.query, - dataSourceId: panel?.dataSource?.id, - styleConfiguration: encodeVisualizationSettings( - panel?.visualizationSettings, - ), - layout: encodeLayout({ - x: item.x, - y: item.y, - cols: item.cols, - rows: item.rows, - }), - }; - return this.apiService.putApiV1PanelUpdate(item['panelId'], request); - }); + const observables = this.panelsComponents.map((pc) => pc.saveLayout()); forkJoin(observables) .pipe( @@ -244,4 +250,19 @@ export class PanelsGridComponent { }, }); } + + protected handleGridsterItemChange( + item: GridsterItem, + component: GridsterItemComponentInterface, + ) { + const panelId = item['panelId']; + this.panelsComponents + .find((pc) => pc.panelId == panelId) + ?.updateLayout({ + x: item.x, + y: item.y, + cols: item.cols, + rows: item.rows, + }); + } } diff --git a/frontend/datacat-ui/src/shared/ui/panel-visualization-options/panel-visualization-options.component.ts b/frontend/datacat-ui/src/shared/ui/panel-visualization-options/panel-visualization-options.component.ts index e5f778a..fb47cc1 100644 --- a/frontend/datacat-ui/src/shared/ui/panel-visualization-options/panel-visualization-options.component.ts +++ b/frontend/datacat-ui/src/shared/ui/panel-visualization-options/panel-visualization-options.component.ts @@ -66,9 +66,6 @@ export class PanelVisualizationOptionsComponent implements OnInit { this.updateOptionsForm(); this.emit(); }); - this.optionsForm?.valueChanges.subscribe(() => { - this.emit(); - }); } ngOnInit() { @@ -84,5 +81,8 @@ export class PanelVisualizationOptionsComponent implements OnInit { private updateOptionsForm() { this.optionsForm = createOptionsForm(this.visualizationType); + this.optionsForm.valueChanges.subscribe(() => { + this.emit(); + }); } } diff --git a/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts b/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts index 27a300e..3e36c16 100644 --- a/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts +++ b/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts @@ -3,7 +3,6 @@ import { VisualizationSettings, VisualizationType } from '../../../entities'; import { ChartModule } from 'primeng/chart'; import { BASIC_OPTIONS } from './consts'; import { DataPoints } from '../../../entities/dashboards/data.types'; -import { ThemeProvider } from 'primeng/config'; @Component({ standalone: true, @@ -20,8 +19,10 @@ export class PanelVisualizationComponent { protected chartRef: any; @ViewChild('chart') protected set chart(ref: any) { - this.chartRef = ref; - this.chartRef?.chart?.update(); + if (ref) { + this.chartRef = ref; + this.chartRef?.chart?.update(); + } } @Input() public visualizationType?: VisualizationType; @@ -41,15 +42,13 @@ export class PanelVisualizationComponent { } protected parseSettingsIntoChartjsOptions(settings: VisualizationSettings) { - const chart: any = this.chartRef?.chart; - this.chartjsOptions.plugins.legend.display = settings.legend?.enabled; this.chartjsOptions.plugins.legend.position = settings.legend?.position; this.chartjsOptions.plugins.title.display = settings.title?.enabled; this.chartjsOptions.plugins.title.text = settings.title?.text; - chart?.update(); + this.chartRef?.chart?.update(); } protected parseDataIntoChartjsData(data: DataPoints) { @@ -63,19 +62,11 @@ export class PanelVisualizationComponent { ], }; - this.chart?.update(); + this.chartRef?.chart?.update(); } protected chartjsData: any = { - labels: ['1', '2', '3', '4', '5', '6'], - datasets: [ - { - order: 0, - label: 'Label 1', - data: [1, 8, 3, 2, 5, 10], - // borderColor: 'red', - // backgroundColor: 'blue', - }, - ], + labels: [], + datasets: [], }; } From 94decf537eaf172c20ee2111a33e8829c8b5c004 Mon Sep 17 00:00:00 2001 From: Nikita Nazarov Date: Thu, 22 May 2025 14:23:01 +0300 Subject: [PATCH 03/11] feat(datacat-ui): add time range select, wire panels on grid with API --- .../panel-in-grid/panel-in-grid.component.ts | 41 +++++++++++- .../panels-grid/panels-grid.component.html | 10 +-- .../panels-grid/panels-grid.component.scss | 8 ++- .../panels-grid/panels-grid.component.ts | 17 +++++ .../src/shared/ui/time-range-select/index.ts | 2 + .../time-range-select.component.html | 31 ++++++++++ .../time-range-select.component.scss | 15 +++++ .../time-range-select.component.ts | 62 +++++++++++++++++++ .../time-range-select.consts.ts | 26 ++++++++ .../time-range-select.types.ts | 5 ++ 10 files changed, 207 insertions(+), 10 deletions(-) create mode 100644 frontend/datacat-ui/src/shared/ui/time-range-select/index.ts create mode 100644 frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.html create mode 100644 frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.scss create mode 100644 frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.ts create mode 100644 frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.consts.ts create mode 100644 frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.types.ts diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts index d2b37e6..1e7d449 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts @@ -16,10 +16,13 @@ import { ToastLoggerService } from '../../../../shared/services/toast-logger.ser import { ButtonModule } from 'primeng/button'; import { Router } from '@angular/router'; import * as urls from '../../../../shared/common/urls'; -import { DataPoints } from '../../../../entities/dashboards/data.types'; +import { + DataPoint, + DataPoints, +} from '../../../../entities/dashboards/data.types'; import { DatePipe } from '@angular/common'; import { Observable, of } from 'rxjs'; -import { GridsterItem } from 'angular-gridster2'; +import { TimeRange } from '../../../../shared/ui/time-range-select'; @Component({ standalone: true, @@ -100,6 +103,40 @@ export class PanelInGridComponent { ]; } + public refreshTimeRange(timeRange: TimeRange) { + if (this.panel && this.panel.dataSource) { + this.apiService + .getApiV1MetricsQueryRange( + this.panel.dataSource.name, + this.panel.query, + 'undefined' as any, + null, + timeRange.from, + timeRange.to, + timeRange.step, + ) + .subscribe({ + next: (data) => { + if (data.length !== 0) { + const datepipe = new DatePipe('en-US'); + this.data = + data[0].points?.map((mp) => { + return { + value: mp.value || 0, + timestamp: datepipe.transform(mp.timestamp) || '', + }; + }) || []; + } else { + this.data = []; + } + }, + error: (e) => { + this.loggerService.error(e); + }, + }); + } + } + public saveLayout(): Observable { if (this.panel) { const request = { diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.html b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.html index 34cd325..e9c13c9 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.html +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.html @@ -20,13 +20,6 @@ />
- - }
+
diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.scss b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.scss index a7f14e0..269e9a2 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.scss +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.scss @@ -8,7 +8,7 @@ .panels-grid { margin-top: var(--p-padding-md); display: block; - height: 600px; + height: 580px; } .container-header { @@ -22,6 +22,12 @@ } } +.container-footer { + display: flex; + justify-content: start; + gap: var(--p-padding-md); +} + gridster-item { background: transparent; } diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts index 4068308..5baa1f6 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts @@ -33,6 +33,10 @@ import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { AddVariableButtonComponent } from '../add-variable'; import { DeleteVariableButtonComponent } from '../delete-variable'; import { DatePickerModule } from 'primeng/datepicker'; +import { + TimeRange, + TimeRangeSelectComponent, +} from '../../../shared/ui/time-range-select'; @Component({ standalone: true, @@ -54,6 +58,7 @@ import { DatePickerModule } from 'primeng/datepicker'; AddVariableButtonComponent, DeleteVariableButtonComponent, DatePickerModule, + TimeRangeSelectComponent, ], }) export class PanelsGridComponent { @@ -68,6 +73,9 @@ export class PanelsGridComponent { this.refreshRateControl.valueChanges.subscribe((seconds) => this.setRefreshRate(seconds), ); + this.timeRangeControl.valueChanges.subscribe((timeRange) => { + this.updateTimeRange(timeRange); + }); } protected _dashboardId?: string; @@ -92,6 +100,7 @@ export class PanelsGridComponent { ]; protected refreshRateControl = new FormControl(null); protected refreshRateSubscription?: Subscription; + protected timeRangeControl = new FormControl(null); protected variables: DashboardVariable[] = []; @@ -265,4 +274,12 @@ export class PanelsGridComponent { rows: item.rows, }); } + + protected updateTimeRange(timeRange: TimeRange | null) { + if (timeRange) { + this.panelsComponents.forEach((pc) => { + pc.refreshTimeRange(timeRange); + }); + } + } } diff --git a/frontend/datacat-ui/src/shared/ui/time-range-select/index.ts b/frontend/datacat-ui/src/shared/ui/time-range-select/index.ts new file mode 100644 index 0000000..8209ca4 --- /dev/null +++ b/frontend/datacat-ui/src/shared/ui/time-range-select/index.ts @@ -0,0 +1,2 @@ +export { TimeRangeSelectComponent } from './time-range-select.component'; +export { TimeRange } from './time-range-select.types'; diff --git a/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.html b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.html new file mode 100644 index 0000000..26ed68e --- /dev/null +++ b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.html @@ -0,0 +1,31 @@ +
+
+

Step

+ +
+
+

From

+ +
+
+

To

+ +
+
diff --git a/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.scss b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.scss new file mode 100644 index 0000000..206e57e --- /dev/null +++ b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.scss @@ -0,0 +1,15 @@ +.container { + display: flex; + gap: var(--p-padding-md); + align-items: center; +} + +.item { + display: flex; + flex-direction: column; + gap: var(--p-padding-xs); + + p { + color: var(--p-surface-400); + } +} diff --git a/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.ts b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.ts new file mode 100644 index 0000000..cbe672a --- /dev/null +++ b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.ts @@ -0,0 +1,62 @@ +import { Component, forwardRef } from '@angular/core'; +import { + ControlValueAccessor, + FormControl, + FormGroup, + NG_VALUE_ACCESSOR, + ReactiveFormsModule, +} from '@angular/forms'; +import { DatePickerModule } from 'primeng/datepicker'; +import { TimeRange } from './time-range-select.types'; +import { SelectModule } from 'primeng/select'; +import { STEP_OPTIONS } from './time-range-select.consts'; + +@Component({ + standalone: true, + selector: 'datacat-time-range-select', + templateUrl: './time-range-select.component.html', + styleUrl: './time-range-select.component.scss', + imports: [DatePickerModule, SelectModule, ReactiveFormsModule], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimeRangeSelectComponent), + multi: true, + }, + ], +}) +export class TimeRangeSelectComponent implements ControlValueAccessor { + protected stepOptions = STEP_OPTIONS; + + private onChange = (_: any) => {}; + private onTouched = () => {}; + + protected formGroup = new FormGroup({ + step: new FormControl(this.stepOptions[0].value), + from: new FormControl(new Date()), + to: new FormControl(new Date()), + }); + + constructor() { + this.formGroup.valueChanges.subscribe(() => this.notifyTouchedAndChanged()); + } + + writeValue(value: TimeRange | undefined): void { + if (value) { + this.formGroup.setValue(value); + } + } + + registerOnChange(fn: any): void { + this.onChange = fn; + } + + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + notifyTouchedAndChanged() { + this.onChange(this.formGroup.getRawValue()); + this.onTouched(); + } +} diff --git a/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.consts.ts b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.consts.ts new file mode 100644 index 0000000..bdaad57 --- /dev/null +++ b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.consts.ts @@ -0,0 +1,26 @@ +export const STEP_OPTIONS = [ + { + label: '1s', + value: '00:00:01', + }, + { + label: '10s', + value: '00:00:10', + }, + { + label: '30s', + value: '00:00:30', + }, + { + label: '1m', + value: '00:01:00', + }, + { + label: '10m', + value: '00:10:00', + }, + { + label: '30m', + value: '00:30:00', + }, +]; diff --git a/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.types.ts b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.types.ts new file mode 100644 index 0000000..fcf9f27 --- /dev/null +++ b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.types.ts @@ -0,0 +1,5 @@ +export type TimeRange = { + from: Date; + to: Date; + step: string; +}; From 34a261cbf04e558c6522587cc35cb4c543afdcbd Mon Sep 17 00:00:00 2001 From: Nikita Nazarov Date: Fri, 23 May 2025 00:40:31 +0300 Subject: [PATCH 04/11] feat(datacat-ui): fix visualization settings bugs --- .../src/entities/dashboards/panel.types.ts | 10 ++-- .../edit-panel/edit-panel.component.ts | 47 ++++++++++------ .../panel-in-grid/panel-in-grid.component.ts | 3 +- .../panels-grid/panels-grid.component.ts | 55 +++++++++++++------ .../panel-visualization-options.component.ts | 8 +++ .../time-range-select.component.ts | 6 +- 6 files changed, 84 insertions(+), 45 deletions(-) diff --git a/frontend/datacat-ui/src/entities/dashboards/panel.types.ts b/frontend/datacat-ui/src/entities/dashboards/panel.types.ts index 9173542..e6d1f9b 100644 --- a/frontend/datacat-ui/src/entities/dashboards/panel.types.ts +++ b/frontend/datacat-ui/src/entities/dashboards/panel.types.ts @@ -106,9 +106,9 @@ export const encodeVisualizationType = ( case VisualizationType.LINE: return 1; case VisualizationType.BAR: - return 2; - case VisualizationType.PIE: return 3; + case VisualizationType.PIE: + return 2; default: return 4; } @@ -118,11 +118,11 @@ export const decodeVisualizationType = ( type: string | undefined, ): VisualizationType => { switch (type) { - case 'Graph': + case 'LineChart': return VisualizationType.LINE; - case 'Table': + case 'BarChart': return VisualizationType.BAR; - case 'Pie Chart': + case 'PieChart': return VisualizationType.PIE; default: return VisualizationType.UNKNOWN; diff --git a/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.ts b/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.ts index 0de0332..58600a3 100644 --- a/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.ts @@ -1,4 +1,10 @@ -import { afterNextRender, Component, Input } from '@angular/core'; +import { + afterNextRender, + AfterViewInit, + Component, + Input, + ViewChild, +} from '@angular/core'; import { PanelVisualizationComponent } from '../../../shared/ui/panel-visualization'; import { PanelVisualizationOptionsComponent } from '../../../shared/ui/panel-visualization-options'; import { PanelModule } from 'primeng/panel'; @@ -43,7 +49,7 @@ import { DataPoints } from '../../../entities/dashboards/data.types'; ButtonModule, ], }) -export class EditPanelComponent { +export class EditPanelComponent implements AfterViewInit { private _panelId?: string; @Input() public set panelId(id: string | undefined) { @@ -51,22 +57,12 @@ export class EditPanelComponent { this.refresh(); } + @ViewChild(PanelVisualizationOptionsComponent) + optionsComponent?: PanelVisualizationOptionsComponent; + protected panel?: Panel; - protected data: DataPoints = [ - { - timestamp: '1', - value: 0, - }, - { - timestamp: '2', - value: 5, - }, - { - timestamp: '3', - value: 3, - }, - ]; + protected data: DataPoints = []; protected visualizationType?: VisualizationType; protected visualizationSettings?: VisualizationSettings; @@ -84,6 +80,15 @@ export class EditPanelComponent { private loggerService: ToastLoggerService, ) {} + ngAfterViewInit() { + if (this.panel) { + this.optionsComponent?.setVisualizationSettings( + this.panel.visualizationType!, + this.panel.visualizationSettings!, + ); + } + } + protected refresh() { if (!this._panelId) return; @@ -101,10 +106,16 @@ export class EditPanelComponent { }, layout: decodeLayout(data.layout), visualizationType: decodeVisualizationType(data.typeName), - visualizationSettings: - data.styleConfiguration as VisualizationSettings, + visualizationSettings: JSON.parse( + data.styleConfiguration!, + ) as VisualizationSettings, }; + this.optionsComponent?.setVisualizationSettings( + this.panel.visualizationType!, + this.panel.visualizationSettings!, + ); + this.editForm.setValue({ title: this.panel.title, dataSourceId: this.panel.dataSource?.id, diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts index 1e7d449..16769a4 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts @@ -5,6 +5,7 @@ import { DataSourceDriver, decodeLayout, decodeVisualizationSettings, + decodeVisualizationType, encodeLayout, encodeVisualizationSettings, Layout, @@ -72,7 +73,7 @@ export class PanelInGridComponent { connectionUrl: data.query?.dataSource?.connectionString || '', }, layout: decodeLayout(data.layout), - visualizationType: VisualizationType.LINE, + visualizationType: decodeVisualizationType(data.typeName), visualizationSettings: decodeVisualizationSettings( data.styleConfiguration, ), diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts index 5baa1f6..c52b0e7 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts @@ -1,9 +1,13 @@ -import { Component, Input, QueryList, ViewChildren } from '@angular/core'; +import { + AfterViewInit, + Component, + Input, + QueryList, + ViewChildren, +} from '@angular/core'; import { DashboardVariable, decodeLayout, - encodeLayout, - encodeVisualizationSettings, Panel, PanelType, VisualizationType, @@ -37,6 +41,7 @@ import { TimeRange, TimeRangeSelectComponent, } from '../../../shared/ui/time-range-select'; +import { STEP_OPTIONS } from '../../../shared/ui/time-range-select/time-range-select.consts'; @Component({ standalone: true, @@ -61,23 +66,10 @@ import { TimeRangeSelectComponent, ], }) -export class PanelsGridComponent { +export class PanelsGridComponent implements AfterViewInit { @ViewChildren(PanelInGridComponent) public panelsComponents!: QueryList; - constructor( - private apiService: ApiService, - private loggerService: ToastLoggerService, - ) { - this.freezeGrid(); - this.refreshRateControl.valueChanges.subscribe((seconds) => - this.setRefreshRate(seconds), - ); - this.timeRangeControl.valueChanges.subscribe((timeRange) => { - this.updateTimeRange(timeRange); - }); - } - protected _dashboardId?: string; @Input() public set dashboardId(id: string) { @@ -100,7 +92,15 @@ export class PanelsGridComponent { ]; protected refreshRateControl = new FormControl(null); protected refreshRateSubscription?: Subscription; - protected timeRangeControl = new FormControl(null); + protected timeRangeControl = new FormControl({ + step: STEP_OPTIONS[0].value, + from: (() => { + const date = new Date(); + date.setDate(date.getDate() - 1); + return date; + })(), + to: new Date(), + }); protected variables: DashboardVariable[] = []; @@ -126,6 +126,25 @@ export class PanelsGridComponent { itemChangeCallback: this.handleGridsterItemChange.bind(this), }; + constructor( + private apiService: ApiService, + private loggerService: ToastLoggerService, + ) { + this.freezeGrid(); + this.refreshRateControl.valueChanges.subscribe((seconds) => + this.setRefreshRate(seconds), + ); + this.timeRangeControl.valueChanges.subscribe((timeRange) => { + this.updateTimeRange(timeRange); + }); + } + + ngAfterViewInit() { + this.panelsComponents.changes.subscribe((pcs) => + this.updateTimeRange(this.timeRangeControl.getRawValue()), + ); + } + protected refreshPanelTypes() { this.apiService.getApiV1PanelTypes().subscribe({ next: (data) => { diff --git a/frontend/datacat-ui/src/shared/ui/panel-visualization-options/panel-visualization-options.component.ts b/frontend/datacat-ui/src/shared/ui/panel-visualization-options/panel-visualization-options.component.ts index fb47cc1..f10d5fb 100644 --- a/frontend/datacat-ui/src/shared/ui/panel-visualization-options/panel-visualization-options.component.ts +++ b/frontend/datacat-ui/src/shared/ui/panel-visualization-options/panel-visualization-options.component.ts @@ -85,4 +85,12 @@ export class PanelVisualizationOptionsComponent implements OnInit { this.emit(); }); } + + public setVisualizationSettings( + type: VisualizationType, + settings: VisualizationSettings, + ) { + this.visualizationTypeControl.setValue(type); + this.optionsForm.setValue(settings); + } } diff --git a/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.ts b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.ts index cbe672a..8666261 100644 --- a/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.ts +++ b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.ts @@ -32,9 +32,9 @@ export class TimeRangeSelectComponent implements ControlValueAccessor { private onTouched = () => {}; protected formGroup = new FormGroup({ - step: new FormControl(this.stepOptions[0].value), - from: new FormControl(new Date()), - to: new FormControl(new Date()), + step: new FormControl(null), + from: new FormControl(null), + to: new FormControl(null), }); constructor() { From 7f14f71aeef80ed7670996ab8df5872d3610768a Mon Sep 17 00:00:00 2001 From: Nikita Nazarov Date: Fri, 23 May 2025 16:52:46 +0300 Subject: [PATCH 05/11] feat(datacat-ui): add dashboard delete and edit buttons; make dashboad panel display dashboard name and description; fix various bugs in visualization --- .../src/entities/dashboards/data.types.ts | 4 + .../src/entities/dashboards/panel.types.ts | 6 -- .../dashboards-list.component.html | 2 +- .../delete-dashboard-button.component.html | 31 +++++++ .../delete-dashboard-button.component.scss | 9 ++ .../delete-dashboard-button.component.ts | 53 +++++++++++ .../dashboards/delete-dashboard/index.ts | 1 + .../edit-dashboard-button.component.html | 48 ++++++++++ .../edit-dashboard-button.component.scss | 32 +++++++ .../edit-dashboard-button.component.ts | 92 +++++++++++++++++++ .../dashboards/edit-dashboard/index.ts | 1 + .../panel-in-grid.component.html | 45 +++++++-- .../panel-in-grid.component.scss | 16 ++++ .../panel-in-grid/panel-in-grid.component.ts | 40 ++++++-- .../panels-grid/panels-grid.component.scss | 2 +- .../panels-grid/panels-grid.component.ts | 4 +- .../view-dashboard.component.html | 15 ++- .../view-dashboard.component.scss | 9 +- .../view-dashboard.component.ts | 62 ++++++++++++- .../ui/panel-visualization-options/consts.ts | 15 +++ .../ui/panel-visualization-options/forms.ts | 3 + .../tooltip-options.component.html | 2 +- ...panel-visualization-options.component.html | 2 +- .../panel-visualization-options.component.ts | 3 +- .../panel-visualization.component.html | 64 +++++++------ .../panel-visualization.component.ts | 23 ++++- 26 files changed, 517 insertions(+), 67 deletions(-) create mode 100644 frontend/datacat-ui/src/features/dashboards/delete-dashboard/delete-dashboard-button.component.html create mode 100644 frontend/datacat-ui/src/features/dashboards/delete-dashboard/delete-dashboard-button.component.scss create mode 100644 frontend/datacat-ui/src/features/dashboards/delete-dashboard/delete-dashboard-button.component.ts create mode 100644 frontend/datacat-ui/src/features/dashboards/delete-dashboard/index.ts create mode 100644 frontend/datacat-ui/src/features/dashboards/edit-dashboard/edit-dashboard-button.component.html create mode 100644 frontend/datacat-ui/src/features/dashboards/edit-dashboard/edit-dashboard-button.component.scss create mode 100644 frontend/datacat-ui/src/features/dashboards/edit-dashboard/edit-dashboard-button.component.ts create mode 100644 frontend/datacat-ui/src/features/dashboards/edit-dashboard/index.ts create mode 100644 frontend/datacat-ui/src/shared/ui/panel-visualization-options/consts.ts diff --git a/frontend/datacat-ui/src/entities/dashboards/data.types.ts b/frontend/datacat-ui/src/entities/dashboards/data.types.ts index 4819862..a7922e4 100644 --- a/frontend/datacat-ui/src/entities/dashboards/data.types.ts +++ b/frontend/datacat-ui/src/entities/dashboards/data.types.ts @@ -4,3 +4,7 @@ export type DataPoint = { }; export type DataPoints = DataPoint[]; + +// export type TimeSeries = { +// labels +// } diff --git a/frontend/datacat-ui/src/entities/dashboards/panel.types.ts b/frontend/datacat-ui/src/entities/dashboards/panel.types.ts index e6d1f9b..c1c6acc 100644 --- a/frontend/datacat-ui/src/entities/dashboards/panel.types.ts +++ b/frontend/datacat-ui/src/entities/dashboards/panel.types.ts @@ -9,8 +9,6 @@ export enum VisualizationType { LINE = 'line', BAR = 'bar', PIE = 'pie', - GAUGE = 'gauge', - TABLE = 'table', UNKNOWN = 'unknown', } @@ -56,10 +54,6 @@ export type Panel = { visualizationSettings?: VisualizationSettings; }; -export type LineStyle = { - lineWidth: number; -}; - export const decodeLayout = (encoded: string | undefined): Layout => { if (encoded) { try { diff --git a/frontend/datacat-ui/src/features/dashboards/dashboards-list/dashboards-list.component.html b/frontend/datacat-ui/src/features/dashboards/dashboards-list/dashboards-list.component.html index a69c232..529cb0e 100644 --- a/frontend/datacat-ui/src/features/dashboards/dashboards-list/dashboards-list.component.html +++ b/frontend/datacat-ui/src/features/dashboards/dashboards-list/dashboards-list.component.html @@ -54,7 +54,7 @@ - + diff --git a/frontend/datacat-ui/src/features/dashboards/delete-dashboard/delete-dashboard-button.component.html b/frontend/datacat-ui/src/features/dashboards/delete-dashboard/delete-dashboard-button.component.html new file mode 100644 index 0000000..9e2a050 --- /dev/null +++ b/frontend/datacat-ui/src/features/dashboards/delete-dashboard/delete-dashboard-button.component.html @@ -0,0 +1,31 @@ + + +

Are you sure you want to delete this dashboard?

+ @if (isDeletionError) { +

Unable to delete dashboard

+ } +
+ + +
+
diff --git a/frontend/datacat-ui/src/features/dashboards/delete-dashboard/delete-dashboard-button.component.scss b/frontend/datacat-ui/src/features/dashboards/delete-dashboard/delete-dashboard-button.component.scss new file mode 100644 index 0000000..340d0cc --- /dev/null +++ b/frontend/datacat-ui/src/features/dashboards/delete-dashboard/delete-dashboard-button.component.scss @@ -0,0 +1,9 @@ +.actions-container { + display: flex; + justify-content: space-between; + margin-top: var(--p-padding-md); +} + +.error { + margin-top: var(--p-padding-sm); +} diff --git a/frontend/datacat-ui/src/features/dashboards/delete-dashboard/delete-dashboard-button.component.ts b/frontend/datacat-ui/src/features/dashboards/delete-dashboard/delete-dashboard-button.component.ts new file mode 100644 index 0000000..424ad27 --- /dev/null +++ b/frontend/datacat-ui/src/features/dashboards/delete-dashboard/delete-dashboard-button.component.ts @@ -0,0 +1,53 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ButtonModule } from 'primeng/button'; +import { DialogModule } from 'primeng/dialog'; +import { ApiService } from '../../../shared/services/datacat-generated-client'; +import { ToastLoggerService } from '../../../shared/services/toast-logger.service'; + +@Component({ + standalone: true, + selector: 'datacat-delete-dashboard-button', + templateUrl: './delete-dashboard-button.component.html', + styleUrl: './delete-dashboard-button.component.scss', + imports: [ButtonModule, DialogModule], +}) +export class DeleteDashboardButtonComponent { + @Output() onDelete = new EventEmitter(); + + @Input() dashboardId?: string; + protected isDeletionInitiated = false; + protected isDeletionDialogVisible = false; + protected isDeletionError = false; + + constructor( + private apiService: ApiService, + private loggerService: ToastLoggerService, + ) {} + + protected showDeletionDialog() { + this.isDeletionError = false; + this.isDeletionDialogVisible = true; + } + + protected hideDeletionDialog() { + this.isDeletionDialogVisible = false; + } + + protected deleteDashboard() { + this.isDeletionError = false; + this.isDeletionInitiated = true; + if (this.dashboardId) { + this.apiService.deleteApiV1DashboardRemove(this.dashboardId).subscribe({ + next: () => { + this.loggerService.success('Deleted dashboard'); + this.onDelete.emit(); + }, + error: (e) => { + this.loggerService.error(e); + this.isDeletionInitiated = false; + this.isDeletionError = true; + }, + }); + } + } +} diff --git a/frontend/datacat-ui/src/features/dashboards/delete-dashboard/index.ts b/frontend/datacat-ui/src/features/dashboards/delete-dashboard/index.ts new file mode 100644 index 0000000..b977585 --- /dev/null +++ b/frontend/datacat-ui/src/features/dashboards/delete-dashboard/index.ts @@ -0,0 +1 @@ +export { DeleteDashboardButtonComponent } from './delete-dashboard-button.component'; diff --git a/frontend/datacat-ui/src/features/dashboards/edit-dashboard/edit-dashboard-button.component.html b/frontend/datacat-ui/src/features/dashboards/edit-dashboard/edit-dashboard-button.component.html new file mode 100644 index 0000000..4969b29 --- /dev/null +++ b/frontend/datacat-ui/src/features/dashboards/edit-dashboard/edit-dashboard-button.component.html @@ -0,0 +1,48 @@ + + + +
+
+
+

Name

+ +
+
+

Description

+ +
+
+ @if (nameControl.invalid) { +

* Name is required

+ } + @if (descriptionControl.invalid) { +

* Description is required

+ } +
+
+
+ + + +
diff --git a/frontend/datacat-ui/src/features/dashboards/edit-dashboard/edit-dashboard-button.component.scss b/frontend/datacat-ui/src/features/dashboards/edit-dashboard/edit-dashboard-button.component.scss new file mode 100644 index 0000000..308f334 --- /dev/null +++ b/frontend/datacat-ui/src/features/dashboards/edit-dashboard/edit-dashboard-button.component.scss @@ -0,0 +1,32 @@ +.creation-form { + display: flex; + flex-direction: column; + gap: var(--p-padding-md); + + &__item { + display: flex; + flex-direction: column; + gap: var(--p-padding-xs); + } + + &__item p { + color: var(--p-surface-400); + } + + &__footer { + display: flex; + justify-content: end; + } +} + +.creation-dialog { + display: flex; + gap: var(--p-padding-md); +} + +.validation-errors { + display: flex; + flex-direction: column; + gap: var(--p-padding-xs); + color: var(--p-surface-400); +} diff --git a/frontend/datacat-ui/src/features/dashboards/edit-dashboard/edit-dashboard-button.component.ts b/frontend/datacat-ui/src/features/dashboards/edit-dashboard/edit-dashboard-button.component.ts new file mode 100644 index 0000000..2b31770 --- /dev/null +++ b/frontend/datacat-ui/src/features/dashboards/edit-dashboard/edit-dashboard-button.component.ts @@ -0,0 +1,92 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ButtonModule } from 'primeng/button'; +import { DialogModule } from 'primeng/dialog'; +import { + FormControl, + FormGroup, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; +import { finalize } from 'rxjs'; +import { InputTextModule } from 'primeng/inputtext'; +import { TextareaModule } from 'primeng/textarea'; +import { PanelModule } from 'primeng/panel'; +import { ApiService } from '../../../shared/services/datacat-generated-client'; +import { ToastLoggerService } from '../../../shared/services/toast-logger.service'; + +@Component({ + standalone: true, + selector: './datacat-edit-dashboard-button', + templateUrl: './edit-dashboard-button.component.html', + styleUrl: './edit-dashboard-button.component.scss', + imports: [ + ButtonModule, + DialogModule, + ReactiveFormsModule, + InputTextModule, + TextareaModule, + PanelModule, + ], +}) +export class EditDashboardButtonComponent { + @Output() onEdit = new EventEmitter(); + + @Input() public dashboardId?: string; + + protected isEditDialogVisible = false; + protected isEditInitiated = false; + + protected editForm = new FormGroup({ + name: new FormControl('', Validators.required), + description: new FormControl('', Validators.required), + }); + + protected get nameControl() { + return this.editForm.get('name')!; + } + + protected get descriptionControl() { + return this.editForm.get('description')!; + } + + constructor( + private apiService: ApiService, + private loggerService: ToastLoggerService, + ) {} + + protected editDashboard() { + this.editForm.markAllAsTouched(); + this.editForm.updateValueAndValidity(); + + if (this.editForm.invalid || !this.dashboardId) return; + + this.isEditInitiated = true; + + const request: any = this.editForm.getRawValue(); + + this.apiService + .putApiV1DashboardUpdate(this.dashboardId, request) + .pipe( + finalize(() => { + this.isEditInitiated = false; + }), + ) + .subscribe({ + next: () => { + this.isEditDialogVisible = false; + this.editForm.reset(); + this.onEdit.emit(); + }, + error: (e) => { + this.loggerService.error(e); + }, + }); + } + + public fillForm(name: string, description: string) { + this.editForm.setValue({ + name, + description, + }); + } +} diff --git a/frontend/datacat-ui/src/features/dashboards/edit-dashboard/index.ts b/frontend/datacat-ui/src/features/dashboards/edit-dashboard/index.ts new file mode 100644 index 0000000..214c365 --- /dev/null +++ b/frontend/datacat-ui/src/features/dashboards/edit-dashboard/index.ts @@ -0,0 +1 @@ +export { EditDashboardButtonComponent } from './edit-dashboard-button.component'; diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.html b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.html index eb7c515..076724a 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.html +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.html @@ -2,18 +2,27 @@

{{ panel?.title || 'Unnamed panel' }}

- +
+ + +
@if (isRefreshError) { -

Loading error

+

Loading error

} @else { + + +
+
+

Query

+ +
+ + +
+
diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.scss b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.scss index 0b95477..28a083a 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.scss +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.scss @@ -11,8 +11,24 @@ margin-left: var(--p-padding-md); margin-right: var(--p-padding-md); margin-bottom: var(--p-padding-md); + text-align: center; } .p-panel-content { flex-grow: 1; } + +.dialog { + width: 800px; + height: 400px; +} + +.attr { + display: flex; + flex-direction: column; + gap: var(--p-padding-sm); + + p { + color: var(--p-surface-400); + } +} diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts index 16769a4..0b59b7f 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts @@ -10,7 +10,6 @@ import { encodeVisualizationSettings, Layout, Panel, - VisualizationType, } from '../../../../entities'; import { ApiService } from '../../../../shared/services/datacat-generated-client'; import { ToastLoggerService } from '../../../../shared/services/toast-logger.service'; @@ -22,15 +21,25 @@ import { DataPoints, } from '../../../../entities/dashboards/data.types'; import { DatePipe } from '@angular/common'; -import { Observable, of } from 'rxjs'; +import { Observable, of, timer } from 'rxjs'; import { TimeRange } from '../../../../shared/ui/time-range-select'; +import { DialogModule } from 'primeng/dialog'; +import { TextareaModule } from 'primeng/textarea'; +import { DividerModule } from 'primeng/divider'; @Component({ standalone: true, selector: 'datacat-panel-in-grid', templateUrl: './panel-in-grid.component.html', styleUrl: './panel-in-grid.component.scss', - imports: [PanelModule, PanelVisualizationComponent, ButtonModule], + imports: [ + PanelModule, + PanelVisualizationComponent, + ButtonModule, + DialogModule, + TextareaModule, + DividerModule, + ], }) export class PanelInGridComponent { private _panelId?: string; @@ -50,6 +59,10 @@ export class PanelInGridComponent { protected isRefreshError = false; + protected isDialogShown = false; + + protected timeRange?: TimeRange; + constructor( private router: Router, private apiService: ApiService, @@ -78,6 +91,7 @@ export class PanelInGridComponent { data.styleConfiguration, ), }; + this.refreshTimeRange(this.timeRange); }, error: (e) => { this.loggerService.error(e); @@ -104,8 +118,16 @@ export class PanelInGridComponent { ]; } - public refreshTimeRange(timeRange: TimeRange) { + public refreshTimeRange(timeRange: TimeRange | undefined) { + if (timeRange) { + this.timeRange = timeRange; + this.loadTimeRangeData(timeRange); + } + } + + protected loadTimeRangeData(timeRange: TimeRange) { if (this.panel && this.panel.dataSource) { + this.isRefreshError = false; this.apiService .getApiV1MetricsQueryRange( this.panel.dataSource.name, @@ -119,7 +141,9 @@ export class PanelInGridComponent { .subscribe({ next: (data) => { if (data.length !== 0) { - const datepipe = new DatePipe('en-US'); + const datepipe = new DatePipe('en-US', undefined, { + dateFormat: 'M/d/yy, h:mm a', + }); this.data = data[0].points?.map((mp) => { return { @@ -132,7 +156,7 @@ export class PanelInGridComponent { } }, error: (e) => { - this.loggerService.error(e); + this.isRefreshError = true; }, }); } @@ -160,4 +184,8 @@ export class PanelInGridComponent { this.panel.layout = layout; } } + + public showDialog() { + this.isDialogShown = true; + } } diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.scss b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.scss index 269e9a2..269ac2c 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.scss +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.scss @@ -8,7 +8,7 @@ .panels-grid { margin-top: var(--p-padding-md); display: block; - height: 580px; + height: 570px; } .container-header { diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts index c52b0e7..476218c 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts @@ -93,10 +93,10 @@ export class PanelsGridComponent implements AfterViewInit { protected refreshRateControl = new FormControl(null); protected refreshRateSubscription?: Subscription; protected timeRangeControl = new FormControl({ - step: STEP_OPTIONS[0].value, + step: '00:30:00', from: (() => { const date = new Date(); - date.setDate(date.getDate() - 1); + date.setMinutes(date.getMinutes() - 360); return date; })(), to: new Date(), diff --git a/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.html b/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.html index 9fa9b0d..3b02baa 100644 --- a/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.html +++ b/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.html @@ -1,7 +1,20 @@
-

Dashboard {{ dashboardId }}

+
+

{{ dashboard?.name }}

+ +
+
+ + +
diff --git a/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.scss b/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.scss index fbd24f1..574a96e 100644 --- a/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.scss +++ b/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.scss @@ -1,5 +1,12 @@ .header { display: flex; - gap: var(--p-padding-md); align-items: center; + justify-content: space-between; + width: 100%; + + &__name { + display: flex; + align-items: center; + gap: var(--p-padding-md); + } } diff --git a/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.ts b/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.ts index 335e3cd..5f2daec 100644 --- a/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.ts +++ b/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.ts @@ -1,15 +1,71 @@ -import { Component, Input } from '@angular/core'; +import { AfterContentInit, Component, Input, ViewChild } from '@angular/core'; import { PanelModule } from 'primeng/panel'; import { ButtonModule } from 'primeng/button'; import { PanelsGridComponent } from '../../features/dashboards/panels-grid/panels-grid.component'; +import { DeleteDashboardButtonComponent } from '../../features/dashboards/delete-dashboard'; +import { Router } from '@angular/router'; +import * as urls from '../../shared/common/urls'; +import { EditDashboardButtonComponent } from '../../features/dashboards/edit-dashboard'; +import { Dashboard } from '../../entities'; +import { ApiService } from '../../shared/services/datacat-generated-client'; +import { ToastLoggerService } from '../../shared/services/toast-logger.service'; +import { TooltipModule } from 'primeng/tooltip'; @Component({ standalone: true, selector: 'datacat-view-dashboard', templateUrl: './view-dashboard.component.html', styleUrl: './view-dashboard.component.scss', - imports: [PanelModule, ButtonModule, PanelsGridComponent], + imports: [ + PanelModule, + ButtonModule, + PanelsGridComponent, + DeleteDashboardButtonComponent, + EditDashboardButtonComponent, + TooltipModule, + ], }) -export class ViewDashboardComponent { +export class ViewDashboardComponent implements AfterContentInit { @Input() protected dashboardId: string = ''; + + @ViewChild(EditDashboardButtonComponent) + editDashboardButtonComponent?: EditDashboardButtonComponent; + + protected dashboard?: Dashboard; + + constructor( + private apiService: ApiService, + private loggerService: ToastLoggerService, + private router: Router, + ) {} + + ngAfterContentInit() { + this.refresh(); + } + + protected refresh() { + this.apiService.getApiV1Dashboard(this.dashboardId).subscribe({ + next: (data) => { + this.dashboard = { + id: data.id!, + name: data.name!, + description: data.description!, + panels: [], + createdAt: data.createdAt!, + lastUpdatedAt: data.updatedAt!, + }; + this.editDashboardButtonComponent!.fillForm( + this.dashboard.name, + this.dashboard.description, + ); + }, + error: (e) => { + this.loggerService.error(e); + }, + }); + } + + protected showDashboardsList() { + this.router.navigateByUrl(urls.DASHBOARDS_EXPLORER_URL); + } } diff --git a/frontend/datacat-ui/src/shared/ui/panel-visualization-options/consts.ts b/frontend/datacat-ui/src/shared/ui/panel-visualization-options/consts.ts new file mode 100644 index 0000000..eca8ee2 --- /dev/null +++ b/frontend/datacat-ui/src/shared/ui/panel-visualization-options/consts.ts @@ -0,0 +1,15 @@ +import { LineVisualizationOptions } from '../../../entities'; + +export const DEFAULT_OPTIONS: LineVisualizationOptions = { + title: { + enabled: false, + text: '', + }, + legend: { + enabled: false, + position: 'top', + }, + tooltip: { + enabled: true, + }, +}; diff --git a/frontend/datacat-ui/src/shared/ui/panel-visualization-options/forms.ts b/frontend/datacat-ui/src/shared/ui/panel-visualization-options/forms.ts index 4fc880c..60dad57 100644 --- a/frontend/datacat-ui/src/shared/ui/panel-visualization-options/forms.ts +++ b/frontend/datacat-ui/src/shared/ui/panel-visualization-options/forms.ts @@ -13,6 +13,9 @@ export const createOptionsForm = ( enabled: new FormControl(true), text: new FormControl('a'), }), + tooltip: new FormGroup({ + enabled: new FormControl(true), + }) }); // switch (type) { diff --git a/frontend/datacat-ui/src/shared/ui/panel-visualization-options/option-groups/tooltip-options/tooltip-options.component.html b/frontend/datacat-ui/src/shared/ui/panel-visualization-options/option-groups/tooltip-options/tooltip-options.component.html index 2a9ff85..5301dbe 100644 --- a/frontend/datacat-ui/src/shared/ui/panel-visualization-options/option-groups/tooltip-options/tooltip-options.component.html +++ b/frontend/datacat-ui/src/shared/ui/panel-visualization-options/option-groups/tooltip-options/tooltip-options.component.html @@ -2,7 +2,7 @@

Enabled

- +
diff --git a/frontend/datacat-ui/src/shared/ui/panel-visualization-options/panel-visualization-options.component.html b/frontend/datacat-ui/src/shared/ui/panel-visualization-options/panel-visualization-options.component.html index c27c536..685b310 100644 --- a/frontend/datacat-ui/src/shared/ui/panel-visualization-options/panel-visualization-options.component.html +++ b/frontend/datacat-ui/src/shared/ui/panel-visualization-options/panel-visualization-options.component.html @@ -24,7 +24,7 @@ Tooltip - + diff --git a/frontend/datacat-ui/src/shared/ui/panel-visualization-options/panel-visualization-options.component.ts b/frontend/datacat-ui/src/shared/ui/panel-visualization-options/panel-visualization-options.component.ts index f10d5fb..10253f9 100644 --- a/frontend/datacat-ui/src/shared/ui/panel-visualization-options/panel-visualization-options.component.ts +++ b/frontend/datacat-ui/src/shared/ui/panel-visualization-options/panel-visualization-options.component.ts @@ -12,6 +12,7 @@ import { TooltipOptionsComponent, } from './option-groups'; import { TitleOptionsComponent } from './option-groups/title-options/title-options.component'; +import { DEFAULT_OPTIONS } from './consts'; @Component({ standalone: true, @@ -91,6 +92,6 @@ export class PanelVisualizationOptionsComponent implements OnInit { settings: VisualizationSettings, ) { this.visualizationTypeControl.setValue(type); - this.optionsForm.setValue(settings); + this.optionsForm.setValue({ ...DEFAULT_OPTIONS, ...settings }); } } diff --git a/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.html b/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.html index 11816bd..b4c828f 100644 --- a/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.html +++ b/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.html @@ -1,34 +1,38 @@
- @switch (visualizationType) { - @case (VisualizationType.LINE) { - - - } - @case (VisualizationType.BAR) { - - - } - @case (VisualizationType.PIE) { - - - } - @default { -

Unsupported visualization

+ @if (hasData()) { + @switch (visualizationType) { + @case (VisualizationType.LINE) { + + + } + @case (VisualizationType.BAR) { + + + } + @case (VisualizationType.PIE) { + + + } + @default { +

Unsupported visualization

+ } } + } @else { +

No data

}
diff --git a/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts b/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts index 3e36c16..0b2603f 100644 --- a/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts +++ b/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts @@ -3,13 +3,14 @@ import { VisualizationSettings, VisualizationType } from '../../../entities'; import { ChartModule } from 'primeng/chart'; import { BASIC_OPTIONS } from './consts'; import { DataPoints } from '../../../entities/dashboards/data.types'; +import { CommonModule } from '@angular/common'; @Component({ standalone: true, selector: 'datacat-panel-vizualization', templateUrl: './panel-visualization.component.html', styleUrl: './panel-visualization.component.scss', - imports: [ChartModule], + imports: [ChartModule, CommonModule], }) export class PanelVisualizationComponent { protected VisualizationType = VisualizationType; @@ -18,6 +19,11 @@ export class PanelVisualizationComponent { protected chartRef: any; + protected chartjsData: any = { + labels: [], + datasets: [], + }; + @ViewChild('chart') protected set chart(ref: any) { if (ref) { this.chartRef = ref; @@ -48,6 +54,8 @@ export class PanelVisualizationComponent { this.chartjsOptions.plugins.title.display = settings.title?.enabled; this.chartjsOptions.plugins.title.text = settings.title?.text; + this.chartjsOptions.plugins.tooltip.enabled = settings.tooltip?.enabled; + this.chartRef?.chart?.update(); } @@ -65,8 +73,13 @@ export class PanelVisualizationComponent { this.chartRef?.chart?.update(); } - protected chartjsData: any = { - labels: [], - datasets: [], - }; + protected hasData(): boolean { + if (this.chartjsData.datasets.length === 0) return false; + + for (const ds of this.chartjsData.datasets) { + if (ds.data.length !== 0) return true; + } + + return false; + } } From f0696b18bbeab2846fd129f7fd544b1d7a1b1b5e Mon Sep 17 00:00:00 2001 From: Nikita Nazarov Date: Fri, 23 May 2025 21:31:02 +0300 Subject: [PATCH 06/11] feat(datacat-ui): fix bugs --- frontend/datacat-ui/src/app/app.config.ts | 80 ++++++++++--------- frontend/datacat-ui/src/app/app.style.scss | 2 +- .../src/entities/dashboards/data.types.ts | 10 +-- .../edit-panel/edit-panel.component.ts | 12 +-- .../panel-in-grid/panel-in-grid.component.ts | 34 ++++---- .../panel-visualization.component.ts | 20 ++--- 6 files changed, 82 insertions(+), 76 deletions(-) diff --git a/frontend/datacat-ui/src/app/app.config.ts b/frontend/datacat-ui/src/app/app.config.ts index bf37361..7443c4e 100644 --- a/frontend/datacat-ui/src/app/app.config.ts +++ b/frontend/datacat-ui/src/app/app.config.ts @@ -1,40 +1,46 @@ -import {APP_INITIALIZER, ApplicationConfig, provideZoneChangeDetection,} from '@angular/core'; -import {provideRouter, withComponentInputBinding} from '@angular/router'; -import {ROUTES} from '../pages/workspace.routes'; -import {provideAnimationsAsync} from '@angular/platform-browser/animations/async'; -import {providePrimeNG} from 'primeng/config'; -import {PRIMENG_CONFIG} from '../shared/primeng/primeng.config'; -import {provideHttpClient, withInterceptors} from '@angular/common/http'; -import {apiInterceptor} from '../shared/interceptors/api.interceptor'; -import {authInterceptor} from '../shared/interceptors/auth.interceptor'; -import {DialogService} from 'primeng/dynamicdialog'; -import {MessageService} from 'primeng/api'; -import {ApiService} from '../shared/services/datacat-generated-client'; -import {ThemeSelectionService} from '../features/appearence/select-theme/select-theme.service'; -import {namespaceInterceptor} from "../shared/interceptors/namespace.interceptor"; -import {NamespaceService} from "../shared/services/namespace.service"; -import {UserService} from "../shared/services/user.service"; +import { + APP_INITIALIZER, + ApplicationConfig, + provideZoneChangeDetection, +} from '@angular/core'; +import { provideRouter, withComponentInputBinding } from '@angular/router'; +import { ROUTES } from '../pages/workspace.routes'; +import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; +import { providePrimeNG } from 'primeng/config'; +import { PRIMENG_CONFIG } from '../shared/primeng/primeng.config'; +import { provideHttpClient, withInterceptors } from '@angular/common/http'; +import { apiInterceptor } from '../shared/interceptors/api.interceptor'; +import { authInterceptor } from '../shared/interceptors/auth.interceptor'; +import { DialogService } from 'primeng/dynamicdialog'; +import { MessageService } from 'primeng/api'; +import { ApiService } from '../shared/services/datacat-generated-client'; +import { ThemeSelectionService } from '../features/appearence/select-theme/select-theme.service'; +import { namespaceInterceptor } from '../shared/interceptors/namespace.interceptor'; +import { NamespaceService } from '../shared/services/namespace.service'; +import { UserService } from '../shared/services/user.service'; export const APP_CONFIG: ApplicationConfig = { - providers: [ - provideZoneChangeDetection({eventCoalescing: true}), - provideRouter(ROUTES, withComponentInputBinding()), - provideAnimationsAsync(), - provideHttpClient(withInterceptors([apiInterceptor, namespaceInterceptor, authInterceptor])), - providePrimeNG(PRIMENG_CONFIG), - DialogService, - MessageService, - ApiService, - ThemeSelectionService, - NamespaceService, - UserService, - { - provide: APP_INITIALIZER, - useFactory: (themeService: ThemeSelectionService) => { - return () => themeService.loadSavedTheme(); - }, - deps: [ThemeSelectionService], - multi: true, - }, - ], + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(ROUTES, withComponentInputBinding()), + provideAnimationsAsync(), + provideHttpClient( + withInterceptors([apiInterceptor, namespaceInterceptor, authInterceptor]), + ), + providePrimeNG(PRIMENG_CONFIG), + DialogService, + MessageService, + ApiService, + ThemeSelectionService, + NamespaceService, + UserService, + { + provide: APP_INITIALIZER, + useFactory: (themeService: ThemeSelectionService) => { + return () => themeService.loadSavedTheme(); + }, + deps: [ThemeSelectionService], + multi: true, + }, + ], }; diff --git a/frontend/datacat-ui/src/app/app.style.scss b/frontend/datacat-ui/src/app/app.style.scss index fea0bc1..d220f0e 100644 --- a/frontend/datacat-ui/src/app/app.style.scss +++ b/frontend/datacat-ui/src/app/app.style.scss @@ -73,4 +73,4 @@ .p-panel-content-container { flex-grow: 1; -} \ No newline at end of file +} diff --git a/frontend/datacat-ui/src/entities/dashboards/data.types.ts b/frontend/datacat-ui/src/entities/dashboards/data.types.ts index a7922e4..ef2e3de 100644 --- a/frontend/datacat-ui/src/entities/dashboards/data.types.ts +++ b/frontend/datacat-ui/src/entities/dashboards/data.types.ts @@ -3,8 +3,8 @@ export type DataPoint = { timestamp: string; }; -export type DataPoints = DataPoint[]; - -// export type TimeSeries = { -// labels -// } +export type TimeSeries = { + metric?: string; + labels?: { [key: string]: string; }; + dataPoints: DataPoint[]; +} diff --git a/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.ts b/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.ts index 58600a3..48c40f6 100644 --- a/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.ts @@ -1,10 +1,4 @@ -import { - afterNextRender, - AfterViewInit, - Component, - Input, - ViewChild, -} from '@angular/core'; +import { AfterViewInit, Component, Input, ViewChild } from '@angular/core'; import { PanelVisualizationComponent } from '../../../shared/ui/panel-visualization'; import { PanelVisualizationOptionsComponent } from '../../../shared/ui/panel-visualization-options'; import { PanelModule } from 'primeng/panel'; @@ -31,7 +25,7 @@ import { ApiService } from '../../../shared/services/datacat-generated-client'; import { ToastLoggerService } from '../../../shared/services/toast-logger.service'; import { ButtonModule } from 'primeng/button'; import { finalize } from 'rxjs'; -import { DataPoints } from '../../../entities/dashboards/data.types'; +import { TimeSeries } from '../../../entities/dashboards/data.types'; @Component({ standalone: true, @@ -62,7 +56,7 @@ export class EditPanelComponent implements AfterViewInit { protected panel?: Panel; - protected data: DataPoints = []; + protected data: TimeSeries[] = []; protected visualizationType?: VisualizationType; protected visualizationSettings?: VisualizationSettings; diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts index 0b59b7f..9456bb7 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts @@ -18,7 +18,7 @@ import { Router } from '@angular/router'; import * as urls from '../../../../shared/common/urls'; import { DataPoint, - DataPoints, + TimeSeries, } from '../../../../entities/dashboards/data.types'; import { DatePipe } from '@angular/common'; import { Observable, of, timer } from 'rxjs'; @@ -53,7 +53,7 @@ export class PanelInGridComponent { return this._panelId; } - protected data: DataPoints = []; + protected data: TimeSeries[] = []; protected panel?: Panel; @@ -107,15 +107,14 @@ export class PanelInGridComponent { } public refreshData() { - const datepipe = new DatePipe('en-US'); - - this.data = [ - ...this.data, - { - value: Math.random() * 10, - timestamp: datepipe.transform(Date.now(), 'dd.MM HH:mm:ss') || '', - }, - ]; + // const datepipe = new DatePipe('en-US'); + // this.data = [ + // ...this.data, + // { + // value: Math.random() * 10, + // timestamp: datepipe.transform(Date.now(), 'dd.MM HH:mm:ss') || '', + // }, + // ]; } public refreshTimeRange(timeRange: TimeRange | undefined) { @@ -145,10 +144,17 @@ export class PanelInGridComponent { dateFormat: 'M/d/yy, h:mm a', }); this.data = - data[0].points?.map((mp) => { + data.map((ts) => { return { - value: mp.value || 0, - timestamp: datepipe.transform(mp.timestamp) || '', + metric: ts.metricName, + labels: ts.labels, + dataPoints: + ts.points?.map((p) => { + return { + value: p.value!, + timestamp: datepipe.transform(p.timestamp!) || '', + }; + }) || [], }; }) || []; } else { diff --git a/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts b/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts index 0b2603f..b9af94d 100644 --- a/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts +++ b/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts @@ -2,7 +2,7 @@ import { Component, Input, ViewChild } from '@angular/core'; import { VisualizationSettings, VisualizationType } from '../../../entities'; import { ChartModule } from 'primeng/chart'; import { BASIC_OPTIONS } from './consts'; -import { DataPoints } from '../../../entities/dashboards/data.types'; +import { TimeSeries } from '../../../entities/dashboards/data.types'; import { CommonModule } from '@angular/common'; @Component({ @@ -33,7 +33,7 @@ export class PanelVisualizationComponent { @Input() public visualizationType?: VisualizationType; - @Input() public set data(data: DataPoints) { + @Input() public set data(data: TimeSeries[]) { if (data) { this.parseDataIntoChartjsData(data); } @@ -59,15 +59,15 @@ export class PanelVisualizationComponent { this.chartRef?.chart?.update(); } - protected parseDataIntoChartjsData(data: DataPoints) { + protected parseDataIntoChartjsData(data: TimeSeries[]) { this.chartjsData = { - labels: data.map((d) => d.timestamp), - datasets: [ - { - label: 'Label', - data: data.map((d) => d.value), - }, - ], + labels: data[0]?.dataPoints.map((d) => d.timestamp) || [], + datasets: data.map((ts) => { + return { + label: ts.metric, + data: ts.dataPoints.map((d) => d.value), + }; + }), }; this.chartRef?.chart?.update(); From 58d1d3a4885b741786c5cbe679ce13919b00aa72 Mon Sep 17 00:00:00 2001 From: Nikita Nazarov Date: Sat, 24 May 2025 23:01:26 +0300 Subject: [PATCH 07/11] fix(datacat-ui): extract dashboards logic into independent service --- .../src/entities/dashboards/etc.types.ts | 5 + .../src/entities/dashboards/index.ts | 1 + .../src/entities/dashboards/mappings.ts | 103 ++++++++ .../panels-grid/dashboard.service.ts | 181 +++++++++++++ .../features/dashboards/panels-grid/index.ts | 1 + .../dashboards/panels-grid/panel.service.ts | 17 ++ .../panels-grid/panels-grid.component.html | 15 +- .../panels-grid/panels-grid.component.ts | 238 ++++-------------- .../panels-grid/panels-grid.consts.ts | 21 ++ .../view-dashboard.component.html | 9 +- .../view-dashboard.component.ts | 34 +-- 11 files changed, 394 insertions(+), 231 deletions(-) create mode 100644 frontend/datacat-ui/src/entities/dashboards/etc.types.ts create mode 100644 frontend/datacat-ui/src/entities/dashboards/mappings.ts create mode 100644 frontend/datacat-ui/src/features/dashboards/panels-grid/dashboard.service.ts create mode 100644 frontend/datacat-ui/src/features/dashboards/panels-grid/panel.service.ts create mode 100644 frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.consts.ts diff --git a/frontend/datacat-ui/src/entities/dashboards/etc.types.ts b/frontend/datacat-ui/src/entities/dashboards/etc.types.ts new file mode 100644 index 0000000..fcf9f27 --- /dev/null +++ b/frontend/datacat-ui/src/entities/dashboards/etc.types.ts @@ -0,0 +1,5 @@ +export type TimeRange = { + from: Date; + to: Date; + step: string; +}; diff --git a/frontend/datacat-ui/src/entities/dashboards/index.ts b/frontend/datacat-ui/src/entities/dashboards/index.ts index d02c72d..5275871 100644 --- a/frontend/datacat-ui/src/entities/dashboards/index.ts +++ b/frontend/datacat-ui/src/entities/dashboards/index.ts @@ -1,3 +1,4 @@ export * from './dashboard.types'; export * from './panel.types'; export * from './variables.types'; +export * from './mappings'; diff --git a/frontend/datacat-ui/src/entities/dashboards/mappings.ts b/frontend/datacat-ui/src/entities/dashboards/mappings.ts new file mode 100644 index 0000000..6750e2e --- /dev/null +++ b/frontend/datacat-ui/src/entities/dashboards/mappings.ts @@ -0,0 +1,103 @@ +import { + DataSourceResponse, + GetPanelResponse, + VariableResponse, +} from '../../shared/services/datacat-generated-client'; +import { DataSource } from '../alerting'; +import { + Layout, + Panel, + VisualizationSettings, + VisualizationType, +} from './panel.types'; +import { DashboardVariable } from './variables.types'; + +const parseLayout = (s: string): Layout => { + try { + return JSON.parse(s); + } catch { + return { + x: 0, + y: 0, + rows: 3, + cols: 5, + }; + } +}; + +const parseVisualizationSettings = (s: string): VisualizationSettings => { + try { + return JSON.parse(s); + } catch { + return {}; + } +}; + +const parseVisualizationType = (s: string): VisualizationType => { + switch (s) { + case 'LineChart': + return VisualizationType.LINE; + case 'BarChart': + return VisualizationType.BAR; + case 'PieChart': + return VisualizationType.PIE; + default: + return VisualizationType.UNKNOWN; + } +}; + +export const mapDataSourceResponseToDataSource = ( + r: DataSourceResponse, +): DataSource => { + return { + id: r.id!, + name: r.name!, + driver: r.type! as any, + connectionUrl: r.connectionString!, + }; +}; + +export const mapGetPanelResponeToPanel = (r: GetPanelResponse): Panel => { + return { + id: r.id!, + title: r.title!, + query: r.query!.query!, + dataSource: mapDataSourceResponseToDataSource(r.query!.dataSource!), + layout: parseLayout(r.layout!), + visualizationType: parseVisualizationType(r.typeName!), + visualizationSettings: parseVisualizationSettings(r.styleConfiguration!), + }; +}; + +export const mapVariableResponseToDashboardVariable = ( + r: VariableResponse, +): DashboardVariable => { + return { + id: r.id!, + placeholder: r.placeholder!, + value: r.value!, + }; +}; + +export const serializeVisualizationSettings = ( + vs: VisualizationSettings, +): string => { + return JSON.stringify(vs); +}; + +export const serializeLayout = (layout: Layout): string => { + return JSON.stringify(layout); +}; + +export const serializeVisualizationType = (type: VisualizationType): number => { + switch (type) { + case VisualizationType.LINE: + return 1; + case VisualizationType.BAR: + return 3; + case VisualizationType.PIE: + return 2; + default: + return 4; + } +}; diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/dashboard.service.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/dashboard.service.ts new file mode 100644 index 0000000..7ff2496 --- /dev/null +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/dashboard.service.ts @@ -0,0 +1,181 @@ +import { Injectable } from '@angular/core'; +import { + BehaviorSubject, + finalize, + forkJoin, + Observable, + ObservableLike, +} from 'rxjs'; +import { + Dashboard, + DashboardVariable, + decodeLayout, + mapGetPanelResponeToPanel, + mapVariableResponseToDashboardVariable, + Panel, + serializeLayout, + serializeVisualizationSettings, + serializeVisualizationType, +} from '../../../entities'; +import { TimeRange } from '../../../entities/dashboards/etc.types'; +import { ApiService } from '../../../shared/services/datacat-generated-client'; +import { ToastLoggerService } from '../../../shared/services/toast-logger.service'; + +@Injectable({ + providedIn: 'root', +}) +export class DashboardService { + public dashboard$: Observable; + public timeRange$: Observable; + public refreshCall$: Observable; + public panels$: Observable; + public isBusy$: Observable; + public variables$: Observable; + + private dashboardSubject = new BehaviorSubject(null); + private timeRangeSubject = new BehaviorSubject(null); + private panelsSubject = new BehaviorSubject(null); + private isBusySubject = new BehaviorSubject(false); + private variablesSubject = new BehaviorSubject( + null, + ); + + private panels?: Panel[]; + private dashboard?: Dashboard; + + constructor( + private api: ApiService, + private logger: ToastLoggerService, + ) { + this.dashboard$ = this.dashboardSubject.asObservable(); + this.timeRange$ = this.timeRangeSubject.asObservable(); + this.refreshCall$ = {} as any; + this.panels$ = this.panelsSubject.asObservable(); + this.isBusy$ = this.isBusySubject.asObservable(); + this.variables$ = this.variablesSubject.asObservable(); + } + + public set dashboardId(id: string) { + if (this.dashboard?.id === id) return; + this.refreshDashboardPanelsById(id); + } + + public set timeRange(tr: TimeRange) { + this.timeRangeSubject.next(tr); + } + + public savePanelsLayout() { + this.isBusySubject.next(true); + const requests = this.panels!.map((p) => { + const request = { + title: p.title, + type: serializeVisualizationType(p.visualizationType!), + rawQuery: p.query, + dataSourceId: p.dataSource!.id, + layout: serializeLayout(p.layout!), + styleConfiguration: serializeVisualizationSettings( + p.visualizationSettings!, + ), + } as any; + return this.api.putApiV1PanelUpdate(p.id, request); + }); + + forkJoin(requests) + .pipe(finalize(() => this.isBusySubject.next(false))) + .subscribe({ + error: () => { + this.logger.error('Cannot save full layout'); + }, + }); + } + + public refreshDashboardVariables() { + if (!this.dashboard) return; + + this.api.getApiV1VariablesDashboard(this.dashboard.id).subscribe({ + next: (data) => { + const variables = data.map(mapVariableResponseToDashboardVariable); + this.variablesSubject.next(variables); + }, + error: () => { + this.logger.error('Unable to update variables'); + }, + }); + } + + public refreshDashboardOnly() { + if (!this.dashboard) return; + this.api + .getApiV1DashboardFull(this.dashboard.id) + .pipe(finalize(() => this.isBusySubject.next(false))) + .subscribe({ + next: (data) => { + const panels = data.panels!.map((p) => { + return { + id: p.id!, + title: p.title!, + query: p.query!, + layout: decodeLayout(p.layout!), + }; + }); + this.dashboard = { + id: data.id!, + name: data.name!, + description: data.description!, + panels, + createdAt: data.createdAt!, + lastUpdatedAt: data.updatedAt!, + }; + this.dashboardSubject.next(this.dashboard!); + }, + error: () => {}, + }); + } + + public refreshDashboardPanels() { + if (!this.dashboard) return; + this.refreshDashboardPanelsById(this.dashboard.id); + } + + private refreshDashboardPanelsById(id: string) { + this.isBusySubject.next(true); + this.api + .getApiV1DashboardFull(id) + .pipe(finalize(() => this.isBusySubject.next(false))) + .subscribe({ + next: (data) => { + const panels = data.panels!.map((p) => { + return { + id: p.id!, + title: p.title!, + query: p.query!, + layout: decodeLayout(p.layout!), + }; + }); + this.dashboard = { + id: data.id!, + name: data.name!, + description: data.description!, + panels, + createdAt: data.createdAt!, + lastUpdatedAt: data.updatedAt!, + }; + this.dashboardSubject.next(this.dashboard!); + + const requests = panels.map((p) => this.api.getApiV1Panel(p.id)); + forkJoin(requests).subscribe({ + next: (data) => { + this.panels = data.map(mapGetPanelResponeToPanel); + this.panelsSubject.next(this.panels); + }, + error: () => { + this.logger.error('Cannot load panel data'); + }, + }); + }, + error: () => { + this.logger.error('Cannot load dashboard'); + }, + }); + } +} diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/index.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/index.ts index eaa1573..7094a4f 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/index.ts +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/index.ts @@ -1,2 +1,3 @@ +export { REFRESH_RATE_OPTIONS } from './panels-grid.consts'; export { PanelsGridComponent } from './panels-grid.component'; export { RefreshRateOption } from './panels-grid.types'; diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel.service.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel.service.ts new file mode 100644 index 0000000..a10cf5c --- /dev/null +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { TimeSeries } from '../../../entities/dashboards/data.types'; +import { Panel } from '../../../entities'; + +@Injectable({ + providedIn: 'root', +}) +export class PanelService { + public data$: Observable; + public panel$: Observable; + + constructor() { + this.data$ = {} as any; + this.panel$ = {} as any; + } +} diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.html b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.html index e9c13c9..16fd726 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.html +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.html @@ -15,7 +15,7 @@ } @@ -24,13 +24,13 @@ icon="pi pi-save" label="Save" severity="secondary" - [loading]="isSaving" - (onClick)="saveLayout()" + [loading]="isBusy" + (onClick)="savePanelsLayout()" />
- @if (panels.length === 0) { + @if (!panels) { +

...

+ } @else if (panels.length === 0) {

You haven't created any panels yet!

} @else { ; - - protected _dashboardId?: string; - - @Input() public set dashboardId(id: string) { - this._dashboardId = id; - this.refreshDashboard(); - this.refreshDashboardVariables(); - this.refreshPanelTypes(); - } - - protected isSaving = false; - protected isBusy = false; - - protected refreshRateOptions: RefreshRateOption[] = [ - { title: 'off', seconds: null }, - { title: '10s', seconds: 10 }, - { title: '30s', seconds: 30 }, - { title: '1m', seconds: 60 }, - { title: '10m', seconds: 600 }, - { title: '1h', seconds: 3600 }, - ]; +export class PanelsGridComponent { + protected refreshRateOptions: RefreshRateOption[] = REFRESH_RATE_OPTIONS; protected refreshRateControl = new FormControl(null); protected refreshRateSubscription?: Subscription; - protected timeRangeControl = new FormControl({ - step: '00:30:00', - from: (() => { - const date = new Date(); - date.setMinutes(date.getMinutes() - 360); - return date; - })(), - to: new Date(), - }); - - protected variables: DashboardVariable[] = []; - - protected panels: Panel[] = []; + protected timeRangeControl = new FormControl(DEFAULT_TIME_RANGE); + protected dashboard: Dashboard | null = null; + protected variables: DashboardVariable[] | null = null; + protected panels: Panel[] | null = null; protected panelTypes: PanelType[] = []; + protected isBusy = false; - protected gridsterItems: GridsterItem[] = []; + protected gridsterItems: GridsterItem[] | null = null; protected gridsterOptions: GridsterConfig = { gridType: GridType.Fixed, displayGrid: DisplayGrid.None, @@ -126,101 +86,20 @@ export class PanelsGridComponent implements AfterViewInit { itemChangeCallback: this.handleGridsterItemChange.bind(this), }; - constructor( - private apiService: ApiService, - private loggerService: ToastLoggerService, - ) { + constructor(private dashboardService: DashboardService) { this.freezeGrid(); - this.refreshRateControl.valueChanges.subscribe((seconds) => - this.setRefreshRate(seconds), - ); + // this.refreshRateControl.valueChanges.subscribe((seconds) => + // this.setRefreshRate(seconds), + // ); this.timeRangeControl.valueChanges.subscribe((timeRange) => { - this.updateTimeRange(timeRange); + this.dashboardService.timeRange = timeRange!; }); - } - - ngAfterViewInit() { - this.panelsComponents.changes.subscribe((pcs) => - this.updateTimeRange(this.timeRangeControl.getRawValue()), - ); - } - - protected refreshPanelTypes() { - this.apiService.getApiV1PanelTypes().subscribe({ - next: (data) => { - this.panelTypes = data.map((d) => { - return { - id: d.id || 0, - type: d.name as VisualizationType, - }; - }); - }, - error: (e) => { - this.loggerService.error(e); - }, - }); - } - - protected refreshDashboard() { - if (!this._dashboardId) return; - - this.isBusy = true; - this.refreshRateControl.disable(); - this.apiService - .getApiV1DashboardFull(this._dashboardId) - .pipe( - finalize(() => { - this.isBusy = false; - this.refreshRateControl.enable(); - }), - ) - .subscribe({ - next: (data) => { - this.panels = - data.panels?.map((item) => { - return { - id: item.id || '', - title: item.title || '', - query: item.query || '', - visualizationType: item.panelType as VisualizationType, - layout: decodeLayout(item.layout), - }; - }) || []; - this.refreshGridsterItems(); - }, - error: (e) => { - this.loggerService.error(e); - }, - }); - } - - protected refreshDashboardVariables() { - if (!this._dashboardId) return; - - this.apiService.getApiV1VariablesDashboard(this._dashboardId).subscribe({ - next: (data) => { - this.variables = data.map((item) => { - return { - id: item.id || '', - placeholder: item.placeholder || '', - value: item.value || '', - }; - }); - }, - error: (e) => { - this.loggerService.error(e); - }, + this.dashboardService.panels$.subscribe((v) => { + this.panels = v; + this.refreshGridsterItems(); }); - } - - protected refreshDashboardsData() { - this.isBusy = true; - - this.panelsComponents.forEach((component) => { - component.refreshData(); - }); - - timer(500).subscribe(() => (this.isBusy = false)); + this.dashboardService.isBusy$.subscribe((v) => (this.isBusy = v)); + this.dashboardService.dashboard$.subscribe((v) => (this.dashboard = v)); } protected freezeGrid() { @@ -235,10 +114,23 @@ export class PanelsGridComponent implements AfterViewInit { this.gridsterOptions.api?.optionsChanged!(); } + protected refreshDashboardVariables() { + this.dashboardService.refreshDashboardVariables(); + } + + protected refreshDashboardPanels() { + this.dashboardService.refreshDashboardPanels(); + } + + protected savePanelsLayout() { + this.dashboardService.savePanelsLayout(); + } + protected refreshGridsterItems() { - this.gridsterItems = this.panels.map((panel) => { - return { ...panel.layout, panelId: panel.id }; - }); + this.gridsterItems = + this.panels?.map((panel) => { + return { ...panel.layout, panelId: panel.id }; + }) || null; } protected toggleMode(event: any) { @@ -249,56 +141,10 @@ export class PanelsGridComponent implements AfterViewInit { } } - protected setRefreshRate(seconds: number | null) { - this.refreshRateSubscription?.unsubscribe(); - if (seconds) { - this.refreshRateSubscription = interval(seconds * 1000).subscribe(() => { - this.refreshDashboardsData(); - }); - } - } - - protected saveLayout() { - this.isSaving = true; - - const observables = this.panelsComponents.map((pc) => pc.saveLayout()); - - forkJoin(observables) - .pipe( - finalize(() => { - this.isSaving = false; - }), - ) - .subscribe({ - next: () => { - this.loggerService.success('Saved layout'); - }, - error: () => { - this.loggerService.error('Unable to save layout'); - }, - }); - } - protected handleGridsterItemChange( item: GridsterItem, component: GridsterItemComponentInterface, ) { const panelId = item['panelId']; - this.panelsComponents - .find((pc) => pc.panelId == panelId) - ?.updateLayout({ - x: item.x, - y: item.y, - cols: item.cols, - rows: item.rows, - }); - } - - protected updateTimeRange(timeRange: TimeRange | null) { - if (timeRange) { - this.panelsComponents.forEach((pc) => { - pc.refreshTimeRange(timeRange); - }); - } } } diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.consts.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.consts.ts new file mode 100644 index 0000000..bffa8aa --- /dev/null +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.consts.ts @@ -0,0 +1,21 @@ +import { TimeRange } from '../../../entities/dashboards/etc.types'; +import { RefreshRateOption } from './panels-grid.types'; + +export const REFRESH_RATE_OPTIONS: RefreshRateOption[] = [ + { title: 'off', seconds: null }, + { title: '10s', seconds: 10 }, + { title: '30s', seconds: 30 }, + { title: '1m', seconds: 60 }, + { title: '10m', seconds: 600 }, + { title: '1h', seconds: 3600 }, +]; + +export const DEFAULT_TIME_RANGE: TimeRange = { + step: '00:30:00', + from: (() => { + const date = new Date(); + date.setMinutes(date.getMinutes() - 360); + return date; + })(), + to: new Date(), +}; diff --git a/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.html b/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.html index 3b02baa..9825b23 100644 --- a/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.html +++ b/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.html @@ -3,12 +3,15 @@

{{ dashboard?.name }}

- +
- + diff --git a/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.ts b/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.ts index 5f2daec..c1b4728 100644 --- a/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.ts +++ b/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.ts @@ -10,6 +10,7 @@ import { Dashboard } from '../../entities'; import { ApiService } from '../../shared/services/datacat-generated-client'; import { ToastLoggerService } from '../../shared/services/toast-logger.service'; import { TooltipModule } from 'primeng/tooltip'; +import { DashboardService } from '../../features/dashboards/panels-grid/dashboard.service'; @Component({ standalone: true, @@ -31,38 +32,21 @@ export class ViewDashboardComponent implements AfterContentInit { @ViewChild(EditDashboardButtonComponent) editDashboardButtonComponent?: EditDashboardButtonComponent; - protected dashboard?: Dashboard; + protected dashboard: Dashboard | null = null; constructor( - private apiService: ApiService, - private loggerService: ToastLoggerService, + private dashboardService: DashboardService, private router: Router, - ) {} + ) { + this.dashboardService.dashboard$.subscribe((v) => (this.dashboard = v)); + } ngAfterContentInit() { - this.refresh(); + this.dashboardService.dashboardId = this.dashboardId; } - protected refresh() { - this.apiService.getApiV1Dashboard(this.dashboardId).subscribe({ - next: (data) => { - this.dashboard = { - id: data.id!, - name: data.name!, - description: data.description!, - panels: [], - createdAt: data.createdAt!, - lastUpdatedAt: data.updatedAt!, - }; - this.editDashboardButtonComponent!.fillForm( - this.dashboard.name, - this.dashboard.description, - ); - }, - error: (e) => { - this.loggerService.error(e); - }, - }); + protected refreshDashboardOnly() { + this.dashboardService.refreshDashboardOnly(); } protected showDashboardsList() { From 4ddfea051f92c04a2072f0395f72ff341fb1a1bb Mon Sep 17 00:00:00 2001 From: Nikita Nazarov Date: Sun, 25 May 2025 02:10:51 +0300 Subject: [PATCH 08/11] fix(datacat-ui): trying to make graphs load on startup --- frontend/datacat-ui/package-lock.json | 10 + frontend/datacat-ui/package.json | 1 + .../src/entities/dashboards/data.types.ts | 6 +- .../src/entities/dashboards/mappings.ts | 10 +- .../panels-grid/dashboard.service.ts | 19 ++ .../panels-grid/panel-data.service.ts | 74 +++++++ .../panel-in-grid.component.html | 16 +- .../panel-in-grid/panel-in-grid.component.ts | 180 +++--------------- .../dashboards/panels-grid/panel.service.ts | 17 -- .../panels-grid/panels-grid.component.html | 2 +- .../panels-grid/panels-grid.component.ts | 24 ++- .../panel-visualization.component.ts | 11 +- 12 files changed, 175 insertions(+), 195 deletions(-) create mode 100644 frontend/datacat-ui/src/features/dashboards/panels-grid/panel-data.service.ts delete mode 100644 frontend/datacat-ui/src/features/dashboards/panels-grid/panel.service.ts diff --git a/frontend/datacat-ui/package-lock.json b/frontend/datacat-ui/package-lock.json index fe8a12d..00789f9 100644 --- a/frontend/datacat-ui/package-lock.json +++ b/frontend/datacat-ui/package-lock.json @@ -24,6 +24,7 @@ "primeng": "^18.0.2", "rxjs": "~7.8.0", "tslib": "^2.3.0", + "zod": "^3.25.28", "zone.js": "~0.14.3" }, "devDependencies": { @@ -16726,6 +16727,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.25.28", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.28.tgz", + "integrity": "sha512-/nt/67WYKnr5by3YS7LroZJbtcCBurDKKPBPWWzaxvVCGuG/NOsiKkrjoOhI8mJ+SQUXEbUzeB3S+6XDUEEj7Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zone.js": { "version": "0.14.10", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz", diff --git a/frontend/datacat-ui/package.json b/frontend/datacat-ui/package.json index b4bbb2c..4415a22 100644 --- a/frontend/datacat-ui/package.json +++ b/frontend/datacat-ui/package.json @@ -27,6 +27,7 @@ "primeng": "^18.0.2", "rxjs": "~7.8.0", "tslib": "^2.3.0", + "zod": "^3.25.28", "zone.js": "~0.14.3" }, "devDependencies": { diff --git a/frontend/datacat-ui/src/entities/dashboards/data.types.ts b/frontend/datacat-ui/src/entities/dashboards/data.types.ts index ef2e3de..f050441 100644 --- a/frontend/datacat-ui/src/entities/dashboards/data.types.ts +++ b/frontend/datacat-ui/src/entities/dashboards/data.types.ts @@ -1,10 +1,10 @@ export type DataPoint = { value: number; - timestamp: string; + timestamp: Date; }; export type TimeSeries = { metric?: string; - labels?: { [key: string]: string; }; + labels?: { [key: string]: string }; dataPoints: DataPoint[]; -} +}; diff --git a/frontend/datacat-ui/src/entities/dashboards/mappings.ts b/frontend/datacat-ui/src/entities/dashboards/mappings.ts index 6750e2e..dedcf3e 100644 --- a/frontend/datacat-ui/src/entities/dashboards/mappings.ts +++ b/frontend/datacat-ui/src/entities/dashboards/mappings.ts @@ -11,10 +11,18 @@ import { VisualizationType, } from './panel.types'; import { DashboardVariable } from './variables.types'; +import { z } from 'zod/v4'; + +const LayoutShema = z.object({ + x: z.number(), + y: z.number(), + cols: z.number(), + rows: z.number(), +}); const parseLayout = (s: string): Layout => { try { - return JSON.parse(s); + return LayoutShema.parse(JSON.parse(s)); } catch { return { x: 0, diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/dashboard.service.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/dashboard.service.ts index 7ff2496..88aff86 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/dashboard.service.ts +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/dashboard.service.ts @@ -10,6 +10,7 @@ import { Dashboard, DashboardVariable, decodeLayout, + Layout, mapGetPanelResponeToPanel, mapVariableResponseToDashboardVariable, Panel, @@ -40,6 +41,7 @@ export class DashboardService { null, ); + private _timeRange: TimeRange | null = null; private panels?: Panel[]; private dashboard?: Dashboard; @@ -61,9 +63,14 @@ export class DashboardService { } public set timeRange(tr: TimeRange) { + this._timeRange = tr; this.timeRangeSubject.next(tr); } + public get timeRange(): TimeRange | null { + return this._timeRange; + } + public savePanelsLayout() { this.isBusySubject.next(true); const requests = this.panels!.map((p) => { @@ -178,4 +185,16 @@ export class DashboardService { }, }); } + + public updatePanelLayout(panelId: string, layout: Layout) { + this.panels = this.panels?.map((p) => { + if (p.id == panelId) { + return { + ...p, + layout, + }; + } + return p; + }); + } } diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-data.service.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-data.service.ts new file mode 100644 index 0000000..b78e16a --- /dev/null +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-data.service.ts @@ -0,0 +1,74 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable, Subject } from 'rxjs'; +import { DataPoint, TimeSeries } from '../../../entities/dashboards/data.types'; +import { Panel } from '../../../entities'; +import { TimeRange } from '../../../entities/dashboards/etc.types'; +import { ApiService } from '../../../shared/services/datacat-generated-client'; + +@Injectable({ + providedIn: 'root', +}) +export class PanelDataService { + public data$: Observable; + public error$: Observable; + + private dataSubject = new BehaviorSubject(null); + private errorSubject = new BehaviorSubject(false); + + private data?: TimeSeries[]; + private _panel?: Panel; + + public set panel(p: Panel | undefined) { + this._panel = p; + } + + constructor(private api: ApiService) { + this.data$ = this.dataSubject.asObservable(); + this.error$ = this.errorSubject.asObservable(); + } + + public loadTimeRange(tr: TimeRange): void { + if (!this._panel) return; + + console.log(tr, this._panel.query, this._panel.dataSource!.name); + + this.errorSubject.next(false); + this.api + .getApiV1MetricsQueryRange( + this._panel.dataSource!.name, + this._panel.query, + 'undefined' as any, + null, + tr.from, + tr.to, + tr.step, + ) + .subscribe({ + next: (data) => { + console.log(data); + if (data.length !== 0) { + this.data = + data.map((ts) => { + return { + metric: ts.metricName, + labels: ts.labels, + dataPoints: + ts.points?.map((p) => { + return { + value: p.value!, + timestamp: p.timestamp!, + }; + }) || [], + }; + }) || []; + } else { + this.data = []; + } + this.dataSubject.next(this.data); + }, + error: () => { + this.errorSubject.next(true); + }, + }); + } +} diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.html b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.html index 076724a..71aeae9 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.html +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.html @@ -1,7 +1,7 @@
-

{{ panel?.title || 'Unnamed panel' }}

+

{{ _panel?.title || 'Unnamed panel' }}

- @if (isRefreshError) { + @if (isError) {

Loading error

} @else { }
@@ -37,18 +37,18 @@ [modal]="true" [(visible)]="isDialogShown" appendTo="body" - [header]="panel?.title" + [header]="_panel?.title" >

Query

- +
diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts index 9456bb7..41ac6a5 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts @@ -1,31 +1,17 @@ import { Component, Input } from '@angular/core'; import { PanelModule } from 'primeng/panel'; import { PanelVisualizationComponent } from '../../../../shared/ui/panel-visualization/panel-visualization.component'; -import { - DataSourceDriver, - decodeLayout, - decodeVisualizationSettings, - decodeVisualizationType, - encodeLayout, - encodeVisualizationSettings, - Layout, - Panel, -} from '../../../../entities'; -import { ApiService } from '../../../../shared/services/datacat-generated-client'; -import { ToastLoggerService } from '../../../../shared/services/toast-logger.service'; +import { Panel } from '../../../../entities'; import { ButtonModule } from 'primeng/button'; import { Router } from '@angular/router'; import * as urls from '../../../../shared/common/urls'; -import { - DataPoint, - TimeSeries, -} from '../../../../entities/dashboards/data.types'; -import { DatePipe } from '@angular/common'; -import { Observable, of, timer } from 'rxjs'; -import { TimeRange } from '../../../../shared/ui/time-range-select'; +import { TimeSeries } from '../../../../entities/dashboards/data.types'; import { DialogModule } from 'primeng/dialog'; import { TextareaModule } from 'primeng/textarea'; import { DividerModule } from 'primeng/divider'; +import { PanelDataService } from '../panel-data.service'; +import { DashboardService } from '../dashboard.service'; +import { ThemeProvider } from 'primeng/config'; @Component({ standalone: true, @@ -42,152 +28,36 @@ import { DividerModule } from 'primeng/divider'; ], }) export class PanelInGridComponent { - private _panelId?: string; - - @Input() set panelId(id: string | undefined) { - this._panelId = id; - this.refresh(); - } - - get panelId() { - return this._panelId; + @Input() set panel(p: Panel | undefined) { + this._panel = p; + this.panelDataService.panel = p; + console.log('wtf'); + if (this.dashboardService.timeRange) { + console.log('ww'); + this.panelDataService.loadTimeRange(this.dashboardService.timeRange); + } } - protected data: TimeSeries[] = []; - - protected panel?: Panel; - - protected isRefreshError = false; - + protected _panel?: Panel; + protected isError: boolean = false; + protected data: TimeSeries[] | null = null; protected isDialogShown = false; - protected timeRange?: TimeRange; - constructor( private router: Router, - private apiService: ApiService, - private loggerService: ToastLoggerService, - ) {} - - protected refresh() { - if (!this._panelId) return; - - this.apiService.getApiV1Panel(this._panelId).subscribe({ - next: (data) => { - this.isRefreshError = false; - this.panel = { - id: data.id || '', - title: data.title || '', - query: data.query?.query || '', - dataSource: { - id: data.query?.dataSource?.id || '', - name: data.query?.dataSource?.name || '', - driver: data.query?.dataSource?.type as DataSourceDriver, - connectionUrl: data.query?.dataSource?.connectionString || '', - }, - layout: decodeLayout(data.layout), - visualizationType: decodeVisualizationType(data.typeName), - visualizationSettings: decodeVisualizationSettings( - data.styleConfiguration, - ), - }; - this.refreshTimeRange(this.timeRange); - }, - error: (e) => { - this.loggerService.error(e); - this.isRefreshError = true; - }, + private panelDataService: PanelDataService, + private dashboardService: DashboardService, + ) { + this.panelDataService.data$.subscribe((v) => (this.data = v)); + this.panelDataService.error$.subscribe((v) => (this.isError = v)); + this.dashboardService.timeRange$.subscribe((tr) => { + if (tr) this.panelDataService.loadTimeRange(tr); }); } protected editPanel() { - if (this._panelId) { - this.router.navigateByUrl(urls.panelEditUrl(this._panelId)); - } - } - - public refreshData() { - // const datepipe = new DatePipe('en-US'); - // this.data = [ - // ...this.data, - // { - // value: Math.random() * 10, - // timestamp: datepipe.transform(Date.now(), 'dd.MM HH:mm:ss') || '', - // }, - // ]; - } - - public refreshTimeRange(timeRange: TimeRange | undefined) { - if (timeRange) { - this.timeRange = timeRange; - this.loadTimeRangeData(timeRange); - } - } - - protected loadTimeRangeData(timeRange: TimeRange) { - if (this.panel && this.panel.dataSource) { - this.isRefreshError = false; - this.apiService - .getApiV1MetricsQueryRange( - this.panel.dataSource.name, - this.panel.query, - 'undefined' as any, - null, - timeRange.from, - timeRange.to, - timeRange.step, - ) - .subscribe({ - next: (data) => { - if (data.length !== 0) { - const datepipe = new DatePipe('en-US', undefined, { - dateFormat: 'M/d/yy, h:mm a', - }); - this.data = - data.map((ts) => { - return { - metric: ts.metricName, - labels: ts.labels, - dataPoints: - ts.points?.map((p) => { - return { - value: p.value!, - timestamp: datepipe.transform(p.timestamp!) || '', - }; - }) || [], - }; - }) || []; - } else { - this.data = []; - } - }, - error: (e) => { - this.isRefreshError = true; - }, - }); - } - } - - public saveLayout(): Observable { - if (this.panel) { - const request = { - title: this.panel.title, - type: 1, // this.panel.visualizationType, - rawQuery: this.panel.query, - dataSourceId: this.panel.dataSource?.id, - layout: encodeLayout(this.panel.layout), - styleConfiguration: encodeVisualizationSettings( - this.panel.visualizationSettings, - ), - } as any; - return this.apiService.putApiV1PanelUpdate(this.panel.id, request); - } - return of(undefined); - } - - public updateLayout(layout: Layout) { - if (this.panel) { - this.panel.layout = layout; + if (this._panel?.id) { + this.router.navigateByUrl(urls.panelEditUrl(this._panel?.id)); } } diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel.service.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel.service.ts deleted file mode 100644 index a10cf5c..0000000 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel.service.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; -import { TimeSeries } from '../../../entities/dashboards/data.types'; -import { Panel } from '../../../entities'; - -@Injectable({ - providedIn: 'root', -}) -export class PanelService { - public data$: Observable; - public panel$: Observable; - - constructor() { - this.data$ = {} as any; - this.panel$ = {} as any; - } -} diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.html b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.html index 16fd726..a0ce7a6 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.html +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.html @@ -71,7 +71,7 @@ > @for (item of gridsterItems; track $index) { - + } diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts index 986f039..24b227f 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts @@ -126,13 +126,6 @@ export class PanelsGridComponent { this.dashboardService.savePanelsLayout(); } - protected refreshGridsterItems() { - this.gridsterItems = - this.panels?.map((panel) => { - return { ...panel.layout, panelId: panel.id }; - }) || null; - } - protected toggleMode(event: any) { if (event.checked) { this.unfreezeGrid(); @@ -141,10 +134,27 @@ export class PanelsGridComponent { } } + protected refreshGridsterItems() { + this.gridsterItems = + this.panels?.map((panel) => { + return { ...panel.layout, panelId: panel.id }; + }) || null; + } + protected handleGridsterItemChange( item: GridsterItem, component: GridsterItemComponentInterface, ) { const panelId = item['panelId']; + this.dashboardService.updatePanelLayout(panelId, { + x: item.x, + y: item.y, + cols: item.cols, + rows: item.rows, + }); + } + + protected getPanelById(id: string): Panel | undefined { + return this.panels?.find((p) => p.id == id); } } diff --git a/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts b/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts index b9af94d..2d853cf 100644 --- a/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts +++ b/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts @@ -3,7 +3,7 @@ import { VisualizationSettings, VisualizationType } from '../../../entities'; import { ChartModule } from 'primeng/chart'; import { BASIC_OPTIONS } from './consts'; import { TimeSeries } from '../../../entities/dashboards/data.types'; -import { CommonModule } from '@angular/common'; +import { CommonModule, DatePipe } from '@angular/common'; @Component({ standalone: true, @@ -33,7 +33,7 @@ export class PanelVisualizationComponent { @Input() public visualizationType?: VisualizationType; - @Input() public set data(data: TimeSeries[]) { + @Input() public set data(data: TimeSeries[] | null) { if (data) { this.parseDataIntoChartjsData(data); } @@ -60,8 +60,13 @@ export class PanelVisualizationComponent { } protected parseDataIntoChartjsData(data: TimeSeries[]) { + const datePipe = new DatePipe('en-US', undefined, { + dateFormat: 'M/d/yy, h:mm a', + }); + this.chartjsData = { - labels: data[0]?.dataPoints.map((d) => d.timestamp) || [], + labels: + data[0]?.dataPoints.map((d) => datePipe.transform(d.timestamp)) || [], datasets: data.map((ts) => { return { label: ts.metric, From ba02cbd6055fdb0d3bfb18ad2a31f091b6452855 Mon Sep 17 00:00:00 2001 From: Nikita Nazarov Date: Sun, 25 May 2025 12:52:19 +0300 Subject: [PATCH 09/11] fix(datacat-ui): various fixes --- .../delete-variable-button.component.ts | 1 - .../edit-panel/edit-panel.component.html | 1 + .../edit-panel/edit-panel.component.ts | 70 +++++++++++++++---- .../panels-grid/dashboard.service.ts | 49 ++++++++----- .../panels-grid/panel-data.service.ts | 3 - .../panel-in-grid/panel-in-grid.component.ts | 4 +- .../panels-grid/panels-grid.component.ts | 2 + .../view-dashboard.component.ts | 1 + .../ui/panel-visualization-options/forms.ts | 8 +-- .../panel-visualization.component.ts | 2 +- 10 files changed, 96 insertions(+), 45 deletions(-) diff --git a/frontend/datacat-ui/src/features/dashboards/delete-variable/delete-variable-button.component.ts b/frontend/datacat-ui/src/features/dashboards/delete-variable/delete-variable-button.component.ts index b77be16..32f01d2 100644 --- a/frontend/datacat-ui/src/features/dashboards/delete-variable/delete-variable-button.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/delete-variable/delete-variable-button.component.ts @@ -47,7 +47,6 @@ export class DeleteVariableButtonComponent { ) .subscribe({ next: () => { - this.loggerService.success('Deleted variable'); this.onDelete.emit(); this.hideDeletionDialog(); }, diff --git a/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.html b/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.html index 4e99266..dfda1a3 100644 --- a/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.html +++ b/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.html @@ -10,6 +10,7 @@
+

Title

diff --git a/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.ts b/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.ts index 48c40f6..da093b7 100644 --- a/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/edit-panel/edit-panel.component.ts @@ -18,6 +18,7 @@ import { encodeVisualizationSettings, encodeVisualizationType, Panel, + serializeLayout, VisualizationSettings, VisualizationType, } from '../../../entities'; @@ -26,6 +27,9 @@ import { ToastLoggerService } from '../../../shared/services/toast-logger.servic import { ButtonModule } from 'primeng/button'; import { finalize } from 'rxjs'; import { TimeSeries } from '../../../entities/dashboards/data.types'; +import { PanelDataService } from '../panels-grid/panel-data.service'; +import { TimeRangeSelectComponent } from '../../../shared/ui/time-range-select/time-range-select.component'; +import { TimeRange } from '../../../entities/dashboards/etc.types'; @Component({ standalone: true, @@ -41,7 +45,9 @@ import { TimeSeries } from '../../../entities/dashboards/data.types'; TextareaModule, DataSourceSelectComponent, ButtonModule, + TimeRangeSelectComponent, ], + providers: [PanelDataService], }) export class EditPanelComponent implements AfterViewInit { private _panelId?: string; @@ -56,10 +62,20 @@ export class EditPanelComponent implements AfterViewInit { protected panel?: Panel; - protected data: TimeSeries[] = []; + protected data: TimeSeries[] | null = null; protected visualizationType?: VisualizationType; protected visualizationSettings?: VisualizationSettings; + protected timeRangeControl = new FormControl({ + step: '00:30:00', + from: (() => { + const date = new Date(); + date.setMinutes(date.getMinutes() - 360); + return date; + })(), + to: new Date(), + }); + protected editForm = new FormGroup({ title: new FormControl('', Validators.required), dataSourceId: new FormControl( @@ -70,9 +86,29 @@ export class EditPanelComponent implements AfterViewInit { }); constructor( - private apiService: ApiService, - private loggerService: ToastLoggerService, - ) {} + private api: ApiService, + private logger: ToastLoggerService, + private panelDataService: PanelDataService, + ) { + this.panelDataService.data$.subscribe((v) => (this.data = v)); + this.timeRangeControl.valueChanges.subscribe((tr) => { + if (tr) this.panelDataService.loadTimeRange(tr); + }); + this.editForm.get('dataSourceId')?.valueChanges.subscribe((id) => { + if (id && this.panel) { + this.panel.dataSource!.id = id; + this.panelDataService.panel = this.panel; + this.refreshPreview(); + } + }); + this.editForm.get('query')?.valueChanges.subscribe((q) => { + if (q && this.panel) { + this.panel.query = q; + this.panelDataService.panel = this.panel; + this.refreshPreview(); + } + }); + } ngAfterViewInit() { if (this.panel) { @@ -86,7 +122,7 @@ export class EditPanelComponent implements AfterViewInit { protected refresh() { if (!this._panelId) return; - this.apiService.getApiV1Panel(this._panelId).subscribe({ + this.api.getApiV1Panel(this._panelId).subscribe({ next: (data) => { this.panel = { id: data.id || '', @@ -104,6 +140,8 @@ export class EditPanelComponent implements AfterViewInit { data.styleConfiguration!, ) as VisualizationSettings, }; + this.panelDataService.panel = this.panel; + this.refreshPreview(); this.optionsComponent?.setVisualizationSettings( this.panel.visualizationType!, @@ -116,36 +154,40 @@ export class EditPanelComponent implements AfterViewInit { query: this.panel.query, }); }, - error: (e) => { - this.loggerService.error(e); + error: () => { + this.logger.error('Cannot load panel data'); }, }); } + protected refreshPreview() { + this.panelDataService.loadTimeRange(this.timeRangeControl.getRawValue()!); + } + protected saveChanges() { - if (!this._panelId) return; + if (!this.panel) return; const request: any = { title: this.editForm.get('title')?.value || '', type: encodeVisualizationType(this.visualizationType), rawQuery: this.editForm.get('query')?.value || '', dataSourceId: this.editForm.get('dataSourceId')?.value || '', - // layout: '', + layout: serializeLayout(this.panel.layout), styleConfiguration: encodeVisualizationSettings( this.visualizationSettings, ), }; this.editForm.disable(); - this.apiService - .putApiV1PanelUpdate(this._panelId, request) + this.api + .putApiV1PanelUpdate(this.panel.id, request) .pipe(finalize(() => this.editForm.enable())) .subscribe({ next: () => { - this.loggerService.success('Saved'); + this.logger.success('Saved'); }, - error: (e) => { - this.loggerService.error(e); + error: () => { + this.logger.error('Cannot save'); }, }); } diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/dashboard.service.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/dashboard.service.ts index 88aff86..b6c4082 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/dashboard.service.ts +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/dashboard.service.ts @@ -1,11 +1,5 @@ import { Injectable } from '@angular/core'; -import { - BehaviorSubject, - finalize, - forkJoin, - Observable, - ObservableLike, -} from 'rxjs'; +import { BehaviorSubject, filter, finalize, forkJoin, Observable } from 'rxjs'; import { Dashboard, DashboardVariable, @@ -28,7 +22,7 @@ import { ToastLoggerService } from '../../../shared/services/toast-logger.servic export class DashboardService { public dashboard$: Observable; public timeRange$: Observable; - public refreshCall$: Observable; + // public refresh$: Observable; public panels$: Observable; public isBusy$: Observable; public variables$: Observable; @@ -40,6 +34,7 @@ export class DashboardService { private variablesSubject = new BehaviorSubject( null, ); + private refreshSubject = new BehaviorSubject(null); private _timeRange: TimeRange | null = null; private panels?: Panel[]; @@ -51,7 +46,9 @@ export class DashboardService { ) { this.dashboard$ = this.dashboardSubject.asObservable(); this.timeRange$ = this.timeRangeSubject.asObservable(); - this.refreshCall$ = {} as any; + // this.refresh$ = this.refreshSubject + // .asObservable() + // .pipe(filter((v) => v is Date)); this.panels$ = this.panelsSubject.asObservable(); this.isBusy$ = this.isBusySubject.asObservable(); this.variables$ = this.variablesSubject.asObservable(); @@ -60,6 +57,7 @@ export class DashboardService { public set dashboardId(id: string) { if (this.dashboard?.id === id) return; this.refreshDashboardPanelsById(id); + this.refreshDashboardVariablesById(id); } public set timeRange(tr: TimeRange) { @@ -98,8 +96,11 @@ export class DashboardService { public refreshDashboardVariables() { if (!this.dashboard) return; + this.refreshDashboardVariablesById(this.dashboard.id); + } - this.api.getApiV1VariablesDashboard(this.dashboard.id).subscribe({ + public refreshDashboardVariablesById(id: string): void { + this.api.getApiV1VariablesDashboard(id).subscribe({ next: (data) => { const variables = data.map(mapVariableResponseToDashboardVariable); this.variablesSubject.next(variables); @@ -170,15 +171,21 @@ export class DashboardService { this.dashboardSubject.next(this.dashboard!); const requests = panels.map((p) => this.api.getApiV1Panel(p.id)); - forkJoin(requests).subscribe({ - next: (data) => { - this.panels = data.map(mapGetPanelResponeToPanel); - this.panelsSubject.next(this.panels); - }, - error: () => { - this.logger.error('Cannot load panel data'); - }, - }); + + if (requests.length == 0) { + this.panels = []; + this.panelsSubject.next([]); + } else { + forkJoin(requests).subscribe({ + next: (data) => { + this.panels = data.map(mapGetPanelResponeToPanel); + this.panelsSubject.next(this.panels); + }, + error: () => { + this.logger.error('Cannot load panel data'); + }, + }); + } }, error: () => { this.logger.error('Cannot load dashboard'); @@ -197,4 +204,8 @@ export class DashboardService { return p; }); } + + public requestRefresh(date: Date) { + this.refreshSubject.next(date); + } } diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-data.service.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-data.service.ts index b78e16a..12cb4cb 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-data.service.ts +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-data.service.ts @@ -30,8 +30,6 @@ export class PanelDataService { public loadTimeRange(tr: TimeRange): void { if (!this._panel) return; - console.log(tr, this._panel.query, this._panel.dataSource!.name); - this.errorSubject.next(false); this.api .getApiV1MetricsQueryRange( @@ -45,7 +43,6 @@ export class PanelDataService { ) .subscribe({ next: (data) => { - console.log(data); if (data.length !== 0) { this.data = data.map((ts) => { diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts index 41ac6a5..ed022cd 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panel-in-grid/panel-in-grid.component.ts @@ -11,7 +11,6 @@ import { TextareaModule } from 'primeng/textarea'; import { DividerModule } from 'primeng/divider'; import { PanelDataService } from '../panel-data.service'; import { DashboardService } from '../dashboard.service'; -import { ThemeProvider } from 'primeng/config'; @Component({ standalone: true, @@ -26,14 +25,13 @@ import { ThemeProvider } from 'primeng/config'; TextareaModule, DividerModule, ], + providers: [PanelDataService], }) export class PanelInGridComponent { @Input() set panel(p: Panel | undefined) { this._panel = p; this.panelDataService.panel = p; - console.log('wtf'); if (this.dashboardService.timeRange) { - console.log('ww'); this.panelDataService.loadTimeRange(this.dashboardService.timeRange); } } diff --git a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts index 24b227f..d6bdf8e 100644 --- a/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts +++ b/frontend/datacat-ui/src/features/dashboards/panels-grid/panels-grid.component.ts @@ -100,6 +100,8 @@ export class PanelsGridComponent { }); this.dashboardService.isBusy$.subscribe((v) => (this.isBusy = v)); this.dashboardService.dashboard$.subscribe((v) => (this.dashboard = v)); + this.dashboardService.variables$.subscribe((v) => (this.variables = v)); + this.dashboardService.timeRange = this.timeRangeControl.getRawValue()!; } protected freezeGrid() { diff --git a/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.ts b/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.ts index c1b4728..da03d9b 100644 --- a/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.ts +++ b/frontend/datacat-ui/src/processes/view-dashboard/view-dashboard.component.ts @@ -25,6 +25,7 @@ import { DashboardService } from '../../features/dashboards/panels-grid/dashboar EditDashboardButtonComponent, TooltipModule, ], + providers: [DashboardService], }) export class ViewDashboardComponent implements AfterContentInit { @Input() protected dashboardId: string = ''; diff --git a/frontend/datacat-ui/src/shared/ui/panel-visualization-options/forms.ts b/frontend/datacat-ui/src/shared/ui/panel-visualization-options/forms.ts index 60dad57..fecfc19 100644 --- a/frontend/datacat-ui/src/shared/ui/panel-visualization-options/forms.ts +++ b/frontend/datacat-ui/src/shared/ui/panel-visualization-options/forms.ts @@ -6,16 +6,16 @@ export const createOptionsForm = ( ): FormGroup => { return new FormGroup({ legend: new FormGroup({ - enabled: new FormControl(false), + enabled: new FormControl(true), position: new FormControl('top'), }), title: new FormGroup({ - enabled: new FormControl(true), - text: new FormControl('a'), + enabled: new FormControl(false), + text: new FormControl(''), }), tooltip: new FormGroup({ enabled: new FormControl(true), - }) + }), }); // switch (type) { diff --git a/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts b/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts index 2d853cf..858e81e 100644 --- a/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts +++ b/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts @@ -69,7 +69,7 @@ export class PanelVisualizationComponent { data[0]?.dataPoints.map((d) => datePipe.transform(d.timestamp)) || [], datasets: data.map((ts) => { return { - label: ts.metric, + label: ts.metric + JSON.stringify(ts.labels), data: ts.dataPoints.map((d) => d.value), }; }), From 328d4db5fbdb54cd7336995c4dc5672f2a32d616 Mon Sep 17 00:00:00 2001 From: Nikita Nazarov Date: Sun, 25 May 2025 13:16:34 +0300 Subject: [PATCH 10/11] fix(datacat-ui): fix pie chart --- .../panel-visualization.component.ts | 35 ++++++++++++++----- .../time-range-select.component.html | 4 +++ .../time-range-select.component.ts | 3 +- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts b/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts index 858e81e..afe7bc3 100644 --- a/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts +++ b/frontend/datacat-ui/src/shared/ui/panel-visualization/panel-visualization.component.ts @@ -64,16 +64,33 @@ export class PanelVisualizationComponent { dateFormat: 'M/d/yy, h:mm a', }); - this.chartjsData = { - labels: - data[0]?.dataPoints.map((d) => datePipe.transform(d.timestamp)) || [], - datasets: data.map((ts) => { - return { - label: ts.metric + JSON.stringify(ts.labels), - data: ts.dataPoints.map((d) => d.value), + switch (this.visualizationType) { + case VisualizationType.PIE: { + this.chartjsData = { + labels: data?.map((ts) => JSON.stringify(ts.labels)) || [], + datasets: [ + { + label: null, + data: data?.map((ts) => ts.dataPoints[0].value) || [], + }, + ], }; - }), - }; + break; + } + default: { + this.chartjsData = { + labels: + data[0]?.dataPoints.map((d) => datePipe.transform(d.timestamp)) || + [], + datasets: data.map((ts) => { + return { + label: ts.metric + JSON.stringify(ts.labels), + data: ts.dataPoints.map((d) => d.value), + }; + }), + }; + } + } this.chartRef?.chart?.update(); } diff --git a/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.html b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.html index 26ed68e..c3deb6d 100644 --- a/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.html +++ b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.html @@ -29,3 +29,7 @@ />
+ + + + \ No newline at end of file diff --git a/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.ts b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.ts index 8666261..7a3896e 100644 --- a/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.ts +++ b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.ts @@ -10,13 +10,14 @@ import { DatePickerModule } from 'primeng/datepicker'; import { TimeRange } from './time-range-select.types'; import { SelectModule } from 'primeng/select'; import { STEP_OPTIONS } from './time-range-select.consts'; +import { PopoverModule } from 'primeng/popover'; @Component({ standalone: true, selector: 'datacat-time-range-select', templateUrl: './time-range-select.component.html', styleUrl: './time-range-select.component.scss', - imports: [DatePickerModule, SelectModule, ReactiveFormsModule], + imports: [DatePickerModule, SelectModule, ReactiveFormsModule, PopoverModule], providers: [ { provide: NG_VALUE_ACCESSOR, From 6269ea9d0de6c93bbdd5769bfbfab14745438a04 Mon Sep 17 00:00:00 2001 From: Nikita Nazarov Date: Sun, 25 May 2025 13:29:11 +0300 Subject: [PATCH 11/11] fix(datcat-ui): prettify time range widget --- .../time-range-select.component.html | 74 ++++++++++--------- .../time-range-select.component.scss | 4 + .../time-range-select.component.ts | 23 +++++- 3 files changed, 66 insertions(+), 35 deletions(-) diff --git a/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.html b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.html index c3deb6d..c5230e6 100644 --- a/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.html +++ b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.html @@ -1,35 +1,41 @@ -
-
-

Step

- + + + {{ from | date: 'short' }} to + {{ to | date: 'short' }} with step + + {{ step }} + + + +
+
+

Step

+ +
+
+

From

+ +
+
+

To

+ +
-
-

From

- -
-
-

To

- -
-
- - - - \ No newline at end of file + diff --git a/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.scss b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.scss index 206e57e..b87a400 100644 --- a/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.scss +++ b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.scss @@ -13,3 +13,7 @@ color: var(--p-surface-400); } } + +.date { + color: var(--p-primary-100); +} diff --git a/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.ts b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.ts index 7a3896e..f2c3962 100644 --- a/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.ts +++ b/frontend/datacat-ui/src/shared/ui/time-range-select/time-range-select.component.ts @@ -11,13 +11,22 @@ import { TimeRange } from './time-range-select.types'; import { SelectModule } from 'primeng/select'; import { STEP_OPTIONS } from './time-range-select.consts'; import { PopoverModule } from 'primeng/popover'; +import { ButtonModule } from 'primeng/button'; +import { CommonModule } from '@angular/common'; @Component({ standalone: true, selector: 'datacat-time-range-select', templateUrl: './time-range-select.component.html', styleUrl: './time-range-select.component.scss', - imports: [DatePickerModule, SelectModule, ReactiveFormsModule, PopoverModule], + imports: [ + DatePickerModule, + SelectModule, + ReactiveFormsModule, + PopoverModule, + ButtonModule, + CommonModule, + ], providers: [ { provide: NG_VALUE_ACCESSOR, @@ -38,6 +47,18 @@ export class TimeRangeSelectComponent implements ControlValueAccessor { to: new FormControl(null), }); + public get step(): string { + return this.formGroup.get('step')?.value!; + } + + public get from(): Date { + return this.formGroup.get('from')?.value!; + } + + public get to(): Date { + return this.formGroup.get('to')?.value!; + } + constructor() { this.formGroup.valueChanges.subscribe(() => this.notifyTouchedAndChanged()); }