From: Aashish Sharma Date: Tue, 28 Jul 2026 07:00:59 +0000 (+0530) Subject: mgr/dashboard: Add alerts doc url to active alert list X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=03df721ac7876600b308f06c583979f99464e2ff;p=ceph.git mgr/dashboard: Add alerts doc url to active alert list Add documentation link in Learn more column for every alert in the active alerts list table Fixes: https://tracker-origin.ceph.com/issues/78747 Signed-off-by: Aashish Sharma --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.html index 1ea3e54a3746..1aa69e417215 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.html @@ -64,7 +64,6 @@ + + + @if (alertDocUrls[value]) { + Docs + } + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.spec.ts index 72fb3c883bbe..779db6b81236 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.spec.ts @@ -100,4 +100,33 @@ describe('ActiveAlertListComponent', () => { } }); }); + + describe('alertDocUrls', () => { + beforeEach(() => component.ngOnInit()); + + it('should be empty and hasDocUrls false by default (no upstream doc URL)', () => { + expect(component.hasDocUrls).toBe(false); + expect(component.alertDocUrls).toEqual({}); + }); + + it('should remain empty when alerts arrive and hasDocUrls is false', () => { + component['prometheusAlertService'].alerts = [ + { labels: { alertname: 'CephHealthError' } } as any + ]; + component['prometheusAlertService']['totalSubject'].next(1); + expect(component.alertDocUrls).toEqual({}); + }); + + it('should populate map when hasDocUrls is true and alerts arrive', () => { + spyOn(component['docService'], 'alertDocUrl').and.returnValue( + 'https://example.com/docs#managing-alerts__cephhealtherror' + ); + component.hasDocUrls = true; + component['prometheusAlertService'].alerts = [ + { labels: { alertname: 'CephHealthError' } } as any + ]; + component['prometheusAlertService']['totalSubject'].next(1); + expect(component.alertDocUrls['CephHealthError']).toContain('cephhealtherror'); + }); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.ts index c367ad9ba6a6..bea1f46d1d43 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.ts @@ -1,6 +1,9 @@ -import { Component, Inject, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { Component, Inject, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; +import { Subscription } from 'rxjs'; +import { filter, first } from 'rxjs/operators'; + import { PrometheusService } from '~/app/shared/api/prometheus.service'; import { CellTemplate } from '~/app/shared/enum/cell-template.enum'; import { Icons } from '~/app/shared/enum/icons.enum'; @@ -14,6 +17,7 @@ import { AlertState } from '~/app/shared/models/prometheus-alerts'; import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; import { PrometheusAlertService } from '~/app/shared/services/prometheus-alert.service'; import { URLBuilderService } from '~/app/shared/services/url-builder.service'; +import { DocService } from '~/app/shared/services/doc.service'; const BASE_URL = 'silences'; // as only silence actions can be used @@ -30,9 +34,11 @@ const SeverityMap = { styleUrls: ['./active-alert-list.component.scss'], standalone: false }) -export class ActiveAlertListComponent extends PrometheusListHelper implements OnInit { +export class ActiveAlertListComponent extends PrometheusListHelper implements OnInit, OnDestroy { @ViewChild('externalLinkTpl', { static: true }) externalLinkTpl: TemplateRef; + @ViewChild('docLinkTpl', { static: true }) + docLinkTpl: TemplateRef; @ViewChild(TableComponent) table: TableComponent; columns: CdTableColumn[]; innerColumns: CdTableColumn[]; @@ -41,7 +47,11 @@ export class ActiveAlertListComponent extends PrometheusListHelper implements On selection = new CdTableSelection(); icons = Icons; expandedInnerRow: any; + alertDocUrls: Record = {}; + hasDocUrls = false; multilineTextKeys = ['description', 'impact', 'fix']; + private cephRelease = ''; + private alertsSub: Subscription; filters: CdTableColumn[] = [ { @@ -76,10 +86,21 @@ export class ActiveAlertListComponent extends PrometheusListHelper implements On public prometheusAlertService: PrometheusAlertService, private urlBuilder: URLBuilderService, private route: ActivatedRoute, + private docService: DocService, @Inject(PrometheusService) prometheusService: PrometheusService ) { super(prometheusService); this.permission = this.authStorageService.getPermissions().prometheus; + this.docService.releaseData$ + .pipe( + filter((v) => !!v), + first() + ) + .subscribe((release) => { + this.cephRelease = release; + this.hasDocUrls = !!this.docService.urlGenerator('managing-alerts', release); + this.alertDocUrls = {}; + }); this.tableActions = [ { permission: 'create', @@ -153,13 +174,33 @@ export class ActiveAlertListComponent extends PrometheusListHelper implements On flexGrow: 1 }, { - name: $localize`URL`, + name: $localize`Query`, prop: 'generatorURL', flexGrow: 1, sortable: false, cellTemplate: this.externalLinkTpl - } + }, + ...(this.hasDocUrls + ? [ + { + name: $localize`Learn more`, + prop: 'labels.alertname', + flexGrow: 1, + sortable: false, + cellTemplate: this.docLinkTpl + } + ] + : []) ]; + this.alertsSub = this.prometheusAlertService.totalAlerts$.subscribe(() => { + if (!this.hasDocUrls) return; + this.alertDocUrls = Object.fromEntries( + this.prometheusAlertService.alerts.map((a) => [ + a.labels.alertname, + this.docService.alertDocUrl(a.labels.alertname, this.cephRelease) + ]) + ); + }); this.prometheusAlertService.getGroupedAlerts(true); this.route.queryParams.subscribe((params) => { const severity = params['severity']; @@ -175,6 +216,10 @@ export class ActiveAlertListComponent extends PrometheusListHelper implements On }); } + ngOnDestroy() { + this.alertsSub?.unsubscribe(); + } + setExpandedInnerRow(row: any) { this.expandedInnerRow = row; } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/doc.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/doc.service.ts index e28f2f766b0a..c8a4e920b0b9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/doc.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/doc.service.ts @@ -52,6 +52,14 @@ export class DocService { return sections[section]; } + alertDocUrl(alertName: string, releaseVersion = ''): string | null { + const baseUrl = this.urlGenerator('managing-alerts', releaseVersion); + if (!baseUrl || !alertName) { + return null; + } + return `${baseUrl}#${encodeURIComponent(`managing-alerts__${alertName.toLowerCase()}`)}`; + } + subscribeOnce( section: string, next: (release: string) => void,