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
+ }
}
}