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

mcgilman 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 eca0f03bea NIFI-15029 - Update tabbed dialog to leverage 
InjectionToken, allowing subclasses to define their own dialogId (#10358)
eca0f03bea is described below

commit eca0f03bea02d7616336ac04a9afe901ac74abd6
Author: Rob Fellows <[email protected]>
AuthorDate: Tue Sep 30 16:50:46 2025 -0400

    NIFI-15029 - Update tabbed dialog to leverage InjectionToken, allowing 
subclasses to define their own dialogId (#10358)
---
 .../edit-connection/edit-connection.component.ts   |  12 +-
 .../edit-process-group.component.ts                |  12 +-
 .../edit-processor/edit-processor.component.ts     |  12 +-
 .../flowfile-dialog/flowfile-dialog.component.ts   |  10 +-
 .../edit-flow-analysis-rule.component.ts           |  12 +-
 .../edit-parameter-provider.component.ts           |  12 +-
 .../edit-registry-client.component.ts              |  12 +-
 .../edit-reporting-task.component.ts               |  12 +-
 .../edit-controller-service.component.ts           |  12 +-
 .../edit-parameter-context.component.ts            |  12 +-
 .../provenance-event-dialog.component.ts           |  10 +-
 .../system-diagnostics-dialog.component.ts         |  12 +-
 .../tabbed-dialog/tabbed-dialog.component.spec.ts  | 377 +++++++++++++++++++++
 .../tabbed-dialog/tabbed-dialog.component.ts       |  31 +-
 14 files changed, 506 insertions(+), 42 deletions(-)

diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/connection/edit-connection/edit-connection.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/connection/edit-connection/edit-connection.component.ts
index f87064441e..ff9511676e 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/connection/edit-connection/edit-connection.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/connection/edit-connection/edit-connection.component.ts
@@ -52,7 +52,7 @@ import { DestinationProcessGroup } from 
'../destination/destination-process-grou
 import { SourceRemoteProcessGroup } from 
'../source/source-remote-process-group/source-remote-process-group.component';
 import { DestinationRemoteProcessGroup } from 
'../destination/destination-remote-process-group/destination-remote-process-group.component';
 import { BreadcrumbEntity } from '../../../../../state/shared';
-import { TabbedDialog } from 
'../../../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
+import { TabbedDialog, TABBED_DIALOG_ID } from 
'../../../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
 import { ErrorContextKey } from '../../../../../../../state/error';
 import { ContextErrorBanner } from 
'../../../../../../../ui/common/context-error-banner/context-error-banner.component';
 
@@ -86,7 +86,13 @@ import { ContextErrorBanner } from 
'../../../../../../../ui/common/context-error
         CopyDirective
     ],
     templateUrl: './edit-connection.component.html',
-    styleUrls: ['./edit-connection.component.scss']
+    styleUrls: ['./edit-connection.component.scss'],
+    providers: [
+        {
+            provide: TABBED_DIALOG_ID,
+            useValue: 'edit-connection-selected-index'
+        }
+    ]
 })
 export class EditConnectionComponent extends TabbedDialog {
     dialogRequest = inject<EditConnectionDialogRequest>(MAT_DIALOG_DATA);
@@ -230,7 +236,7 @@ export class EditConnectionComponent extends TabbedDialog {
     initialCompression: string;
 
     constructor() {
-        super('edit-connection-selected-index');
+        super();
         const dialogRequest = this.dialogRequest;
 
         const connection: any = dialogRequest.entity.component;
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/edit-process-group/edit-process-group.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/edit-process-group/edit-process-group.component.ts
index 1ee3120e4b..04cd8e9c54 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/edit-process-group/edit-process-group.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/items/process-group/edit-process-group/edit-process-group.component.ts
@@ -30,7 +30,7 @@ import { Client } from 
'../../../../../../../service/client.service';
 import { NifiSpinnerDirective } from 
'../../../../../../../ui/common/spinner/nifi-spinner.directive';
 import { EditComponentDialogRequest } from '../../../../../state/flow';
 import { ClusterConnectionService } from 
'../../../../../../../service/cluster-connection.service';
-import { TabbedDialog } from 
'../../../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
+import { TabbedDialog, TABBED_DIALOG_ID } from 
'../../../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
 import { ErrorContextKey } from '../../../../../../../state/error';
 import { ContextErrorBanner } from 
'../../../../../../../ui/common/context-error-banner/context-error-banner.component';
 import { openNewParameterContextDialog } from 
'../../../../../state/parameter/parameter.actions';
@@ -59,7 +59,13 @@ import { selectCurrentUser } from 
'../../../../../../../state/current-user/curre
         ContextErrorBanner,
         SortObjectByPropertyPipe
     ],
-    styleUrls: ['./edit-process-group.component.scss']
+    styleUrls: ['./edit-process-group.component.scss'],
+    providers: [
+        {
+            provide: TABBED_DIALOG_ID,
+            useValue: 'edit-process-group-selected-index'
+        }
+    ]
 })
 export class EditProcessGroup extends TabbedDialog {
     request = inject<EditComponentDialogRequest>(MAT_DIALOG_DATA);
@@ -188,7 +194,7 @@ export class EditProcessGroup extends TabbedDialog {
     ];
 
     constructor() {
-        super('edit-process-group-selected-index');
+        super();
         const request = this.request;
 
         this.readonly = !request.entity.permissions.canWrite;
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 657980dd0e..8b3aa4ca55 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
@@ -78,7 +78,7 @@ import {
     ModifiedProperties,
     VerifyPropertiesRequestContext
 } from '../../../../../../../state/property-verification';
-import { TabbedDialog } from 
'../../../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
+import { TabbedDialog, TABBED_DIALOG_ID } from 
'../../../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
 import { ErrorContextKey } from '../../../../../../../state/error';
 import { ContextErrorBanner } from 
'../../../../../../../ui/common/context-error-banner/context-error-banner.component';
 import { BulletinsTip } from 
'../../../../../../../ui/common/tooltips/bulletins-tip/bulletins-tip.component';
@@ -108,7 +108,13 @@ import { ConnectedPosition } from '@angular/cdk/overlay';
         CopyDirective,
         NgClass
     ],
-    styleUrls: ['./edit-processor.component.scss']
+    styleUrls: ['./edit-processor.component.scss'],
+    providers: [
+        {
+            provide: TABBED_DIALOG_ID,
+            useValue: 'edit-processor-selected-index'
+        }
+    ]
 })
 export class EditProcessor extends TabbedDialog {
     request = inject<EditComponentDialogRequest>(MAT_DIALOG_DATA);
@@ -210,7 +216,7 @@ export class EditProcessor extends TabbedDialog {
     runDurationMillis: number;
 
     constructor() {
-        super('edit-processor-selected-index');
+        super();
         const request = this.request;
 
         const processorProperties: any = 
request.entity.component.config.properties;
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/queue/ui/queue-listing/flowfile-dialog/flowfile-dialog.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/queue/ui/queue-listing/flowfile-dialog/flowfile-dialog.component.ts
index 535436b4ee..e3eb9f0956 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/queue/ui/queue-listing/flowfile-dialog/flowfile-dialog.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/queue/ui/queue-listing/flowfile-dialog/flowfile-dialog.component.ts
@@ -26,7 +26,7 @@ import { MatDatepickerModule } from 
'@angular/material/datepicker';
 import { MatTabsModule } from '@angular/material/tabs';
 import { FlowFileDialogRequest } from '../../../state/queue-listing';
 import { CopyDirective, NiFiCommon } from '@nifi/shared';
-import { TabbedDialog } from 
'../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
+import { TabbedDialog, TABBED_DIALOG_ID } from 
'../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
 
 @Component({
     selector: 'flowfile-dialog',
@@ -46,6 +46,12 @@ import { TabbedDialog } from 
'../../../../../ui/common/tabbed-dialog/tabbed-dial
         FormsModule,
         KeyValuePipe,
         CopyDirective
+    ],
+    providers: [
+        {
+            provide: TABBED_DIALOG_ID,
+            useValue: 'flowfile-dialog-selected-index'
+        }
     ]
 })
 export class FlowFileDialog extends TabbedDialog {
@@ -58,7 +64,7 @@ export class FlowFileDialog extends TabbedDialog {
     @Output() viewContent: EventEmitter<void> = new EventEmitter<void>();
 
     constructor() {
-        super('flowfile-dialog-selected-index');
+        super();
     }
 
     formatDurationValue(duration: number): string {
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/flow-analysis-rules/edit-flow-analysis-rule/edit-flow-analysis-rule.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/flow-analysis-rules/edit-flow-analysis-rule/edit-flow-analysis-rule.component.ts
index ef6d0480ed..891cf2a5fe 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/flow-analysis-rules/edit-flow-analysis-rule/edit-flow-analysis-rule.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/flow-analysis-rules/edit-flow-analysis-rule/edit-flow-analysis-rule.component.ts
@@ -42,7 +42,7 @@ import {
     VerifyPropertiesRequestContext
 } from '../../../../../state/property-verification';
 import { PropertyVerification } from 
'../../../../../ui/common/property-verification/property-verification.component';
-import { TabbedDialog } from 
'../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
+import { TabbedDialog, TABBED_DIALOG_ID } from 
'../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
 import { ErrorContextKey } from '../../../../../state/error';
 import { ContextErrorBanner } from 
'../../../../../ui/common/context-error-banner/context-error-banner.component';
 
@@ -65,7 +65,13 @@ import { ContextErrorBanner } from 
'../../../../../ui/common/context-error-banne
         ContextErrorBanner,
         CopyDirective
     ],
-    styleUrls: ['./edit-flow-analysis-rule.component.scss']
+    styleUrls: ['./edit-flow-analysis-rule.component.scss'],
+    providers: [
+        {
+            provide: TABBED_DIALOG_ID,
+            useValue: 'edit-flow-analysis-rule-selected-index'
+        }
+    ]
 })
 export class EditFlowAnalysisRule extends TabbedDialog {
     request = inject<EditFlowAnalysisRuleDialogRequest>(MAT_DIALOG_DATA);
@@ -102,7 +108,7 @@ export class EditFlowAnalysisRule extends TabbedDialog {
     ];
 
     constructor() {
-        super('edit-flow-analysis-rule-selected-index');
+        super();
         const request = this.request;
 
         this.readonly =
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.ts
index 7e7e3e5732..5b994b0e74 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/parameter-providers/edit-parameter-provider/edit-parameter-provider.component.ts
@@ -48,7 +48,7 @@ import {
     VerifyPropertiesRequestContext
 } from '../../../../../state/property-verification';
 import { PropertyVerification } from 
'../../../../../ui/common/property-verification/property-verification.component';
-import { TabbedDialog } from 
'../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
+import { TabbedDialog, TABBED_DIALOG_ID } from 
'../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
 import { ErrorContextKey } from '../../../../../state/error';
 import { ContextErrorBanner } from 
'../../../../../ui/common/context-error-banner/context-error-banner.component';
 
@@ -71,7 +71,13 @@ import { ContextErrorBanner } from 
'../../../../../ui/common/context-error-banne
         CopyDirective
     ],
     templateUrl: './edit-parameter-provider.component.html',
-    styleUrls: ['./edit-parameter-provider.component.scss']
+    styleUrls: ['./edit-parameter-provider.component.scss'],
+    providers: [
+        {
+            provide: TABBED_DIALOG_ID,
+            useValue: 'edit-parameter-provider-selected-index'
+        }
+    ]
 })
 export class EditParameterProvider extends TabbedDialog {
     request = inject<EditParameterProviderRequest>(MAT_DIALOG_DATA);
@@ -96,7 +102,7 @@ export class EditParameterProvider extends TabbedDialog {
     readonly: boolean;
 
     constructor() {
-        super('edit-parameter-provider-selected-index');
+        super();
         const request = this.request;
 
         this.readonly = !request.parameterProvider.permissions.canWrite;
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/registry-clients/edit-registry-client/edit-registry-client.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/registry-clients/edit-registry-client/edit-registry-client.component.ts
index 0986ac165b..6785f817ad 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/registry-clients/edit-registry-client/edit-registry-client.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/registry-clients/edit-registry-client/edit-registry-client.component.ts
@@ -37,7 +37,7 @@ import { TextTip, NiFiCommon, CopyDirective } from 
'@nifi/shared';
 import { MatTabsModule } from '@angular/material/tabs';
 import { PropertyTable } from 
'../../../../../ui/common/property-table/property-table.component';
 import { ClusterConnectionService } from 
'../../../../../service/cluster-connection.service';
-import { TabbedDialog } from 
'../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
+import { TabbedDialog, TABBED_DIALOG_ID } from 
'../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
 import { ErrorContextKey } from '../../../../../state/error';
 import { ContextErrorBanner } from 
'../../../../../ui/common/context-error-banner/context-error-banner.component';
 
@@ -58,7 +58,13 @@ import { ContextErrorBanner } from 
'../../../../../ui/common/context-error-banne
         ContextErrorBanner,
         CopyDirective
     ],
-    styleUrls: ['./edit-registry-client.component.scss']
+    styleUrls: ['./edit-registry-client.component.scss'],
+    providers: [
+        {
+            provide: TABBED_DIALOG_ID,
+            useValue: 'edit-registry-client-selected-index'
+        }
+    ]
 })
 export class EditRegistryClient extends TabbedDialog {
     request = inject<EditRegistryClientDialogRequest>(MAT_DIALOG_DATA);
@@ -79,7 +85,7 @@ export class EditRegistryClient extends TabbedDialog {
     editRegistryClientForm: FormGroup;
 
     constructor() {
-        super('edit-registry-client-selected-index');
+        super();
         const request = this.request;
 
         const serviceProperties: any = 
request.registryClient.component.properties;
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/reporting-tasks/edit-reporting-task/edit-reporting-task.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/reporting-tasks/edit-reporting-task/edit-reporting-task.component.ts
index c07aab4bc9..9cf6896e24 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/reporting-tasks/edit-reporting-task/edit-reporting-task.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/settings/ui/reporting-tasks/edit-reporting-task/edit-reporting-task.component.ts
@@ -48,7 +48,7 @@ import {
     VerifyPropertiesRequestContext
 } from '../../../../../state/property-verification';
 import { PropertyVerification } from 
'../../../../../ui/common/property-verification/property-verification.component';
-import { TabbedDialog } from 
'../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
+import { TabbedDialog, TABBED_DIALOG_ID } from 
'../../../../../ui/common/tabbed-dialog/tabbed-dialog.component';
 import { SelectOption } from '@nifi/shared';
 import { ErrorContextKey } from '../../../../../state/error';
 import { ContextErrorBanner } from 
'../../../../../ui/common/context-error-banner/context-error-banner.component';
@@ -73,7 +73,13 @@ import { ContextErrorBanner } from 
'../../../../../ui/common/context-error-banne
         ContextErrorBanner,
         CopyDirective
     ],
-    styleUrls: ['./edit-reporting-task.component.scss']
+    styleUrls: ['./edit-reporting-task.component.scss'],
+    providers: [
+        {
+            provide: TABBED_DIALOG_ID,
+            useValue: 'edit-reporting-task-selected-index'
+        }
+    ]
 })
 export class EditReportingTask extends TabbedDialog {
     request = inject<EditReportingTaskDialogRequest>(MAT_DIALOG_DATA);
@@ -116,7 +122,7 @@ export class EditReportingTask extends TabbedDialog {
     ];
 
     constructor() {
-        super('edit-reporting-task-selected-index');
+        super();
         const request = this.request;
 
         this.readonly =
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/edit-controller-service/edit-controller-service.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/edit-controller-service/edit-controller-service.component.ts
index abc08dd821..dc18bac93a 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/edit-controller-service/edit-controller-service.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/controller-service/edit-controller-service/edit-controller-service.component.ts
@@ -50,7 +50,7 @@ import {
     ModifiedProperties,
     VerifyPropertiesRequestContext
 } from '../../../../state/property-verification';
-import { TabbedDialog } from '../../tabbed-dialog/tabbed-dialog.component';
+import { TabbedDialog, TABBED_DIALOG_ID } from 
'../../tabbed-dialog/tabbed-dialog.component';
 import { ErrorContextKey } from '../../../../state/error';
 import { ContextErrorBanner } from 
'../../context-error-banner/context-error-banner.component';
 
@@ -76,7 +76,13 @@ import { ContextErrorBanner } from 
'../../context-error-banner/context-error-ban
         ContextErrorBanner,
         CopyDirective
     ],
-    styleUrls: ['./edit-controller-service.component.scss']
+    styleUrls: ['./edit-controller-service.component.scss'],
+    providers: [
+        {
+            provide: TABBED_DIALOG_ID,
+            useValue: 'edit-controller-service-selected-index'
+        }
+    ]
 })
 export class EditControllerService extends TabbedDialog {
     request = inject<EditControllerServiceDialogRequest>(MAT_DIALOG_DATA);
@@ -132,7 +138,7 @@ export class EditControllerService extends TabbedDialog {
     ];
 
     constructor() {
-        super('edit-controller-service-selected-index');
+        super();
         const request = this.request;
 
         this.readonly =
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.ts
index 4ed38d9732..74726e20cf 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/parameter-context/edit-parameter-context/edit-parameter-context.component.ts
@@ -43,7 +43,7 @@ import { ParameterContextInheritance } from 
'../parameter-context-inheritance/pa
 import { ParameterReferences } from 
'../../parameter-references/parameter-references.component';
 import { RouterLink } from '@angular/router';
 import { ClusterConnectionService } from 
'../../../../service/cluster-connection.service';
-import { TabbedDialog } from '../../tabbed-dialog/tabbed-dialog.component';
+import { TabbedDialog, TABBED_DIALOG_ID } from 
'../../tabbed-dialog/tabbed-dialog.component';
 import { NiFiCommon, TextTip, NifiTooltipDirective, CopyDirective, Parameter } 
from '@nifi/shared';
 import { ErrorContextKey } from '../../../../state/error';
 import { ContextErrorBanner } from 
'../../context-error-banner/context-error-banner.component';
@@ -72,7 +72,13 @@ import { ContextErrorBanner } from 
'../../context-error-banner/context-error-ban
         ContextErrorBanner,
         CopyDirective
     ],
-    styleUrls: ['./edit-parameter-context.component.scss']
+    styleUrls: ['./edit-parameter-context.component.scss'],
+    providers: [
+        {
+            provide: TABBED_DIALOG_ID,
+            useValue: NiFiCommon.EDIT_PARAMETER_CONTEXT_DIALOG_ID
+        }
+    ]
 })
 export class EditParameterContext extends TabbedDialog {
     request = inject<EditParameterContextRequest>(MAT_DIALOG_DATA);
@@ -99,7 +105,7 @@ export class EditParameterContext extends TabbedDialog {
     parameters!: ParameterEntity[];
 
     constructor() {
-        super(NiFiCommon.EDIT_PARAMETER_CONTEXT_DIALOG_ID);
+        super();
         const request = this.request;
 
         if (request.parameterContext) {
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/provenance-event-dialog/provenance-event-dialog.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/provenance-event-dialog/provenance-event-dialog.component.ts
index f6d3c216b0..f68e040f1e 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/provenance-event-dialog/provenance-event-dialog.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/provenance-event-dialog/provenance-event-dialog.component.ts
@@ -26,7 +26,7 @@ import { MatDatepickerModule } from 
'@angular/material/datepicker';
 import { CopyDirective, NiFiCommon } from '@nifi/shared';
 import { MatTabsModule } from '@angular/material/tabs';
 import { Attribute, ProvenanceEventDialogRequest } from 
'../../../state/shared';
-import { TabbedDialog } from '../tabbed-dialog/tabbed-dialog.component';
+import { TabbedDialog, TABBED_DIALOG_ID } from 
'../tabbed-dialog/tabbed-dialog.component';
 
 @Component({
     selector: 'provenance-event-dialog',
@@ -44,6 +44,12 @@ import { TabbedDialog } from 
'../tabbed-dialog/tabbed-dialog.component';
         NgTemplateOutlet,
         FormsModule,
         CopyDirective
+    ],
+    providers: [
+        {
+            provide: TABBED_DIALOG_ID,
+            useValue: 'edit-provenance-event-selected-index'
+        }
     ]
 })
 export class ProvenanceEventDialog extends TabbedDialog {
@@ -59,7 +65,7 @@ export class ProvenanceEventDialog extends TabbedDialog {
     onlyShowModifiedAttributes = false;
 
     constructor() {
-        super('edit-provenance-event-selected-index');
+        super();
     }
 
     formatDurationValue(duration: number): string {
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/system-diagnostics-dialog/system-diagnostics-dialog.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/system-diagnostics-dialog/system-diagnostics-dialog.component.ts
index f2a08ad0b1..82bb49f904 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/system-diagnostics-dialog/system-diagnostics-dialog.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/system-diagnostics-dialog/system-diagnostics-dialog.component.ts
@@ -30,7 +30,7 @@ import { MatButtonModule } from '@angular/material/button';
 import { reloadSystemDiagnostics } from 
'../../../state/system-diagnostics/system-diagnostics.actions';
 import { isDefinedAndNotNull, NiFiCommon, NifiTooltipDirective, TextTip } from 
'@nifi/shared';
 import { MatProgressBarModule } from '@angular/material/progress-bar';
-import { TabbedDialog } from '../tabbed-dialog/tabbed-dialog.component';
+import { TabbedDialog, TABBED_DIALOG_ID } from 
'../tabbed-dialog/tabbed-dialog.component';
 import { ErrorContextKey } from '../../../state/error';
 import { ContextErrorBanner } from 
'../context-error-banner/context-error-banner.component';
 
@@ -46,7 +46,13 @@ import { ContextErrorBanner } from 
'../context-error-banner/context-error-banner
         ContextErrorBanner
     ],
     templateUrl: './system-diagnostics-dialog.component.html',
-    styleUrls: ['./system-diagnostics-dialog.component.scss']
+    styleUrls: ['./system-diagnostics-dialog.component.scss'],
+    providers: [
+        {
+            provide: TABBED_DIALOG_ID,
+            useValue: 'system-diagnostics-selected-index'
+        }
+    ]
 })
 export class SystemDiagnosticsDialog extends TabbedDialog implements OnInit {
     private store = inject<Store<SystemDiagnosticsState>>(Store);
@@ -58,7 +64,7 @@ export class SystemDiagnosticsDialog extends TabbedDialog 
implements OnInit {
     sortedGarbageCollections: GarbageCollection[] | null = null;
 
     constructor() {
-        super('system-diagnostics-selected-index');
+        super();
     }
 
     ngOnInit(): void {
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tabbed-dialog/tabbed-dialog.component.spec.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tabbed-dialog/tabbed-dialog.component.spec.ts
new file mode 100644
index 0000000000..4cd70982a2
--- /dev/null
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tabbed-dialog/tabbed-dialog.component.spec.ts
@@ -0,0 +1,377 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component } from '@angular/core';
+import { TestBed } from '@angular/core/testing';
+import { MatDialogRef } from '@angular/material/dialog';
+import { Storage } from '@nifi/shared';
+import { TabbedDialog, TABBED_DIALOG_ID } from './tabbed-dialog.component';
+
+// Concrete test implementation of the abstract TabbedDialog class
+@Component({
+    selector: 'test-tabbed-dialog',
+    template: '<div>Test Component</div>',
+    standalone: true
+})
+class TestTabbedDialogComponent extends TabbedDialog {
+    // Expose private properties for testing
+    getDialogId(): string {
+        return (this as any).dialogId;
+    }
+
+    getStorage(): Storage {
+        return (this as any).storage;
+    }
+}
+
+interface SetupOptions {
+    customDialogId?: string;
+    initialStorageValue?: number;
+}
+
+async function setup(options: SetupOptions = {}) {
+    const mockStorage = {
+        getItem: jest.fn(),
+        setItem: jest.fn()
+    };
+
+    const mockDialogRef = {
+        keydownEvents: jest.fn().mockReturnValue({
+            pipe: jest.fn().mockReturnValue({
+                subscribe: jest.fn()
+            })
+        }),
+        close: jest.fn()
+    };
+
+    const providers: any[] = [
+        { provide: Storage, useValue: mockStorage },
+        { provide: MatDialogRef, useValue: mockDialogRef }
+    ];
+
+    // Add custom dialog ID provider if specified
+    if (options.customDialogId !== undefined) {
+        providers.push({
+            provide: TABBED_DIALOG_ID,
+            useValue: options.customDialogId
+        });
+    }
+
+    // Setup initial storage value if specified
+    if (options.initialStorageValue !== undefined) {
+        mockStorage.getItem.mockReturnValue(options.initialStorageValue);
+    }
+
+    await TestBed.configureTestingModule({
+        imports: [TestTabbedDialogComponent],
+        providers
+    }).compileComponents();
+
+    const fixture = TestBed.createComponent(TestTabbedDialogComponent);
+    const component = fixture.componentInstance;
+
+    return {
+        fixture,
+        component,
+        mockStorage,
+        mockDialogRef
+    };
+}
+
+describe('TabbedDialog', () => {
+    beforeEach(() => {
+        jest.clearAllMocks();
+    });
+
+    describe('Dialog ID Configuration', () => {
+        it('should use default dialog ID when no injection token is provided', 
async () => {
+            const { component } = await setup();
+
+            
expect(component.getDialogId()).toBe('tabbed-dialog-selected-index');
+        });
+
+        it('should use injected dialog ID when injection token is provided', 
async () => {
+            const customDialogId = 'custom-test-dialog-id';
+            const { component } = await setup({ customDialogId });
+
+            expect(component.getDialogId()).toBe(customDialogId);
+        });
+
+        it('should use injected dialog ID even when it is an empty string', 
async () => {
+            const { component } = await setup({ customDialogId: '' });
+
+            expect(component.getDialogId()).toBe('');
+        });
+    });
+
+    describe('Storage Integration', () => {
+        it('should retrieve previous selected index from storage using default 
dialog ID', async () => {
+            const { component, mockStorage } = await setup({
+                initialStorageValue: 2
+            });
+
+            
expect(mockStorage.getItem).toHaveBeenCalledWith('tabbed-dialog-selected-index');
+            expect(component.selectedIndex).toBe(2);
+        });
+
+        it('should retrieve previous selected index from storage using custom 
dialog ID', async () => {
+            const customDialogId = 'edit-parameter-context-dialog';
+            const { component, mockStorage } = await setup({
+                customDialogId,
+                initialStorageValue: 3
+            });
+
+            expect(mockStorage.getItem).toHaveBeenCalledWith(customDialogId);
+            expect(component.selectedIndex).toBe(3);
+        });
+
+        it('should use default selectedIndex when storage returns null', async 
() => {
+            const { component, mockStorage } = await setup();
+
+            mockStorage.getItem.mockReturnValue(null);
+
+            expect(component.selectedIndex).toBe(0);
+        });
+
+        it('should use default selectedIndex when storage returns undefined', 
async () => {
+            const { component, mockStorage } = await setup();
+
+            mockStorage.getItem.mockReturnValue(undefined);
+
+            expect(component.selectedIndex).toBe(0);
+        });
+    });
+
+    describe('Tab Change Functionality', () => {
+        it('should save tab index to storage using default dialog ID', async 
() => {
+            const { component, mockStorage } = await setup();
+
+            component.tabChanged(1);
+
+            
expect(mockStorage.setItem).toHaveBeenCalledWith('tabbed-dialog-selected-index',
 1);
+        });
+
+        it('should save tab index to storage using custom dialog ID', async () 
=> {
+            const customDialogId = 'custom-dialog-test';
+            const { component, mockStorage } = await setup({ customDialogId });
+
+            component.tabChanged(2);
+
+            expect(mockStorage.setItem).toHaveBeenCalledWith(customDialogId, 
2);
+        });
+
+        it('should handle zero tab index correctly', async () => {
+            const { component, mockStorage } = await setup();
+
+            component.tabChanged(0);
+
+            
expect(mockStorage.setItem).toHaveBeenCalledWith('tabbed-dialog-selected-index',
 0);
+        });
+
+        it('should handle negative tab index (edge case)', async () => {
+            const { component, mockStorage } = await setup();
+
+            component.tabChanged(-1);
+
+            
expect(mockStorage.setItem).toHaveBeenCalledWith('tabbed-dialog-selected-index',
 -1);
+        });
+    });
+
+    describe('Integration Scenarios', () => {
+        it('should maintain separate storage entries for different dialog 
IDs', async () => {
+            // Test first dialog with custom ID
+            const mockStorage1 = {
+                getItem: jest.fn().mockReturnValue(1),
+                setItem: jest.fn()
+            };
+
+            TestBed.resetTestingModule();
+            await TestBed.configureTestingModule({
+                imports: [TestTabbedDialogComponent],
+                providers: [
+                    { provide: Storage, useValue: mockStorage1 },
+                    {
+                        provide: MatDialogRef,
+                        useValue: {
+                            keydownEvents: jest
+                                .fn()
+                                .mockReturnValue({ pipe: 
jest.fn().mockReturnValue({ subscribe: jest.fn() }) }),
+                            close: jest.fn()
+                        }
+                    },
+                    { provide: TABBED_DIALOG_ID, useValue: 'dialog-1' }
+                ]
+            }).compileComponents();
+
+            const fixture1 = 
TestBed.createComponent(TestTabbedDialogComponent);
+            const component1 = fixture1.componentInstance;
+
+            // Verify first dialog uses its own storage key
+            expect(mockStorage1.getItem).toHaveBeenCalledWith('dialog-1');
+            expect(component1.selectedIndex).toBe(1);
+
+            // Change tab in first dialog
+            component1.tabChanged(3);
+            expect(mockStorage1.setItem).toHaveBeenCalledWith('dialog-1', 3);
+
+            // Test second dialog with different custom ID
+            const mockStorage2 = {
+                getItem: jest.fn().mockReturnValue(2),
+                setItem: jest.fn()
+            };
+
+            TestBed.resetTestingModule();
+            await TestBed.configureTestingModule({
+                imports: [TestTabbedDialogComponent],
+                providers: [
+                    { provide: Storage, useValue: mockStorage2 },
+                    {
+                        provide: MatDialogRef,
+                        useValue: {
+                            keydownEvents: jest
+                                .fn()
+                                .mockReturnValue({ pipe: 
jest.fn().mockReturnValue({ subscribe: jest.fn() }) }),
+                            close: jest.fn()
+                        }
+                    },
+                    { provide: TABBED_DIALOG_ID, useValue: 'dialog-2' }
+                ]
+            }).compileComponents();
+
+            const fixture2 = 
TestBed.createComponent(TestTabbedDialogComponent);
+            const component2 = fixture2.componentInstance;
+
+            // Verify second dialog uses its own storage key
+            expect(mockStorage2.getItem).toHaveBeenCalledWith('dialog-2');
+            expect(component2.selectedIndex).toBe(2);
+
+            // Change tab in second dialog
+            component2.tabChanged(4);
+            expect(mockStorage2.setItem).toHaveBeenCalledWith('dialog-2', 4);
+        });
+
+        it('should work correctly when switching from default to custom dialog 
ID', async () => {
+            // Test default dialog ID
+            const mockStorage1 = {
+                getItem: jest.fn().mockReturnValue(1),
+                setItem: jest.fn()
+            };
+
+            TestBed.resetTestingModule();
+            await TestBed.configureTestingModule({
+                imports: [TestTabbedDialogComponent],
+                providers: [
+                    { provide: Storage, useValue: mockStorage1 },
+                    {
+                        provide: MatDialogRef,
+                        useValue: {
+                            keydownEvents: jest
+                                .fn()
+                                .mockReturnValue({ pipe: 
jest.fn().mockReturnValue({ subscribe: jest.fn() }) }),
+                            close: jest.fn()
+                        }
+                    }
+                ]
+            }).compileComponents();
+
+            const fixture1 = 
TestBed.createComponent(TestTabbedDialogComponent);
+
+            
expect(mockStorage1.getItem).toHaveBeenCalledWith('tabbed-dialog-selected-index');
+
+            // Test custom dialog ID
+            const mockStorage2 = {
+                getItem: jest.fn().mockReturnValue(5),
+                setItem: jest.fn()
+            };
+
+            TestBed.resetTestingModule();
+            await TestBed.configureTestingModule({
+                imports: [TestTabbedDialogComponent],
+                providers: [
+                    { provide: Storage, useValue: mockStorage2 },
+                    {
+                        provide: MatDialogRef,
+                        useValue: {
+                            keydownEvents: jest
+                                .fn()
+                                .mockReturnValue({ pipe: 
jest.fn().mockReturnValue({ subscribe: jest.fn() }) }),
+                            close: jest.fn()
+                        }
+                    },
+                    { provide: TABBED_DIALOG_ID, useValue: 'custom-id' }
+                ]
+            }).compileComponents();
+
+            const fixture2 = 
TestBed.createComponent(TestTabbedDialogComponent);
+            const component2 = fixture2.componentInstance;
+
+            expect(mockStorage2.getItem).toHaveBeenCalledWith('custom-id');
+            expect(component2.selectedIndex).toBe(5);
+        });
+    });
+
+    describe('Error Handling', () => {
+        it('should handle storage errors gracefully during initialization', 
async () => {
+            const mockStorage = {
+                getItem: jest.fn().mockImplementation(() => {
+                    throw new Error('storage error');
+                }),
+                setItem: jest.fn()
+            };
+
+            const mockDialogRef = {
+                keydownEvents: jest.fn().mockReturnValue({
+                    pipe: jest.fn().mockReturnValue({
+                        subscribe: jest.fn()
+                    })
+                }),
+                close: jest.fn()
+            };
+
+            // Should not throw an error during component creation
+            expect(() => {
+                TestBed.resetTestingModule();
+                TestBed.configureTestingModule({
+                    imports: [TestTabbedDialogComponent],
+                    providers: [
+                        { provide: Storage, useValue: mockStorage },
+                        { provide: MatDialogRef, useValue: mockDialogRef }
+                    ]
+                }).compileComponents();
+
+                const fixture = 
TestBed.createComponent(TestTabbedDialogComponent);
+                const component = fixture.componentInstance;
+
+                // Component should still be created with default selectedIndex
+                expect(component.selectedIndex).toBe(0);
+            }).not.toThrow();
+        });
+
+        it('should handle storage errors gracefully during tabChanged', async 
() => {
+            const { component, mockStorage } = await setup();
+
+            mockStorage.setItem.mockImplementation(() => {
+                throw new Error('storage error');
+            });
+
+            // Should not throw an error when calling tabChanged
+            expect(() => {
+                component.tabChanged(1);
+            }).not.toThrow();
+        });
+    });
+});
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tabbed-dialog/tabbed-dialog.component.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tabbed-dialog/tabbed-dialog.component.ts
index 4d567411a9..c553e3e731 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tabbed-dialog/tabbed-dialog.component.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/tabbed-dialog/tabbed-dialog.component.ts
@@ -15,26 +15,41 @@
  * limitations under the License.
  */
 
-import { inject } from '@angular/core';
+import { inject, InjectionToken } from '@angular/core';
 import { Storage, CloseOnEscapeDialog } from '@nifi/shared';
 
+export const TABBED_DIALOG_ID = new InjectionToken<string>('TABBED_DIALOG_ID');
+
 export abstract class TabbedDialog extends CloseOnEscapeDialog {
     private storage: Storage = inject(Storage);
-    private dialogId: string;
+    private dialogId: string = 'tabbed-dialog-selected-index';
 
     selectedIndex = 0;
 
-    constructor(dialogId: string) {
+    constructor() {
         super();
-        this.dialogId = dialogId;
 
-        const previousSelectedIndex = 
this.storage.getItem<number>(this.dialogId);
-        if (previousSelectedIndex != null) {
-            this.selectedIndex = previousSelectedIndex;
+        // Inject the dialog ID if provided, otherwise use default
+        const injectedDialogId = inject(TABBED_DIALOG_ID, { optional: true });
+        if (injectedDialogId !== null && injectedDialogId !== undefined) {
+            this.dialogId = injectedDialogId;
+        }
+
+        try {
+            const previousSelectedIndex = 
this.storage.getItem<number>(this.dialogId);
+            if (previousSelectedIndex != null) {
+                this.selectedIndex = previousSelectedIndex;
+            }
+        } catch (error) {
+            // Gracefully handle localStorage errors - use default 
selectedIndex
         }
     }
 
     tabChanged(selectedTabIndex: number): void {
-        this.storage.setItem<number>(this.dialogId, selectedTabIndex);
+        try {
+            this.storage.setItem<number>(this.dialogId, selectedTabIndex);
+        } catch (error) {
+            // Gracefully handle localStorage errors
+        }
     }
 }


Reply via email to