From fcff35949849e4fee7954db33d02f29176985aaa Mon Sep 17 00:00:00 2001 From: Nagy Szabolcs Date: Wed, 31 Jan 2024 12:22:46 +0200 Subject: [PATCH] feat(history): Added progress bar --- backend/src/south/south-connector.spec.ts | 3 +- backend/src/south/south-connector.ts | 93 +++++++++++-------- frontend/proxy.conf.mjs | 3 +- .../history-metrics.component.html | 31 +++++++ .../history-metrics.component.spec.ts | 23 ++++- .../history-metrics.component.ts | 24 ++++- .../progressbar/progressbar.component.html | 4 + .../progressbar/progressbar.component.scss | 10 ++ .../progressbar/progressbar.component.spec.ts | 22 +++++ .../progressbar/progressbar.component.ts | 16 ++++ .../history-query-detail.component.ts | 6 ++ frontend/src/i18n/en.json | 9 +- shared/model/engine.model.ts | 14 ++- shared/model/history-query.model.ts | 2 +- 14 files changed, 213 insertions(+), 47 deletions(-) create mode 100644 frontend/src/app/history-query/history-query-detail/history-metrics/progressbar/progressbar.component.html create mode 100644 frontend/src/app/history-query/history-query-detail/history-metrics/progressbar/progressbar.component.scss create mode 100644 frontend/src/app/history-query/history-query-detail/history-metrics/progressbar/progressbar.component.spec.ts create mode 100644 frontend/src/app/history-query/history-query-detail/history-metrics/progressbar/progressbar.component.ts diff --git a/backend/src/south/south-connector.spec.ts b/backend/src/south/south-connector.spec.ts index ef443a8ab4..375d0c2e6f 100644 --- a/backend/src/south/south-connector.spec.ts +++ b/backend/src/south/south-connector.spec.ts @@ -53,7 +53,8 @@ jest.mock( }, metrics: { numberOfValuesRetrieved: 1, - numberOfFilesRetrieved: 1 + numberOfFilesRetrieved: 1, + historyMetrics: {} } }; } diff --git a/backend/src/south/south-connector.ts b/backend/src/south/south-connector.ts index 1230cb08f7..ff1b9be375 100644 --- a/backend/src/south/south-connector.ts +++ b/backend/src/south/south-connector.ts @@ -262,10 +262,9 @@ export default class SouthConnector, currentIntervalIndex: number, startTime: Instant) { + // calculate progress based on time + const progress = + 1 - + (DateTime.fromISO(intervals[intervals.length - 1].end).toMillis() - + DateTime.fromISO(intervals[currentIntervalIndex].start).toMillis()) / + (DateTime.fromISO(intervals[intervals.length - 1].end).toMillis() - DateTime.fromISO(startTime).toMillis()); + + // round to 2 decimals + const roundedProgress = Math.round((progress + Number.EPSILON) * 100) / 100; + + // in the chance that the rounded progress is 0.99, but it's the last interval, we want to return 1 + if (currentIntervalIndex === intervals.length - 1) { + return 1; + } + + return roundedProgress; + } + /** * Add new values to the South connector buffer. */ diff --git a/frontend/proxy.conf.mjs b/frontend/proxy.conf.mjs index 3d2a63ee77..7ef8cd38e0 100644 --- a/frontend/proxy.conf.mjs +++ b/frontend/proxy.conf.mjs @@ -1,7 +1,8 @@ const PROXY_CONFIG = [ { // proxy all the requests starting with /api to the NodeJS backend - context: ['/api'], + // note: we need to add the /sse path for the server sent events + context: ['/api', '/sse'], target: 'http://localhost:2223', secure: false } diff --git a/frontend/src/app/history-query/history-query-detail/history-metrics/history-metrics.component.html b/frontend/src/app/history-query/history-query-detail/history-metrics/history-metrics.component.html index fde1431140..5995904a3e 100644 --- a/frontend/src/app/history-query/history-query-detail/history-metrics/history-metrics.component.html +++ b/frontend/src/app/history-query/history-query-detail/history-metrics/history-metrics.component.html @@ -14,6 +14,13 @@ + + + + + + + @@ -64,6 +71,30 @@ + + + + + + + + + + + + {{ historyMetrics.south.historyMetrics.currentIntervalNumber }} / {{ historyMetrics.south.historyMetrics.numberOfIntervals }} + + + + + + {{ historyMetrics.south.historyMetrics.currentIntervalStart }} + + + + + {{ historyMetrics.south.historyMetrics.currentIntervalEnd }} + diff --git a/frontend/src/app/history-query/history-query-detail/history-metrics/history-metrics.component.spec.ts b/frontend/src/app/history-query/history-query-detail/history-metrics/history-metrics.component.spec.ts index db0fb4532c..1f388d17b4 100644 --- a/frontend/src/app/history-query/history-query-detail/history-metrics/history-metrics.component.spec.ts +++ b/frontend/src/app/history-query/history-query-detail/history-metrics/history-metrics.component.spec.ts @@ -118,7 +118,28 @@ class TestComponent { retentionDuration: 0 } }; - historyMetrics: HistoryMetrics = { north: {}, south: {} } as HistoryMetrics; + historyMetrics: HistoryMetrics = { + north: { + numberOfValuesSent: 10, + numberOfFilesSent: 0, + lastValueSent: null, + lastFileSent: null, + cacheSize: 0, + metricsStart: '2023-01-01T00:00:00.000Z', + lastConnection: null, + lastRunStart: null, + lastRunDuration: null + }, + south: { + numberOfValuesRetrieved: 20, + numberOfFilesRetrieved: 0, + lastValueRetrieved: null, + lastFileRetrieved: null, + historyMetrics: { + intervalProgress: 1 + } + } + } as HistoryMetrics; } class HistoryMetricsComponentTester extends ComponentTester { diff --git a/frontend/src/app/history-query/history-query-detail/history-metrics/history-metrics.component.ts b/frontend/src/app/history-query/history-query-detail/history-metrics/history-metrics.component.ts index 7d2aac0971..d1c5fc38dc 100644 --- a/frontend/src/app/history-query/history-query-detail/history-metrics/history-metrics.component.ts +++ b/frontend/src/app/history-query/history-query-detail/history-metrics/history-metrics.component.ts @@ -8,12 +8,13 @@ import { BoxComponent, BoxTitleDirective } from '../../../shared/box/box.compone import { HistoryQueryDTO } from '../../../../../../shared/model/history-query.model'; import { NorthConnectorManifest } from '../../../../../../shared/model/north-connector.model'; import { SouthConnectorManifest } from '../../../../../../shared/model/south-connector.model'; +import { ProgressbarComponent } from './progressbar/progressbar.component'; @Component({ selector: 'oib-history-metrics', templateUrl: './history-metrics.component.html', styleUrl: './history-metrics.component.scss', - imports: [TranslateModule, NgIf, DatetimePipe, DurationPipe, BoxComponent, BoxTitleDirective, JsonPipe], + imports: [TranslateModule, NgIf, DatetimePipe, DurationPipe, BoxComponent, BoxTitleDirective, JsonPipe, ProgressbarComponent], standalone: true }) export class HistoryMetricsComponent { @@ -23,4 +24,25 @@ export class HistoryMetricsComponent { @Input({ required: true }) historyMetrics!: HistoryMetrics; constructor() {} + + get southProgressbarAnimated(): boolean { + if (this.historyQuery.status === 'RUNNING' && this.historyMetrics.south.historyMetrics.intervalProgress !== 1) { + return true; + } + return false; + } + + get northProgress() { + const valueProgress = this.historyMetrics.north.numberOfValuesSent / this.historyMetrics.south.numberOfValuesRetrieved; + const fileProgress = this.historyMetrics.north.numberOfFilesSent / this.historyMetrics.south.numberOfFilesRetrieved; + + return valueProgress > 0 ? valueProgress : fileProgress; + } + + get northProgressbarAnimated(): boolean { + if (this.historyQuery.status === 'RUNNING' && this.northProgress !== 1) { + return true; + } + return false; + } } diff --git a/frontend/src/app/history-query/history-query-detail/history-metrics/progressbar/progressbar.component.html b/frontend/src/app/history-query/history-query-detail/history-metrics/progressbar/progressbar.component.html new file mode 100644 index 0000000000..dd5ee10979 --- /dev/null +++ b/frontend/src/app/history-query/history-query-detail/history-metrics/progressbar/progressbar.component.html @@ -0,0 +1,4 @@ +
+ {{value | percent}} + +
diff --git a/frontend/src/app/history-query/history-query-detail/history-metrics/progressbar/progressbar.component.scss b/frontend/src/app/history-query/history-query-detail/history-metrics/progressbar/progressbar.component.scss new file mode 100644 index 0000000000..05819ae189 --- /dev/null +++ b/frontend/src/app/history-query/history-query-detail/history-metrics/progressbar/progressbar.component.scss @@ -0,0 +1,10 @@ +.oib-progressbar { + position: relative; +} + +.oib-progressbar span { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); +} diff --git a/frontend/src/app/history-query/history-query-detail/history-metrics/progressbar/progressbar.component.spec.ts b/frontend/src/app/history-query/history-query-detail/history-metrics/progressbar/progressbar.component.spec.ts new file mode 100644 index 0000000000..57981d4f05 --- /dev/null +++ b/frontend/src/app/history-query/history-query-detail/history-metrics/progressbar/progressbar.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProgressbarComponent } from './progressbar.component'; + +describe('ProgressbarComponent', () => { + let component: ProgressbarComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProgressbarComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(ProgressbarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/history-query/history-query-detail/history-metrics/progressbar/progressbar.component.ts b/frontend/src/app/history-query/history-query-detail/history-metrics/progressbar/progressbar.component.ts new file mode 100644 index 0000000000..09f15858f1 --- /dev/null +++ b/frontend/src/app/history-query/history-query-detail/history-metrics/progressbar/progressbar.component.ts @@ -0,0 +1,16 @@ +import { PercentPipe } from '@angular/common'; +import { Component, Input } from '@angular/core'; +import { NgbProgressbarModule } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'oib-progressbar', + standalone: true, + imports: [NgbProgressbarModule, PercentPipe], + templateUrl: './progressbar.component.html', + styleUrl: './progressbar.component.scss' +}) +export class ProgressbarComponent { + @Input({ required: true }) value!: number; + @Input() max: number = 1; + @Input({ required: true }) animated!: boolean; +} diff --git a/frontend/src/app/history-query/history-query-detail/history-query-detail.component.ts b/frontend/src/app/history-query/history-query-detail/history-query-detail.component.ts index df3fa36198..ac49fd54f4 100644 --- a/frontend/src/app/history-query/history-query-detail/history-query-detail.component.ts +++ b/frontend/src/app/history-query/history-query-detail/history-query-detail.component.ts @@ -159,6 +159,10 @@ export class HistoryQueryDetailComponent implements OnInit, OnDestroy { } connectToEventSource(): void { + if (this.historyStream) { + this.historyStream.close(); + } + const token = this.windowService.getStorageItem('oibus-token'); this.historyStream = new EventSource(`/sse/history-queries/${this.historyQuery!.id}?token=${token}`, { withCredentials: true }); this.historyStream.addEventListener('message', (event: MessageEvent) => { @@ -186,6 +190,7 @@ export class HistoryQueryDetailComponent implements OnInit, OnDestroy { .subscribe(updatedHistoryQuery => { this.historyQuery = updatedHistoryQuery; this.notificationService.success('history-query.started', { name: this.historyQuery!.name }); + this.connectToEventSource(); }); } else { this.historyQueryService @@ -199,6 +204,7 @@ export class HistoryQueryDetailComponent implements OnInit, OnDestroy { .subscribe(updatedHistoryQuery => { this.historyQuery = updatedHistoryQuery; this.notificationService.success('history-query.paused', { name: this.historyQuery!.name }); + this.historyStream?.close(); }); } } diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 356e113b53..2a91e6a0e0 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -722,7 +722,8 @@ "last-value": "Last value sent", "last-value-content": "{{ pointId }} at {{ timestamp }} with content {{ data }}", "last-run-start": "Last run", - "last-run-duration": "Last run duration" + "last-run-duration": "Last run duration", + "data-sent": "Data sent" }, "south": { "title": "South metrics", @@ -736,7 +737,11 @@ "last-value": "Last value retrieved", "last-value-content": "{{ pointId }} at {{ timestamp }} with content {{ data }}", "last-run-start": "Last run", - "last-run-duration": "Last run duration" + "last-run-duration": "Last run duration", + "data-retrieved": "Data retrieved", + "interval-start": "Interval start", + "interval-end": "Interval end", + "interval-progress": "Interval progress" } } }, diff --git a/shared/model/engine.model.ts b/shared/model/engine.model.ts index fb6536ad21..5ed1d8efd5 100644 --- a/shared/model/engine.model.ts +++ b/shared/model/engine.model.ts @@ -111,7 +111,19 @@ export interface NorthConnectorMetrics extends BaseConnectorMetrics { cacheSize: number; } -export interface SouthHistoryMetrics {} +export interface SouthHistoryMetrics { + running?: boolean; + // Percentage of the current interval that has been processed [0,1] + intervalProgress?: number; + // Start of the current interval + currentIntervalStart?: Instant; + // End of the current interval + currentIntervalEnd?: Instant; + // Number of the current interval + currentIntervalNumber?: number; + // Maximum number of intervals + numberOfIntervals?: number; +} export interface SouthConnectorMetrics extends BaseConnectorMetrics { numberOfValuesRetrieved: number; diff --git a/shared/model/history-query.model.ts b/shared/model/history-query.model.ts index 48059c9383..d7dc7451aa 100644 --- a/shared/model/history-query.model.ts +++ b/shared/model/history-query.model.ts @@ -2,7 +2,7 @@ import { NorthArchiveSettings, NorthCacheSettingsDTO } from './north-connector.m import { SouthConnectorHistorySettings, SouthConnectorItemDTO } from './south-connector.model'; import { BaseEntity } from './types'; -export const HISTORY_QUERY_STATUS = ['PENDING', 'RUNNING', 'PAUSED', 'FINISHED', 'ERRORED']; +export const HISTORY_QUERY_STATUS = ['PENDING', 'RUNNING', 'PAUSED', 'FINISHED', 'ERRORED'] as const; export type HistoryQueryStatus = (typeof HISTORY_QUERY_STATUS)[number]; /**