This is an automated email from the ASF dual-hosted git repository.

scottyaslan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 25d58327df NIFI-15080 - Display bulletin severity (color) in listing 
tables (#10407)
25d58327df is described below

commit 25d58327df65fb14ee887f90bfdbb7fb2f3ff0e5
Author: Rob Fellows <[email protected]>
AuthorDate: Fri Oct 10 13:38:38 2025 -0400

    NIFI-15080 - Display bulletin severity (color) in listing tables (#10407)
---
 .../flow-designer/service/canvas-utils.service.ts  |  33 +-
 .../header/flow-status/flow-status.component.ts    |   7 +-
 .../edit-processor/edit-processor.component.ts     |   2 +-
 .../flow-analysis-rule-table.component.html        |   3 +-
 .../flow-analysis-rule-table.component.ts          |   4 +
 .../registry-client-table.component.html           |   3 +-
 .../registry-client-table.component.ts             |  16 +-
 .../reporting-task-table.component.html            |   3 +-
 .../reporting-task-table.component.ts              |   4 +
 .../controller-service-references.component.html   |   6 +-
 .../controller-service-references.component.ts     |   4 +
 .../controller-service-table.component.html        |   3 +-
 .../controller-service-table.component.ts          |   4 +
 .../parameter-references.component.html            |   6 +-
 .../parameter-references.component.ts              |   4 +
 .../src/services/nifi-common.service.spec.ts       | 401 +++++++++++++++++++++
 .../shared/src/services/nifi-common.service.ts     |  67 +++-
 17 files changed, 523 insertions(+), 47 deletions(-)

diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts
index de006e6e1f..47aa26ccf6 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts
@@ -1279,37 +1279,6 @@ export class CanvasUtils {
         selection.on('mouseenter', null).on('mouseleave', null);
     }
 
-    private getHigherSeverityBulletinLevel(left: BulletinEntity, right: 
BulletinEntity): BulletinEntity {
-        const bulletinSeverityMap: { [key: string]: number } = {
-            TRACE: 0,
-            DEBUG: 1,
-            INFO: 2,
-            WARNING: 3,
-            ERROR: 4
-        };
-        let mappedLeft = 0;
-        let mappedRight = 0;
-        if (left.bulletin) {
-            mappedLeft = 
bulletinSeverityMap[left.bulletin.level.toUpperCase()] || 0;
-        }
-        if (right.bulletin) {
-            mappedRight = 
bulletinSeverityMap[right.bulletin.level.toUpperCase()] || 0;
-        }
-        return mappedLeft >= mappedRight ? left : right;
-    }
-
-    public getMostSevereBulletin(bulletins: BulletinEntity[]): BulletinEntity 
| null {
-        if (bulletins && bulletins.length > 0) {
-            const mostSevere = bulletins.reduce((previous, current) => {
-                return this.getHigherSeverityBulletinLevel(previous, current);
-            });
-            if (mostSevere.bulletin) {
-                return mostSevere;
-            }
-        }
-        return null;
-    }
-
     private resetBulletin(selection: any) {
         // reset the bulletin icon/background
         selection.select('text.bulletin-icon').style('visibility', 'hidden');
@@ -1335,7 +1304,7 @@ export class CanvasUtils {
             this.resetBulletin(selection);
         } else {
             // determine the most severe of the bulletins
-            const mostSevere = this.getMostSevereBulletin(filteredBulletins);
+            const mostSevere = 
this.nifiCommon.getMostSevereBulletin(filteredBulletins);
 
             // add the proper class to indicate the most severe bulletin
             if (mostSevere) {
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.ts
index 1ff82eb87c..942c0dd0fa 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/header/flow-status/flow-status.component.ts
@@ -22,7 +22,7 @@ import { BulletinsTip } from 
'../../../../../../ui/common/tooltips/bulletins-tip
 import { BulletinsTipInput } from '../../../../../../state/shared';
 
 import { Search } from '../search/search.component';
-import { BulletinEntity, NifiTooltipDirective, Storage } from '@nifi/shared';
+import { BulletinEntity, NifiTooltipDirective, Storage, NiFiCommon } from 
'@nifi/shared';
 import { ClusterSummary } from '../../../../../../state/cluster-summary';
 import { ConnectedPosition } from '@angular/cdk/overlay';
 import { FlowAnalysisState } from '../../../../state/flow-analysis';
@@ -30,7 +30,6 @@ import { CommonModule } from '@angular/common';
 import { Store } from '@ngrx/store';
 import { NiFiState } from '../../../../../../state';
 import { setFlowAnalysisOpen } from '../../../../state/flow/flow.actions';
-import { CanvasUtils } from '../../../../service/canvas-utils.service';
 
 @Component({
     selector: 'flow-status',
@@ -41,7 +40,7 @@ import { CanvasUtils } from 
'../../../../service/canvas-utils.service';
 export class FlowStatus {
     private store = inject<Store<NiFiState>>(Store);
     private storage = inject(Storage);
-    private canvasUtils = inject(CanvasUtils);
+    private nifiCommon = inject(NiFiCommon);
 
     private static readonly FLOW_ANALYSIS_VISIBILITY_KEY: string = 
'flow-analysis-visibility';
     private static readonly FLOW_ANALYSIS_KEY: string = 'flow-analysis';
@@ -172,7 +171,7 @@ export class FlowStatus {
 
     getMostSevereBulletinLevel(): string | null {
         // determine the most severe of the bulletins
-        const mostSevere = 
this.canvasUtils.getMostSevereBulletin(this.filteredBulletins);
+        const mostSevere = 
this.nifiCommon.getMostSevereBulletin(this.filteredBulletins);
         return mostSevere ? mostSevere.bulletin.level.toLowerCase() : null;
     }
 
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/processor/edit-processor/edit-processor.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/processor/edit-processor/edit-processor.component.ts
index 8b3aa4ca55..f5f24ee199 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/processor/edit-processor/edit-processor.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/processor/edit-processor/edit-processor.component.ts
@@ -487,7 +487,7 @@ export class EditProcessor extends TabbedDialog {
 
     getMostSevereBulletinLevel(): string | null {
         // determine the most severe of the bulletins
-        const mostSevere = 
this.canvasUtils.getMostSevereBulletin(this.bulletins);
+        const mostSevere = 
this.nifiCommon.getMostSevereBulletin(this.bulletins);
         return mostSevere ? mostSevere.bulletin.level.toLowerCase() : null;
     }
 
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html
index 2798ffbe9b..6be9901cb3 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html
@@ -49,7 +49,8 @@
                             }
                             @if (hasBulletins(item)) {
                                 <div
-                                    class="pointer fa fa-sticky-note-o 
primary-color"
+                                    class="pointer fa fa-sticky-note"
+                                    [ngClass]="getBulletinSeverityClass(item)"
                                     [delayClose]="true"
                                     nifiTooltip
                                     [tooltipComponentType]="BulletinsTip"
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts
index ff19a62f7c..448dd86c9e 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts
@@ -156,6 +156,10 @@ export class FlowAnalysisRuleTable {
         };
     }
 
+    getBulletinSeverityClass(entity: FlowAnalysisRuleEntity): string {
+        return this.nifiCommon.getBulletinSeverityClass(entity.bulletins);
+    }
+
     getStateIcon(entity: FlowAnalysisRuleEntity): string {
         if (entity.status.validationStatus === 'VALIDATING') {
             return 'validating neutral-color fa fa-spin fa-circle-o-notch';
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/registry-clients/registry-client-table/registry-client-table.component.html
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/registry-clients/registry-client-table/registry-client-table.component.html
index a02826164f..5909959cde 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/registry-clients/registry-client-table/registry-client-table.component.html
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/registry-clients/registry-client-table/registry-client-table.component.html
@@ -40,7 +40,8 @@
                             }
                             @if (hasBulletins(item)) {
                                 <div
-                                    class="mr-3 pointer fa fa-sticky-note-o 
primary-color"
+                                    class="mr-3 pointer fa fa-sticky-note"
+                                    [ngClass]="getBulletinSeverityClass(item)"
                                     nifiTooltip
                                     [delayClose]="true"
                                     [tooltipComponentType]="BulletinsTip"
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/registry-clients/registry-client-table/registry-client-table.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/registry-clients/registry-client-table/registry-client-table.component.ts
index 6b8ff63728..fc5ff42c07 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/registry-clients/registry-client-table/registry-client-table.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/registry-clients/registry-client-table/registry-client-table.component.ts
@@ -18,6 +18,7 @@
 import { Component, EventEmitter, Input, Output, inject } from '@angular/core';
 import { MatTableDataSource, MatTableModule } from '@angular/material/table';
 import { MatSortModule, Sort } from '@angular/material/sort';
+import { NgClass } from '@angular/common';
 import { ReportingTaskEntity } from '../../../state/reporting-tasks';
 import { TextTip, NiFiCommon, NifiTooltipDirective } from '@nifi/shared';
 import { BulletinsTip } from 
'../../../../../ui/common/tooltips/bulletins-tip/bulletins-tip.component';
@@ -30,7 +31,16 @@ import { MatMenu, MatMenuItem, MatMenuTrigger } from 
'@angular/material/menu';
     selector: 'registry-client-table',
     templateUrl: './registry-client-table.component.html',
     styleUrls: ['./registry-client-table.component.scss'],
-    imports: [MatTableModule, MatSortModule, NifiTooltipDirective, 
MatIconButton, MatMenuTrigger, MatMenu, MatMenuItem]
+    imports: [
+        MatTableModule,
+        MatSortModule,
+        NgClass,
+        NifiTooltipDirective,
+        MatIconButton,
+        MatMenuTrigger,
+        MatMenu,
+        MatMenuItem
+    ]
 })
 export class RegistryClientTable {
     private nifiCommon = inject(NiFiCommon);
@@ -89,6 +99,10 @@ export class RegistryClientTable {
         };
     }
 
+    getBulletinSeverityClass(entity: RegistryClientEntity): string {
+        return this.nifiCommon.getBulletinSeverityClass(entity.bulletins || 
[]);
+    }
+
     formatType(entity: RegistryClientEntity): string {
         return this.nifiCommon.formatType(entity.component);
     }
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.html
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.html
index 9bdcba1cab..a9858f209d 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.html
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.html
@@ -49,7 +49,8 @@
                             }
                             @if (hasBulletins(item)) {
                                 <div
-                                    class="pointer fa fa-sticky-note-o 
primary-color"
+                                    class="pointer fa fa-sticky-note"
+                                    [ngClass]="getBulletinSeverityClass(item)"
                                     [delayClose]="true"
                                     nifiTooltip
                                     [tooltipComponentType]="BulletinsTip"
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.ts
index 19e741a636..b0279e439d 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.ts
@@ -125,6 +125,10 @@ export class ReportingTaskTable {
         };
     }
 
+    getBulletinSeverityClass(entity: ReportingTaskEntity): string {
+        return this.nifiCommon.getBulletinSeverityClass(entity.bulletins);
+    }
+
     getStateIcon(entity: ReportingTaskEntity): string {
         if (entity.status.validationStatus === 'VALIDATING') {
             return 'validating neutral-color fa fa-spin fa-circle-o-notch';
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/controller-service-references/controller-service-references.component.html
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/controller-service-references/controller-service-references.component.html
index dbe6edbc28..092c74618d 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/controller-service-references/controller-service-references.component.html
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/controller-service-references/controller-service-references.component.html
@@ -107,7 +107,8 @@
                                 }
                                 @if (hasBulletins(reference)) {
                                     <div
-                                        class="pointer fa fa-sticky-note-o 
primary-color"
+                                        class="pointer fa fa-sticky-note"
+                                        
[ngClass]="getBulletinSeverityClass(reference)"
                                         nifiTooltip
                                         [tooltipComponentType]="BulletinsTip"
                                         
[tooltipInputData]="getBulletinsTipData(reference)"
@@ -150,7 +151,8 @@
 
                                     @if (hasBulletins(service)) {
                                         <div
-                                            class="pointer fa fa-sticky-note-o 
primary-color"
+                                            class="pointer fa fa-sticky-note"
+                                            
[ngClass]="getBulletinSeverityClass(service)"
                                             nifiTooltip
                                             [delayClose]="true"
                                             
[tooltipComponentType]="BulletinsTip"
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/controller-service-references/controller-service-references.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/controller-service-references/controller-service-references.component.ts
index c6a5893cc8..9039a65ea5 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/controller-service-references/controller-service-references.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/controller-service-references/controller-service-references.component.ts
@@ -113,6 +113,10 @@ export class ControllerServiceReferences {
         };
     }
 
+    getBulletinSeverityClass(entity: 
ControllerServiceReferencingComponentEntity): string {
+        return this.nifiCommon.getBulletinSeverityClass(entity.bulletins);
+    }
+
     hasActiveThreads(reference: ControllerServiceReferencingComponent): 
boolean {
         return reference.activeThreadCount != null && 
reference.activeThreadCount > 0;
     }
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.html
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.html
index 45c69f605c..07b99dcd51 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.html
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.html
@@ -49,7 +49,8 @@
                             }
                             @if (hasBulletins(item)) {
                                 <div
-                                    class="pointer fa fa-sticky-note-o 
primary-color"
+                                    class="pointer fa fa-sticky-note"
+                                    [ngClass]="getBulletinSeverityClass(item)"
                                     nifiTooltip
                                     [delayClose]="true"
                                     [tooltipComponentType]="BulletinsTip"
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.ts
index 1ae3d5dfe0..63e300580e 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.ts
@@ -138,6 +138,10 @@ export class ControllerServiceTable {
         };
     }
 
+    getBulletinSeverityClass(entity: ControllerServiceEntity): string {
+        return this.nifiCommon.getBulletinSeverityClass(entity.bulletins);
+    }
+
     getStateIcon(entity: ControllerServiceEntity): string {
         if (entity.status.validationStatus === 'VALIDATING') {
             return 'validating neutral-color fa fa-spin fa-circle-o-notch';
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-references/parameter-references.component.html
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-references/parameter-references.component.html
index 325d93cc78..16ed977b03 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-references/parameter-references.component.html
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-references/parameter-references.component.html
@@ -75,7 +75,8 @@
                                 }
                                 @if (hasBulletins(reference)) {
                                     <div
-                                        class="pointer fa fa-sticky-note-o 
primary-color"
+                                        class="pointer fa fa-sticky-note"
+                                        
[ngClass]="getBulletinSeverityClass(reference)"
                                         nifiTooltip
                                         [delayClose]="true"
                                         [tooltipComponentType]="BulletinsTip"
@@ -116,7 +117,8 @@
                                     }
                                     @if (hasBulletins(service)) {
                                         <div
-                                            class="pointer fa fa-sticky-note-o 
primary-color"
+                                            class="pointer fa fa-sticky-note"
+                                            
[ngClass]="getBulletinSeverityClass(service)"
                                             nifiTooltip
                                             [delayClose]="true"
                                             
[tooltipComponentType]="BulletinsTip"
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-references/parameter-references.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-references/parameter-references.component.ts
index c4394a8d8f..c9692168aa 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-references/parameter-references.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-references/parameter-references.component.ts
@@ -147,6 +147,10 @@ export class ParameterReferences {
         };
     }
 
+    getBulletinSeverityClass(entity: AffectedComponentEntity): string {
+        return this.nifiCommon.getBulletinSeverityClass(entity.bulletins);
+    }
+
     hasActiveThreads(reference: AffectedComponent): boolean {
         return reference.activeThreadCount != null && 
reference.activeThreadCount > 0;
     }
diff --git 
a/nifi-frontend/src/main/frontend/libs/shared/src/services/nifi-common.service.spec.ts
 
b/nifi-frontend/src/main/frontend/libs/shared/src/services/nifi-common.service.spec.ts
index c4e9a6b04d..af3140611f 100644
--- 
a/nifi-frontend/src/main/frontend/libs/shared/src/services/nifi-common.service.spec.ts
+++ 
b/nifi-frontend/src/main/frontend/libs/shared/src/services/nifi-common.service.spec.ts
@@ -97,4 +97,405 @@ describe('Common', () => {
             });
         });
     });
+
+    describe('getMostSevereBulletin', () => {
+        it('should return null when no bulletins provided', () => {
+            const result = service.getMostSevereBulletin([]);
+            expect(result).toBeNull();
+        });
+
+        it('should return null when bulletins is null', () => {
+            const result = service.getMostSevereBulletin(null as any);
+            expect(result).toBeNull();
+        });
+
+        it('should return null when bulletins is undefined', () => {
+            const result = service.getMostSevereBulletin(undefined as any);
+            expect(result).toBeNull();
+        });
+
+        it('should return the bulletin when only one bulletin provided', () => 
{
+            const bulletins = [
+                {
+                    id: 1,
+                    canRead: true,
+                    sourceId: 'source1',
+                    groupId: 'group1',
+                    timestamp: '2023-01-01T00:00:00Z',
+                    bulletin: {
+                        id: 1,
+                        sourceId: 'source1',
+                        groupId: 'group1',
+                        category: 'test',
+                        sourceName: 'Source 1',
+                        sourceType: 'PROCESSOR',
+                        level: 'ERROR',
+                        message: 'Error message',
+                        timestamp: '2023-01-01T00:00:00Z'
+                    }
+                }
+            ];
+            const result = service.getMostSevereBulletin(bulletins);
+            expect(result).toEqual(bulletins[0]);
+        });
+
+        it('should return ERROR bulletin when multiple severities present', () 
=> {
+            const bulletins = [
+                {
+                    id: 1,
+                    canRead: true,
+                    sourceId: 'source1',
+                    groupId: 'group1',
+                    timestamp: '2023-01-01T00:00:00Z',
+                    bulletin: {
+                        id: 1,
+                        sourceId: 'source1',
+                        groupId: 'group1',
+                        category: 'test',
+                        sourceName: 'Source 1',
+                        sourceType: 'PROCESSOR',
+                        level: 'INFO',
+                        message: 'Info message',
+                        timestamp: '2023-01-01T00:00:00Z'
+                    }
+                },
+                {
+                    id: 2,
+                    canRead: true,
+                    sourceId: 'source2',
+                    groupId: 'group2',
+                    timestamp: '2023-01-01T00:00:00Z',
+                    bulletin: {
+                        id: 2,
+                        sourceId: 'source2',
+                        groupId: 'group2',
+                        category: 'test',
+                        sourceName: 'Source 2',
+                        sourceType: 'PROCESSOR',
+                        level: 'ERROR',
+                        message: 'Error message',
+                        timestamp: '2023-01-01T00:00:00Z'
+                    }
+                }
+            ];
+            const result = service.getMostSevereBulletin(bulletins);
+            expect(result).toEqual(bulletins[1]);
+        });
+
+        it('should return WARNING bulletin when no ERROR present', () => {
+            const bulletins = [
+                {
+                    id: 1,
+                    canRead: true,
+                    sourceId: 'source1',
+                    groupId: 'group1',
+                    timestamp: '2023-01-01T00:00:00Z',
+                    bulletin: {
+                        id: 1,
+                        sourceId: 'source1',
+                        groupId: 'group1',
+                        category: 'test',
+                        sourceName: 'Source 1',
+                        sourceType: 'PROCESSOR',
+                        level: 'INFO',
+                        message: 'Info message',
+                        timestamp: '2023-01-01T00:00:00Z'
+                    }
+                },
+                {
+                    id: 2,
+                    canRead: true,
+                    sourceId: 'source2',
+                    groupId: 'group2',
+                    timestamp: '2023-01-01T00:00:00Z',
+                    bulletin: {
+                        id: 2,
+                        sourceId: 'source2',
+                        groupId: 'group2',
+                        category: 'test',
+                        sourceName: 'Source 2',
+                        sourceType: 'PROCESSOR',
+                        level: 'WARNING',
+                        message: 'Warning message',
+                        timestamp: '2023-01-01T00:00:00Z'
+                    }
+                }
+            ];
+            const result = service.getMostSevereBulletin(bulletins);
+            expect(result).toEqual(bulletins[1]);
+        });
+    });
+
+    describe('getBulletinSeverityClass', () => {
+        it('should return tertiary-color when no bulletins provided', () => {
+            const result = service.getBulletinSeverityClass([]);
+            expect(result).toBe('tertiary-color');
+        });
+
+        it('should return tertiary-color when bulletins is null', () => {
+            const result = service.getBulletinSeverityClass(null as any);
+            expect(result).toBe('tertiary-color');
+        });
+
+        it('should return error-color for error level bulletins', () => {
+            const bulletins = [
+                {
+                    id: 1,
+                    canRead: true,
+                    sourceId: 'source1',
+                    groupId: 'group1',
+                    timestamp: '2023-01-01T00:00:00Z',
+                    bulletin: {
+                        id: 1,
+                        sourceId: 'source1',
+                        groupId: 'group1',
+                        category: 'test',
+                        sourceName: 'Source 1',
+                        sourceType: 'PROCESSOR',
+                        level: 'ERROR',
+                        message: 'Error message',
+                        timestamp: '2023-01-01T00:00:00Z'
+                    }
+                }
+            ];
+            const result = service.getBulletinSeverityClass(bulletins);
+            expect(result).toBe('error-color');
+        });
+
+        it('should return caution-color for warning level bulletins', () => {
+            const bulletins = [
+                {
+                    id: 1,
+                    canRead: true,
+                    sourceId: 'source1',
+                    groupId: 'group1',
+                    timestamp: '2023-01-01T00:00:00Z',
+                    bulletin: {
+                        id: 1,
+                        sourceId: 'source1',
+                        groupId: 'group1',
+                        category: 'test',
+                        sourceName: 'Source 1',
+                        sourceType: 'PROCESSOR',
+                        level: 'WARNING',
+                        message: 'Warning message',
+                        timestamp: '2023-01-01T00:00:00Z'
+                    }
+                }
+            ];
+            const result = service.getBulletinSeverityClass(bulletins);
+            expect(result).toBe('caution-color');
+        });
+
+        it('should return caution-color for warn level bulletins (case 
insensitive)', () => {
+            const bulletins = [
+                {
+                    id: 1,
+                    canRead: true,
+                    sourceId: 'source1',
+                    groupId: 'group1',
+                    timestamp: '2023-01-01T00:00:00Z',
+                    bulletin: {
+                        id: 1,
+                        sourceId: 'source1',
+                        groupId: 'group1',
+                        category: 'test',
+                        sourceName: 'Source 1',
+                        sourceType: 'PROCESSOR',
+                        level: 'warn',
+                        message: 'Warning message',
+                        timestamp: '2023-01-01T00:00:00Z'
+                    }
+                }
+            ];
+            const result = service.getBulletinSeverityClass(bulletins);
+            expect(result).toBe('caution-color');
+        });
+
+        it('should return success-color-default for info level bulletins', () 
=> {
+            const bulletins = [
+                {
+                    id: 1,
+                    canRead: true,
+                    sourceId: 'source1',
+                    groupId: 'group1',
+                    timestamp: '2023-01-01T00:00:00Z',
+                    bulletin: {
+                        id: 1,
+                        sourceId: 'source1',
+                        groupId: 'group1',
+                        category: 'test',
+                        sourceName: 'Source 1',
+                        sourceType: 'PROCESSOR',
+                        level: 'INFO',
+                        message: 'Info message',
+                        timestamp: '2023-01-01T00:00:00Z'
+                    }
+                }
+            ];
+            const result = service.getBulletinSeverityClass(bulletins);
+            expect(result).toBe('success-color-default');
+        });
+
+        it('should return success-color-default for debug level bulletins', () 
=> {
+            const bulletins = [
+                {
+                    id: 1,
+                    canRead: true,
+                    sourceId: 'source1',
+                    groupId: 'group1',
+                    timestamp: '2023-01-01T00:00:00Z',
+                    bulletin: {
+                        id: 1,
+                        sourceId: 'source1',
+                        groupId: 'group1',
+                        category: 'test',
+                        sourceName: 'Source 1',
+                        sourceType: 'PROCESSOR',
+                        level: 'DEBUG',
+                        message: 'Debug message',
+                        timestamp: '2023-01-01T00:00:00Z'
+                    }
+                }
+            ];
+            const result = service.getBulletinSeverityClass(bulletins);
+            expect(result).toBe('success-color-default');
+        });
+
+        it('should return success-color-default for trace level bulletins', () 
=> {
+            const bulletins = [
+                {
+                    id: 1,
+                    canRead: true,
+                    sourceId: 'source1',
+                    groupId: 'group1',
+                    timestamp: '2023-01-01T00:00:00Z',
+                    bulletin: {
+                        id: 1,
+                        sourceId: 'source1',
+                        groupId: 'group1',
+                        category: 'test',
+                        sourceName: 'Source 1',
+                        sourceType: 'PROCESSOR',
+                        level: 'TRACE',
+                        message: 'Trace message',
+                        timestamp: '2023-01-01T00:00:00Z'
+                    }
+                }
+            ];
+            const result = service.getBulletinSeverityClass(bulletins);
+            expect(result).toBe('success-color-default');
+        });
+
+        it('should return success-color-default for unknown level bulletins', 
() => {
+            const bulletins = [
+                {
+                    id: 1,
+                    canRead: true,
+                    sourceId: 'source1',
+                    groupId: 'group1',
+                    timestamp: '2023-01-01T00:00:00Z',
+                    bulletin: {
+                        id: 1,
+                        sourceId: 'source1',
+                        groupId: 'group1',
+                        category: 'test',
+                        sourceName: 'Source 1',
+                        sourceType: 'PROCESSOR',
+                        level: 'UNKNOWN',
+                        message: 'Unknown message',
+                        timestamp: '2023-01-01T00:00:00Z'
+                    }
+                }
+            ];
+            const result = service.getBulletinSeverityClass(bulletins);
+            expect(result).toBe('success-color-default');
+        });
+
+        it('should return error-color when multiple bulletins with different 
severities', () => {
+            const bulletins = [
+                {
+                    id: 1,
+                    canRead: true,
+                    sourceId: 'source1',
+                    groupId: 'group1',
+                    timestamp: '2023-01-01T00:00:00Z',
+                    bulletin: {
+                        id: 1,
+                        sourceId: 'source1',
+                        groupId: 'group1',
+                        category: 'test',
+                        sourceName: 'Source 1',
+                        sourceType: 'PROCESSOR',
+                        level: 'INFO',
+                        message: 'Info message',
+                        timestamp: '2023-01-01T00:00:00Z'
+                    }
+                },
+                {
+                    id: 2,
+                    canRead: true,
+                    sourceId: 'source2',
+                    groupId: 'group2',
+                    timestamp: '2023-01-01T00:00:00Z',
+                    bulletin: {
+                        id: 2,
+                        sourceId: 'source2',
+                        groupId: 'group2',
+                        category: 'test',
+                        sourceName: 'Source 2',
+                        sourceType: 'PROCESSOR',
+                        level: 'ERROR',
+                        message: 'Error message',
+                        timestamp: '2023-01-01T00:00:00Z'
+                    }
+                }
+            ];
+            const result = service.getBulletinSeverityClass(bulletins);
+            expect(result).toBe('error-color');
+        });
+
+        it('should return caution-color when multiple bulletins with warning 
being most severe', () => {
+            const bulletins = [
+                {
+                    id: 1,
+                    canRead: true,
+                    sourceId: 'source1',
+                    groupId: 'group1',
+                    timestamp: '2023-01-01T00:00:00Z',
+                    bulletin: {
+                        id: 1,
+                        sourceId: 'source1',
+                        groupId: 'group1',
+                        category: 'test',
+                        sourceName: 'Source 1',
+                        sourceType: 'PROCESSOR',
+                        level: 'INFO',
+                        message: 'Info message',
+                        timestamp: '2023-01-01T00:00:00Z'
+                    }
+                },
+                {
+                    id: 2,
+                    canRead: true,
+                    sourceId: 'source2',
+                    groupId: 'group2',
+                    timestamp: '2023-01-01T00:00:00Z',
+                    bulletin: {
+                        id: 2,
+                        sourceId: 'source2',
+                        groupId: 'group2',
+                        category: 'test',
+                        sourceName: 'Source 2',
+                        sourceType: 'PROCESSOR',
+                        level: 'WARNING',
+                        message: 'Warning message',
+                        timestamp: '2023-01-01T00:00:00Z'
+                    }
+                }
+            ];
+            const result = service.getBulletinSeverityClass(bulletins);
+            expect(result).toBe('caution-color');
+        });
+    });
 });
diff --git 
a/nifi-frontend/src/main/frontend/libs/shared/src/services/nifi-common.service.ts
 
b/nifi-frontend/src/main/frontend/libs/shared/src/services/nifi-common.service.ts
index c6a41dbe68..004de27d39 100644
--- 
a/nifi-frontend/src/main/frontend/libs/shared/src/services/nifi-common.service.ts
+++ 
b/nifi-frontend/src/main/frontend/libs/shared/src/services/nifi-common.service.ts
@@ -16,7 +16,7 @@
  */
 
 import { Injectable } from '@angular/core';
-import { SelectOption } from '../types';
+import { SelectOption, BulletinEntity } from '../types';
 
 @Injectable({
     providedIn: 'root'
@@ -713,4 +713,69 @@ export class NiFiCommon {
     public stripProtocol(url: string): string {
         return this.substringAfterFirst(url, ':');
     }
+
+    /**
+     * Determines the most severe bulletin from a list of bulletins.
+     * Severity order: ERROR > WARNING > INFO > DEBUG > TRACE
+     */
+    public getMostSevereBulletin(bulletins: BulletinEntity[]): BulletinEntity 
| null {
+        if (bulletins && bulletins.length > 0) {
+            const mostSevere = bulletins.reduce((previous, current) => {
+                return this.getHigherSeverityBulletinLevel(previous, current);
+            });
+            if (mostSevere.bulletin) {
+                return mostSevere;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Helper method to determine which bulletin has higher severity.
+     * Uses numeric mapping for severity comparison.
+     */
+    private getHigherSeverityBulletinLevel(left: BulletinEntity, right: 
BulletinEntity): BulletinEntity {
+        const bulletinSeverityMap: { [key: string]: number } = {
+            TRACE: 0,
+            DEBUG: 1,
+            INFO: 2,
+            WARNING: 3,
+            ERROR: 4
+        };
+        let mappedLeft = 0;
+        let mappedRight = 0;
+        if (left.bulletin) {
+            mappedLeft = 
bulletinSeverityMap[left.bulletin.level.toUpperCase()] || 0;
+        }
+        if (right.bulletin) {
+            mappedRight = 
bulletinSeverityMap[right.bulletin.level.toUpperCase()] || 0;
+        }
+
+        return mappedLeft >= mappedRight ? left : right;
+    }
+
+    /**
+     * Returns the appropriate CSS class for bulletin severity-based styling.
+     * @param bulletins Array of bulletin entities
+     * @returns CSS class name for severity-based styling
+     */
+    public getBulletinSeverityClass(bulletins: BulletinEntity[]): string {
+        const mostSevere = this.getMostSevereBulletin(bulletins);
+        if (!mostSevere) {
+            return 'tertiary-color';
+        }
+
+        switch (mostSevere.bulletin.level.toLowerCase()) {
+            case 'error':
+                return 'error-color';
+            case 'warn':
+            case 'warning':
+                return 'caution-color';
+            case 'info':
+            case 'debug':
+            case 'trace':
+            default:
+                return 'success-color-default';
+        }
+    }
 }


Reply via email to