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 20ec8064c9 [NIFI-13034] Change Component Version (#8653)
20ec8064c9 is described below

commit 20ec8064c9f6938c3a0740153f0728adf84b137c
Author: Rob Fellows <[email protected]>
AuthorDate: Wed Apr 17 13:36:47 2024 -0400

    [NIFI-13034] Change Component Version (#8653)
    
    * [NIFI-13034] - Change Processor version
    
    * Change controller service version
    
    * Change version of reporting task and flow analysis rule
    
    * add missing license header
    
    * fix for updating parameter context when in clustered mode.
    
    * review feedback - collapse 2 actions for opening change component version 
into 1
    
    * update DocumentedType comparison logic
    
    This closes #8653
---
 .../service/canvas-context-menu.service.ts         | 29 +++++---
 .../flow-designer/service/canvas-utils.service.ts  | 27 +++++++
 .../controller-services.actions.ts                 |  6 ++
 .../controller-services.effects.ts                 | 58 ++++++++++++++-
 .../pages/flow-designer/state/flow/flow.actions.ts |  6 ++
 .../pages/flow-designer/state/flow/flow.effects.ts | 57 +++++++++++++-
 .../controller-services.component.html             |  1 +
 .../controller-services.component.ts               | 15 ++++
 .../edit-parameter-context.component.ts            |  1 +
 .../flow-analysis-rules.actions.ts                 | 14 +++-
 .../flow-analysis-rules.effects.ts                 | 59 ++++++++++++++-
 .../management-controller-services.actions.ts      |  6 ++
 .../management-controller-services.effects.ts      | 58 ++++++++++++++-
 .../reporting-tasks/reporting-tasks.actions.ts     | 12 ++-
 .../reporting-tasks/reporting-tasks.effects.ts     | 57 +++++++++++++-
 .../flow-analysis-rule-table.component.html        |  5 +-
 .../flow-analysis-rule-table.component.ts          |  6 ++
 .../flow-analysis-rules.component.html             |  1 +
 .../flow-analysis-rules.component.ts               | 15 ++++
 .../management-controller-services.component.html  |  1 +
 .../management-controller-services.component.ts    | 15 ++++
 .../reporting-task-table.component.html            |  5 +-
 .../reporting-task-table.component.ts              |  5 ++
 .../reporting-tasks/reporting-tasks.component.html |  1 +
 .../reporting-tasks/reporting-tasks.component.ts   | 21 +++++-
 .../src/app/service/extension-types.service.ts     | 36 +++++++++
 .../src/main/nifi/src/app/state/shared/index.ts    | 13 ++++
 .../change-component-version-dialog.html           | 84 +++++++++++++++++++++
 .../change-component-version-dialog.scss           | 16 ++++
 .../change-component-version-dialog.spec.ts        | 86 ++++++++++++++++++++++
 .../change-component-version-dialog.ts             | 85 +++++++++++++++++++++
 .../controller-service-table.component.html        |  5 +-
 .../controller-service-table.component.ts          |  6 ++
 .../extension-creation.component.ts                | 20 ++++-
 34 files changed, 799 insertions(+), 33 deletions(-)

diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.service.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.service.ts
index 3551dd1841..ec00ba526d 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.service.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.service.ts
@@ -22,14 +22,16 @@ import { CanvasState } from '../state';
 import {
     centerSelectedComponents,
     deleteComponents,
+    downloadFlow,
     enterProcessGroup,
     getParameterContextsAndOpenGroupComponentsDialog,
     goToRemoteProcessGroup,
     leaveProcessGroup,
     moveComponents,
+    moveToFront,
+    navigateToAdvancedProcessorUi,
     navigateToComponent,
     navigateToControllerServicesForProcessGroup,
-    navigateToAdvancedProcessorUi,
     navigateToEditComponent,
     navigateToEditCurrentProcessGroup,
     navigateToManageComponentPolicies,
@@ -37,6 +39,7 @@ import {
     navigateToProvenanceForComponent,
     navigateToQueueListing,
     navigateToViewStatusHistoryForComponent,
+    openChangeProcessorVersionDialog,
     openChangeVersionDialogRequest,
     openCommitLocalChangesDialogRequest,
     openForceCommitLocalChangesDialogRequest,
@@ -51,9 +54,7 @@ import {
     startCurrentProcessGroup,
     stopComponents,
     stopCurrentProcessGroup,
-    stopVersionControlRequest,
-    downloadFlow,
-    moveToFront
+    stopVersionControlRequest
 } from '../state/flow/flow.actions';
 import { ComponentType } from '../../../state/shared';
 import {
@@ -970,14 +971,24 @@ export class CanvasContextMenu implements 
ContextMenuDefinitionProvider {
                 }
             },
             {
-                condition: (selection: any) => {
-                    // TODO - canChangeProcessorVersion
-                    return false;
+                condition: (selection: d3.Selection<any, any, any, any>) => {
+                    return 
this.canvasUtils.canChangeProcessorVersion(selection);
                 },
                 clazz: 'fa fa-exchange',
                 text: 'Change version',
-                action: () => {
-                    // TODO - changeVersion
+                action: (selection: d3.Selection<any, any, any, any>) => {
+                    const data = selection.datum();
+                    this.store.dispatch(
+                        openChangeProcessorVersionDialog({
+                            request: {
+                                id: data.component.id,
+                                uri: data.uri,
+                                revision: data.revision,
+                                type: data.component.type,
+                                bundle: data.component.bundle
+                            }
+                        })
+                    );
                 }
             },
             {
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts
index 1538a6c1d2..e57e390991 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.service.ts
@@ -1712,6 +1712,33 @@ export class CanvasUtils {
         return false;
     }
 
+    /**
+     * Determines whether the current selection is a processor with more than 
one version.
+     *
+     * @argument {d3.Selection} selection      The selection
+     * @returns {boolean}
+     */
+    public canChangeProcessorVersion(selection: d3.Selection<any, any, any, 
any>): boolean {
+        if (selection.size() !== 1) {
+            return false;
+        }
+
+        if (!this.canRead(selection) || !this.canModify(selection)) {
+            return false;
+        }
+
+        if (this.isProcessor(selection)) {
+            const data = selection.datum();
+            const supportsModification = !(
+                data.status.aggregateSnapshot.runStatus === 'Running' ||
+                data.status.aggregateSnapshot.activeThreadCount > 0
+            );
+
+            return supportsModification && 
data.component.multipleVersionsAvailable;
+        }
+        return false;
+    }
+
     public canMoveToFront(selection: d3.Selection<any, any, any, any>): 
boolean {
         // ensure the correct number of components are selected
         if (selection.size() !== 1) {
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.actions.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.actions.ts
index 64b75b193d..840e5a6845 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.actions.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.actions.ts
@@ -30,6 +30,7 @@ import {
     CreateControllerServiceRequest,
     DisableControllerServiceDialogRequest,
     EditControllerServiceDialogRequest,
+    FetchComponentVersionsRequest,
     SetEnableControllerServiceDialogRequest
 } from '../../../../state/shared';
 
@@ -121,3 +122,8 @@ export const selectControllerService = createAction(
     '[Controller Services] Select Controller Service',
     props<{ request: SelectControllerServiceRequest }>()
 );
+
+export const openChangeControllerServiceVersionDialog = createAction(
+    `[Controller Services] Open Change Controller Service Version Dialog`,
+    props<{ request: FetchComponentVersionsRequest }>()
+);
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts
index 771a616164..70d4cdd956 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/controller-services/controller-services.effects.ts
@@ -30,6 +30,7 @@ import { EditControllerService } from 
'../../../../ui/common/controller-service/
 import {
     ComponentType,
     ControllerServiceReferencingComponent,
+    OpenChangeComponentVersionDialogRequest,
     EditControllerServiceDialogRequest,
     UpdateControllerServiceRequest
 } from '../../../../state/shared';
@@ -49,6 +50,8 @@ import { ErrorHelper } from 
'../../../../service/error-helper.service';
 import { HttpErrorResponse } from '@angular/common/http';
 import { ParameterHelperService } from 
'../../service/parameter-helper.service';
 import { LARGE_DIALOG, SMALL_DIALOG, XL_DIALOG } from '../../../../index';
+import { ExtensionTypesService } from 
'../../../../service/extension-types.service';
+import { ChangeComponentVersionDialog } from 
'../../../../ui/common/change-component-version-dialog/change-component-version-dialog';
 
 @Injectable()
 export class ControllerServicesEffects {
@@ -61,7 +64,8 @@ export class ControllerServicesEffects {
         private dialog: MatDialog,
         private router: Router,
         private propertyTableHelperService: PropertyTableHelperService,
-        private parameterHelperService: ParameterHelperService
+        private parameterHelperService: ParameterHelperService,
+        private extensionTypesService: ExtensionTypesService
     ) {}
 
     loadControllerServices$ = createEffect(() =>
@@ -551,6 +555,58 @@ export class ControllerServicesEffects {
         { dispatch: false }
     );
 
+    openChangeControllerServiceVersionDialog$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                
ofType(ControllerServicesActions.openChangeControllerServiceVersionDialog),
+                map((action) => action.request),
+                switchMap((request) =>
+                    from(
+                        
this.extensionTypesService.getControllerServiceVersionsForType(request.type, 
request.bundle)
+                    ).pipe(
+                        map(
+                            (response) =>
+                                ({
+                                    fetchRequest: request,
+                                    componentVersions: 
response.controllerServiceTypes
+                                }) as OpenChangeComponentVersionDialogRequest
+                        ),
+                        tap({
+                            error: (errorResponse: HttpErrorResponse) => {
+                                
this.store.dispatch(ErrorActions.snackBarError({ error: errorResponse.error }));
+                            }
+                        })
+                    )
+                ),
+                tap((request) => {
+                    const dialogRequest = 
this.dialog.open(ChangeComponentVersionDialog, {
+                        ...LARGE_DIALOG,
+                        data: request
+                    });
+
+                    
dialogRequest.componentInstance.changeVersion.pipe(take(1)).subscribe((newVersion)
 => {
+                        this.store.dispatch(
+                            
ControllerServicesActions.configureControllerService({
+                                request: {
+                                    id: request.fetchRequest.id,
+                                    uri: request.fetchRequest.uri,
+                                    payload: {
+                                        component: {
+                                            bundle: newVersion.bundle,
+                                            id: request.fetchRequest.id
+                                        },
+                                        revision: request.fetchRequest.revision
+                                    }
+                                }
+                            })
+                        );
+                        dialogRequest.close();
+                    });
+                })
+            ),
+        { dispatch: false }
+    );
+
     private getRouteForReference(reference: 
ControllerServiceReferencingComponent): string[] {
         if (reference.referenceType == 'ControllerService') {
             if (reference.groupId == null) {
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.actions.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.actions.ts
index ee58813aa7..030d9a01fc 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.actions.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.actions.ts
@@ -94,6 +94,7 @@ import {
     VersionControlInformationEntity
 } from './index';
 import { StatusHistoryRequest } from '../../../../state/status-history';
+import { FetchComponentVersionsRequest } from '../../../../state/shared';
 
 const CANVAS_PREFIX = '[Canvas]';
 
@@ -769,3 +770,8 @@ export const downloadFlow = createAction(
 );
 
 export const moveToFront = createAction(`${CANVAS_PREFIX} Move To Front`, 
props<{ request: MoveToFrontRequest }>());
+
+export const openChangeProcessorVersionDialog = createAction(
+    `${CANVAS_PREFIX} Open Change Processor Version Dialog`,
+    props<{ request: FetchComponentVersionsRequest }>()
+);
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
index 6c43c18234..4e6f79db87 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
@@ -80,6 +80,7 @@ import {
     BucketEntity,
     ComponentType,
     isDefinedAndNotNull,
+    OpenChangeComponentVersionDialogRequest,
     RegistryClientEntity,
     VersionedFlowEntity,
     VersionedFlowSnapshotMetadataEntity
@@ -116,6 +117,8 @@ import { ChangeVersionDialog } from 
'../../ui/canvas/items/flow/change-version-d
 import { ChangeVersionProgressDialog } from 
'../../ui/canvas/items/flow/change-version-progress-dialog/change-version-progress-dialog';
 import { LocalChangesDialog } from 
'../../ui/canvas/items/flow/local-changes-dialog/local-changes-dialog';
 import { ClusterConnectionService } from 
'../../../../service/cluster-connection.service';
+import { ExtensionTypesService } from 
'../../../../service/extension-types.service';
+import { ChangeComponentVersionDialog } from 
'../../../../ui/common/change-component-version-dialog/change-component-version-dialog';
 
 @Injectable()
 export class FlowEffects {
@@ -134,7 +137,8 @@ export class FlowEffects {
         private router: Router,
         private dialog: MatDialog,
         private propertyTableHelperService: PropertyTableHelperService,
-        private parameterHelperService: ParameterHelperService
+        private parameterHelperService: ParameterHelperService,
+        private extensionTypesService: ExtensionTypesService
     ) {}
 
     reloadFlow$ = createEffect(() =>
@@ -3199,4 +3203,55 @@ export class FlowEffects {
             })
         )
     );
+
+    openChangeProcessorVersionDialog$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(FlowActions.openChangeProcessorVersionDialog),
+                map((action) => action.request),
+                switchMap((request) =>
+                    
from(this.extensionTypesService.getProcessorVersionsForType(request.type, 
request.bundle)).pipe(
+                        map(
+                            (response) =>
+                                ({
+                                    fetchRequest: request,
+                                    componentVersions: response.processorTypes
+                                }) as OpenChangeComponentVersionDialogRequest
+                        ),
+                        tap({
+                            error: (errorResponse: HttpErrorResponse) => {
+                                
this.store.dispatch(FlowActions.flowSnackbarError({ error: errorResponse.error 
}));
+                            }
+                        })
+                    )
+                ),
+                tap((request) => {
+                    const dialogRequest = 
this.dialog.open(ChangeComponentVersionDialog, {
+                        ...LARGE_DIALOG,
+                        data: request
+                    });
+
+                    
dialogRequest.componentInstance.changeVersion.pipe(take(1)).subscribe((newVersion)
 => {
+                        this.store.dispatch(
+                            FlowActions.updateProcessor({
+                                request: {
+                                    id: request.fetchRequest.id,
+                                    uri: request.fetchRequest.uri,
+                                    type: ComponentType.Processor,
+                                    payload: {
+                                        component: {
+                                            bundle: newVersion.bundle,
+                                            id: request.fetchRequest.id
+                                        },
+                                        revision: request.fetchRequest.revision
+                                    }
+                                }
+                            })
+                        );
+                        dialogRequest.close();
+                    });
+                })
+            ),
+        { dispatch: false }
+    );
 }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.html
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.html
index 357be8ef22..854cbe3888 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.html
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.html
@@ -53,6 +53,7 @@
                                 
(enableControllerService)="enableControllerService($event)"
                                 
(disableControllerService)="disableControllerService($event)"
                                 
(viewStateControllerService)="viewStateControllerService($event)"
+                                
(changeControllerServiceVersion)="changeControllerServiceVersion($event)"
                                 
(deleteControllerService)="deleteControllerService($event)"></controller-service-table>
                         </div>
                     }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.ts
index 1b9df8e42c..1d233e0f0e 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/controller-service/controller-services.component.ts
@@ -31,6 +31,7 @@ import {
     loadControllerServices,
     navigateToAdvancedServiceUi,
     navigateToEditService,
+    openChangeControllerServiceVersionDialog,
     openConfigureControllerServiceDialog,
     openDisableControllerServiceDialog,
     openEnableControllerServiceDialog,
@@ -228,6 +229,20 @@ export class ControllerServices implements OnInit, 
OnDestroy {
         );
     }
 
+    changeControllerServiceVersion(entity: ControllerServiceEntity): void {
+        this.store.dispatch(
+            openChangeControllerServiceVersionDialog({
+                request: {
+                    id: entity.id,
+                    bundle: entity.component.bundle,
+                    uri: entity.uri,
+                    type: entity.component.type,
+                    revision: entity.revision
+                }
+            })
+        );
+    }
+
     deleteControllerService(entity: ControllerServiceEntity): void {
         this.store.dispatch(
             promptControllerServiceDeletion({
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.ts
index 068c91992f..3aac271a45 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/parameter-contexts/ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component.ts
@@ -156,6 +156,7 @@ export class EditParameterContext {
             const payload: any = {
                 revision: this.client.getRevision(pc),
                 disconnectedNodeAcknowledged: 
this.clusterConnectionService.isDisconnectionAcknowledged(),
+                id: pc.id,
                 component: {
                     id: pc.id,
                     name: this.editParameterContextForm.get('name')?.value,
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.actions.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.actions.ts
index acae3493f3..11c9dc2778 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.actions.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.actions.ts
@@ -23,14 +23,15 @@ import {
     CreateFlowAnalysisRuleSuccess,
     DeleteFlowAnalysisRuleRequest,
     DeleteFlowAnalysisRuleSuccess,
-    EditFlowAnalysisRuleDialogRequest,
-    LoadFlowAnalysisRulesResponse,
-    SelectFlowAnalysisRuleRequest,
     DisableFlowAnalysisRuleRequest,
+    DisableFlowAnalysisRuleSuccess,
+    EditFlowAnalysisRuleDialogRequest,
     EnableFlowAnalysisRuleRequest,
     EnableFlowAnalysisRuleSuccess,
-    DisableFlowAnalysisRuleSuccess
+    LoadFlowAnalysisRulesResponse,
+    SelectFlowAnalysisRuleRequest
 } from './index';
+import { FetchComponentVersionsRequest } from '../../../../state/shared';
 
 export const resetFlowAnalysisRulesState = createAction('[Flow Analysis Rules] 
Reset Flow Analysis Rules State');
 
@@ -126,3 +127,8 @@ export const selectFlowAnalysisRule = createAction(
     '[Flow Analysis Rules] Select Flow Analysis Rule',
     props<{ request: SelectFlowAnalysisRuleRequest }>()
 );
+
+export const openChangeFlowAnalysisRuleVersionDialog = createAction(
+    `[Flow Analysis Rules] Open Change Flow Analysis Rule Version Dialog`,
+    props<{ request: FetchComponentVersionsRequest }>()
+);
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.effects.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.effects.ts
index 02af6d4809..141887b487 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.effects.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/flow-analysis-rules/flow-analysis-rules.effects.ts
@@ -29,7 +29,7 @@ import { ManagementControllerServiceService } from 
'../../service/management-con
 import { CreateFlowAnalysisRule } from 
'../../ui/flow-analysis-rules/create-flow-analysis-rule/create-flow-analysis-rule.component';
 import { Router } from '@angular/router';
 import { selectSaving } from 
'../management-controller-services/management-controller-services.selectors';
-import { UpdateControllerServiceRequest } from '../../../../state/shared';
+import { OpenChangeComponentVersionDialogRequest, 
UpdateControllerServiceRequest } from '../../../../state/shared';
 import { EditFlowAnalysisRule } from 
'../../ui/flow-analysis-rules/edit-flow-analysis-rule/edit-flow-analysis-rule.component';
 import { CreateFlowAnalysisRuleSuccess, EditFlowAnalysisRuleDialogRequest } 
from './index';
 import { PropertyTableHelperService } from 
'../../../../service/property-table-helper.service';
@@ -38,6 +38,8 @@ import { ErrorHelper } from 
'../../../../service/error-helper.service';
 import { selectStatus } from './flow-analysis-rules.selectors';
 import { HttpErrorResponse } from '@angular/common/http';
 import { LARGE_DIALOG, SMALL_DIALOG } from '../../../../index';
+import { ChangeComponentVersionDialog } from 
'../../../../ui/common/change-component-version-dialog/change-component-version-dialog';
+import { ExtensionTypesService } from 
'../../../../service/extension-types.service';
 
 @Injectable()
 export class FlowAnalysisRulesEffects {
@@ -49,7 +51,8 @@ export class FlowAnalysisRulesEffects {
         private errorHelper: ErrorHelper,
         private dialog: MatDialog,
         private router: Router,
-        private propertyTableHelperService: PropertyTableHelperService
+        private propertyTableHelperService: PropertyTableHelperService,
+        private extensionTypesService: ExtensionTypesService
     ) {}
 
     loadFlowAnalysisRule$ = createEffect(() =>
@@ -464,4 +467,56 @@ export class FlowAnalysisRulesEffects {
             ),
         { dispatch: false }
     );
+
+    openChangeFlowAnalysisRuleVersionDialog$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                
ofType(FlowAnalysisRuleActions.openChangeFlowAnalysisRuleVersionDialog),
+                map((action) => action.request),
+                switchMap((request) =>
+                    from(
+                        
this.extensionTypesService.getFlowAnalysisRuleVersionsForType(request.type, 
request.bundle)
+                    ).pipe(
+                        map(
+                            (response) =>
+                                ({
+                                    fetchRequest: request,
+                                    componentVersions: 
response.flowAnalysisRuleTypes
+                                }) as OpenChangeComponentVersionDialogRequest
+                        ),
+                        tap({
+                            error: (errorResponse: HttpErrorResponse) => {
+                                
this.store.dispatch(ErrorActions.snackBarError({ error: errorResponse.error }));
+                            }
+                        })
+                    )
+                ),
+                tap((request) => {
+                    const dialogRequest = 
this.dialog.open(ChangeComponentVersionDialog, {
+                        ...LARGE_DIALOG,
+                        data: request
+                    });
+
+                    
dialogRequest.componentInstance.changeVersion.pipe(take(1)).subscribe((newVersion)
 => {
+                        this.store.dispatch(
+                            FlowAnalysisRuleActions.configureFlowAnalysisRule({
+                                request: {
+                                    id: request.fetchRequest.id,
+                                    uri: request.fetchRequest.uri,
+                                    payload: {
+                                        component: {
+                                            bundle: newVersion.bundle,
+                                            id: request.fetchRequest.id
+                                        },
+                                        revision: request.fetchRequest.revision
+                                    }
+                                }
+                            })
+                        );
+                        dialogRequest.close();
+                    });
+                })
+            ),
+        { dispatch: false }
+    );
 }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.actions.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.actions.ts
index 75a2d20d43..42f1958e6e 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.actions.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.actions.ts
@@ -29,6 +29,7 @@ import {
     CreateControllerServiceRequest,
     DisableControllerServiceDialogRequest,
     EditControllerServiceDialogRequest,
+    FetchComponentVersionsRequest,
     SetEnableControllerServiceDialogRequest
 } from '../../../../state/shared';
 
@@ -128,3 +129,8 @@ export const selectControllerService = createAction(
     '[Management Controller Services] Select Controller Service',
     props<{ request: SelectControllerServiceRequest }>()
 );
+
+export const openChangeMgtControllerServiceVersionDialog = createAction(
+    `[Management Controller Services] Open Change Management Controller 
Service Version Dialog`,
+    props<{ request: FetchComponentVersionsRequest }>()
+);
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.ts
index eb44d76f85..a784fb7aa5 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.ts
@@ -33,6 +33,7 @@ import {
     ComponentType,
     ControllerServiceReferencingComponent,
     EditControllerServiceDialogRequest,
+    OpenChangeComponentVersionDialogRequest,
     UpdateControllerServiceRequest
 } from '../../../../state/shared';
 import { Router } from '@angular/router';
@@ -43,6 +44,8 @@ import { PropertyTableHelperService } from 
'../../../../service/property-table-h
 import { HttpErrorResponse } from '@angular/common/http';
 import { ErrorHelper } from '../../../../service/error-helper.service';
 import { LARGE_DIALOG, SMALL_DIALOG, XL_DIALOG } from '../../../../index';
+import { ChangeComponentVersionDialog } from 
'../../../../ui/common/change-component-version-dialog/change-component-version-dialog';
+import { ExtensionTypesService } from 
'../../../../service/extension-types.service';
 
 @Injectable()
 export class ManagementControllerServicesEffects {
@@ -54,7 +57,8 @@ export class ManagementControllerServicesEffects {
         private errorHelper: ErrorHelper,
         private dialog: MatDialog,
         private router: Router,
-        private propertyTableHelperService: PropertyTableHelperService
+        private propertyTableHelperService: PropertyTableHelperService,
+        private extensionTypesService: ExtensionTypesService
     ) {}
 
     loadManagementControllerServices$ = createEffect(() =>
@@ -507,6 +511,58 @@ export class ManagementControllerServicesEffects {
         { dispatch: false }
     );
 
+    openChangeMgtControllerServiceVersionDialog$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                
ofType(ManagementControllerServicesActions.openChangeMgtControllerServiceVersionDialog),
+                map((action) => action.request),
+                switchMap((request) =>
+                    from(
+                        
this.extensionTypesService.getControllerServiceVersionsForType(request.type, 
request.bundle)
+                    ).pipe(
+                        map(
+                            (response) =>
+                                ({
+                                    fetchRequest: request,
+                                    componentVersions: 
response.controllerServiceTypes
+                                }) as OpenChangeComponentVersionDialogRequest
+                        ),
+                        tap({
+                            error: (errorResponse: HttpErrorResponse) => {
+                                
this.store.dispatch(ErrorActions.snackBarError({ error: errorResponse.error }));
+                            }
+                        })
+                    )
+                ),
+                tap((request) => {
+                    const dialogRequest = 
this.dialog.open(ChangeComponentVersionDialog, {
+                        ...LARGE_DIALOG,
+                        data: request
+                    });
+
+                    
dialogRequest.componentInstance.changeVersion.pipe(take(1)).subscribe((newVersion)
 => {
+                        this.store.dispatch(
+                            
ManagementControllerServicesActions.configureControllerService({
+                                request: {
+                                    id: request.fetchRequest.id,
+                                    uri: request.fetchRequest.uri,
+                                    payload: {
+                                        component: {
+                                            bundle: newVersion.bundle,
+                                            id: request.fetchRequest.id
+                                        },
+                                        revision: request.fetchRequest.revision
+                                    }
+                                }
+                            })
+                        );
+                        dialogRequest.close();
+                    });
+                })
+            ),
+        { dispatch: false }
+    );
+
     private getRouteForReference(reference: 
ControllerServiceReferencingComponent): string[] {
         if (reference.referenceType == 'ControllerService') {
             if (reference.groupId == null) {
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.actions.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.actions.ts
index 993f05fab1..88cb5c7493 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.actions.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.actions.ts
@@ -17,6 +17,8 @@
 
 import { createAction, props } from '@ngrx/store';
 import {
+    ConfigureReportingTaskRequest,
+    ConfigureReportingTaskSuccess,
     CreateReportingTaskRequest,
     CreateReportingTaskSuccess,
     DeleteReportingTaskRequest,
@@ -27,10 +29,9 @@ import {
     StartReportingTaskRequest,
     StartReportingTaskSuccess,
     StopReportingTaskRequest,
-    StopReportingTaskSuccess,
-    ConfigureReportingTaskRequest,
-    ConfigureReportingTaskSuccess
+    StopReportingTaskSuccess
 } from './index';
+import { FetchComponentVersionsRequest } from '../../../../state/shared';
 
 export const resetReportingTasksState = createAction('[Reporting Tasks] Reset 
Reporting Tasks State');
 
@@ -127,3 +128,8 @@ export const selectReportingTask = createAction(
     '[Reporting Tasks] Select Reporting Task',
     props<{ request: SelectReportingTaskRequest }>()
 );
+
+export const openChangeReportingTaskVersionDialog = createAction(
+    `[Reporting Tasks] Open Change Reporting Task Version Dialog`,
+    props<{ request: FetchComponentVersionsRequest }>()
+);
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.effects.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.effects.ts
index 5a3d8fade4..8084446609 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.effects.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/reporting-tasks/reporting-tasks.effects.ts
@@ -28,7 +28,7 @@ import { ReportingTaskService } from 
'../../service/reporting-task.service';
 import { CreateReportingTask } from 
'../../ui/reporting-tasks/create-reporting-task/create-reporting-task.component';
 import { Router } from '@angular/router';
 import { selectSaving } from 
'../management-controller-services/management-controller-services.selectors';
-import { UpdateControllerServiceRequest } from '../../../../state/shared';
+import { OpenChangeComponentVersionDialogRequest, 
UpdateControllerServiceRequest } from '../../../../state/shared';
 import { EditReportingTask } from 
'../../ui/reporting-tasks/edit-reporting-task/edit-reporting-task.component';
 import { CreateReportingTaskSuccess, EditReportingTaskDialogRequest } from 
'./index';
 import { ManagementControllerServiceService } from 
'../../service/management-controller-service.service';
@@ -38,6 +38,8 @@ import { ErrorHelper } from 
'../../../../service/error-helper.service';
 import { selectStatus } from './reporting-tasks.selectors';
 import { HttpErrorResponse } from '@angular/common/http';
 import { LARGE_DIALOG, SMALL_DIALOG } from '../../../../index';
+import { ChangeComponentVersionDialog } from 
'../../../../ui/common/change-component-version-dialog/change-component-version-dialog';
+import { ExtensionTypesService } from 
'../../../../service/extension-types.service';
 
 @Injectable()
 export class ReportingTasksEffects {
@@ -49,7 +51,8 @@ export class ReportingTasksEffects {
         private errorHelper: ErrorHelper,
         private dialog: MatDialog,
         private router: Router,
-        private propertyTableHelperService: PropertyTableHelperService
+        private propertyTableHelperService: PropertyTableHelperService,
+        private extensionTypesService: ExtensionTypesService
     ) {}
 
     loadReportingTasks$ = createEffect(() =>
@@ -444,4 +447,54 @@ export class ReportingTasksEffects {
             ),
         { dispatch: false }
     );
+
+    openChangeReportingTaskVersionDialog$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                
ofType(ReportingTaskActions.openChangeReportingTaskVersionDialog),
+                map((action) => action.request),
+                switchMap((request) =>
+                    
from(this.extensionTypesService.getReportingTaskVersionsForType(request.type, 
request.bundle)).pipe(
+                        map(
+                            (response) =>
+                                ({
+                                    fetchRequest: request,
+                                    componentVersions: 
response.reportingTaskTypes
+                                }) as OpenChangeComponentVersionDialogRequest
+                        ),
+                        tap({
+                            error: (errorResponse: HttpErrorResponse) => {
+                                
this.store.dispatch(ErrorActions.snackBarError({ error: errorResponse.error }));
+                            }
+                        })
+                    )
+                ),
+                tap((request) => {
+                    const dialogRequest = 
this.dialog.open(ChangeComponentVersionDialog, {
+                        ...LARGE_DIALOG,
+                        data: request
+                    });
+
+                    
dialogRequest.componentInstance.changeVersion.pipe(take(1)).subscribe((newVersion)
 => {
+                        this.store.dispatch(
+                            ReportingTaskActions.configureReportingTask({
+                                request: {
+                                    id: request.fetchRequest.id,
+                                    uri: request.fetchRequest.uri,
+                                    payload: {
+                                        component: {
+                                            bundle: newVersion.bundle,
+                                            id: request.fetchRequest.id
+                                        },
+                                        revision: request.fetchRequest.revision
+                                    }
+                                }
+                            })
+                        );
+                        dialogRequest.close();
+                    });
+                })
+            ),
+        { dispatch: false }
+    );
 }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html
index 813825f463..c674e92801 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.html
@@ -142,7 +142,10 @@
                                 title="Enable"></div>
                         }
                         @if (canChangeVersion(item)) {
-                            <div class="pointer fa fa-exchange primary-color" 
title="Change Version"></div>
+                            <div
+                                class="pointer fa fa-exchange primary-color"
+                                (click)="changeVersionClicked(item)"
+                                title="Change Version"></div>
                         }
                         @if (canDelete(item)) {
                             <div
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts
index 3820103180..b25fc5333d 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rule-table/flow-analysis-rule-table.component.ts
@@ -66,6 +66,8 @@ export class FlowAnalysisRuleTable {
         new EventEmitter<FlowAnalysisRuleEntity>();
     @Output() disableFlowAnalysisRule: EventEmitter<FlowAnalysisRuleEntity> =
         new EventEmitter<FlowAnalysisRuleEntity>();
+    @Output() changeFlowAnalysisRuleVersion: 
EventEmitter<FlowAnalysisRuleEntity> =
+        new EventEmitter<FlowAnalysisRuleEntity>();
 
     sort: Sort = {
         active: 'name',
@@ -230,6 +232,10 @@ export class FlowAnalysisRuleTable {
         this.enableFlowAnalysisRule.next(entity);
     }
 
+    changeVersionClicked(entity: FlowAnalysisRuleEntity): void {
+        this.changeFlowAnalysisRuleVersion.next(entity);
+    }
+
     canDisable(entity: FlowAnalysisRuleEntity): boolean {
         const userAuthorized: boolean = this.canRead(entity) && 
this.canOperate(entity);
         return userAuthorized && this.isEnabled(entity);
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.html
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.html
index e4b84518bb..9ff6e55988 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.html
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.html
@@ -41,6 +41,7 @@
                         
(enableFlowAnalysisRule)="enableFlowAnalysisRule($event)"
                         
(disableFlowAnalysisRule)="disableFlowAnalysisRule($event)"
                         
(viewStateFlowAnalysisRule)="viewStateFlowAnalysisRule($event)"
+                        
(changeFlowAnalysisRuleVersion)="changeFlowAnalysisRuleVersion($event)"
                         
(deleteFlowAnalysisRule)="deleteFlowAnalysisRule($event)"></flow-analysis-rule-table>
                 </div>
                 <div class="flex justify-between">
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.ts
index c9622614ae..38e97d6ff6 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/flow-analysis-rules/flow-analysis-rules.component.ts
@@ -30,6 +30,7 @@ import {
     enableFlowAnalysisRule,
     loadFlowAnalysisRules,
     navigateToEditFlowAnalysisRule,
+    openChangeFlowAnalysisRuleVersionDialog,
     openConfigureFlowAnalysisRuleDialog,
     openNewFlowAnalysisRuleDialog,
     promptFlowAnalysisRuleDeletion,
@@ -155,6 +156,20 @@ export class FlowAnalysisRules implements OnInit, 
OnDestroy {
         );
     }
 
+    changeFlowAnalysisRuleVersion(entity: FlowAnalysisRuleEntity): void {
+        this.store.dispatch(
+            openChangeFlowAnalysisRuleVersionDialog({
+                request: {
+                    id: entity.id,
+                    bundle: entity.component.bundle,
+                    uri: entity.uri,
+                    type: entity.component.type,
+                    revision: entity.revision
+                }
+            })
+        );
+    }
+
     deleteFlowAnalysisRule(entity: FlowAnalysisRuleEntity): void {
         this.store.dispatch(
             promptFlowAnalysisRuleDeletion({
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.html
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.html
index 13c1dc27b1..b3a177d0c7 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.html
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.html
@@ -46,6 +46,7 @@
                         
(enableControllerService)="enableControllerService($event)"
                         
(disableControllerService)="disableControllerService($event)"
                         
(viewStateControllerService)="viewStateControllerService($event)"
+                        
(changeControllerServiceVersion)="changeControllerServiceVersion($event)"
                         
(deleteControllerService)="deleteControllerService($event)"></controller-service-table>
                 </div>
                 <div class="flex justify-between">
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.ts
index 45038e2cb7..edcfef84ec 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.component.ts
@@ -28,6 +28,7 @@ import {
     loadManagementControllerServices,
     navigateToAdvancedServiceUi,
     navigateToEditService,
+    openChangeMgtControllerServiceVersionDialog,
     openConfigureControllerServiceDialog,
     openDisableControllerServiceDialog,
     openEnableControllerServiceDialog,
@@ -175,6 +176,20 @@ export class ManagementControllerServices implements 
OnInit, OnDestroy {
         );
     }
 
+    changeControllerServiceVersion(entity: ControllerServiceEntity): void {
+        this.store.dispatch(
+            openChangeMgtControllerServiceVersionDialog({
+                request: {
+                    id: entity.id,
+                    bundle: entity.component.bundle,
+                    uri: entity.uri,
+                    type: entity.component.type,
+                    revision: entity.revision
+                }
+            })
+        );
+    }
+
     deleteControllerService(entity: ControllerServiceEntity): void {
         this.store.dispatch(
             promptControllerServiceDeletion({
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.html
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.html
index abe91ebdf0..e5b2d05f44 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.html
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.html
@@ -143,7 +143,10 @@
                                 title="Start"></div>
                         }
                         @if (canChangeVersion(item)) {
-                            <div class="pointer fa fa-exchange primary-color" 
title="Change Version"></div>
+                            <div
+                                class="pointer fa fa-exchange primary-color"
+                                (click)="changeVersionClicked(item)"
+                                title="Change Version"></div>
                         }
                         @if (canDelete(item)) {
                             <div
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.ts
index d0926222f5..50eb9a44e9 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-task-table/reporting-task-table.component.ts
@@ -57,6 +57,7 @@ export class ReportingTaskTable {
     @Output() openAdvancedUi: EventEmitter<ReportingTaskEntity> = new 
EventEmitter<ReportingTaskEntity>();
     @Output() viewStateReportingTask: EventEmitter<ReportingTaskEntity> = new 
EventEmitter<ReportingTaskEntity>();
     @Output() stopReportingTask: EventEmitter<ReportingTaskEntity> = new 
EventEmitter<ReportingTaskEntity>();
+    @Output() changeReportingTaskVersion: EventEmitter<ReportingTaskEntity> = 
new EventEmitter<ReportingTaskEntity>();
 
     protected readonly TextTip = TextTip;
     protected readonly BulletinsTip = BulletinsTip;
@@ -235,6 +236,10 @@ export class ReportingTaskTable {
         );
     }
 
+    changeVersionClicked(entity: ReportingTaskEntity): void {
+        this.changeReportingTaskVersion.next(entity);
+    }
+
     deleteClicked(entity: ReportingTaskEntity): void {
         this.deleteReportingTask.next(entity);
     }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.html
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.html
index 4105bad1c0..f4a148e7b6 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.html
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.html
@@ -43,6 +43,7 @@
                         
(viewReportingTaskDocumentation)="viewReportingTaskDocumentation($event)"
                         (deleteReportingTask)="deleteReportingTask($event)"
                         (stopReportingTask)="stopReportingTask($event)"
+                        
(changeReportingTaskVersion)="changeReportingTaskVersion($event)"
                         
(startReportingTask)="startReportingTask($event)"></reporting-task-table>
                 </div>
                 <div class="flex justify-between">
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.ts
index fe37c458a2..257a0b5fce 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/reporting-tasks/reporting-tasks.component.ts
@@ -28,15 +28,16 @@ import {
 } from '../../state/reporting-tasks/reporting-tasks.selectors';
 import {
     loadReportingTasks,
+    navigateToAdvancedReportingTaskUi,
     navigateToEditReportingTask,
+    openChangeReportingTaskVersionDialog,
     openConfigureReportingTaskDialog,
     openNewReportingTaskDialog,
     promptReportingTaskDeletion,
     resetReportingTasksState,
-    startReportingTask,
-    stopReportingTask,
     selectReportingTask,
-    navigateToAdvancedReportingTaskUi
+    startReportingTask,
+    stopReportingTask
 } from '../../state/reporting-tasks/reporting-tasks.actions';
 import { initialState } from 
'../../state/reporting-tasks/reporting-tasks.reducer';
 import { selectCurrentUser } from 
'../../../../state/current-user/current-user.selectors';
@@ -184,6 +185,20 @@ export class ReportingTasks implements OnInit, OnDestroy {
         );
     }
 
+    changeReportingTaskVersion(entity: ReportingTaskEntity): void {
+        this.store.dispatch(
+            openChangeReportingTaskVersionDialog({
+                request: {
+                    id: entity.id,
+                    bundle: entity.component.bundle,
+                    uri: entity.uri,
+                    type: entity.component.type,
+                    revision: entity.revision
+                }
+            })
+        );
+    }
+
     ngOnDestroy(): void {
         this.store.dispatch(resetReportingTasksState());
     }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/extension-types.service.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/extension-types.service.ts
index 5fbc01928a..0cb93ba8de 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/extension-types.service.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/extension-types.service.ts
@@ -30,10 +30,28 @@ export class ExtensionTypesService {
         return 
this.httpClient.get(`${ExtensionTypesService.API}/flow/processor-types`);
     }
 
+    getProcessorVersionsForType(processorType: string, bundle: Bundle): 
Observable<any> {
+        const params = {
+            bundleGroupFilter: bundle.group,
+            bundleArtifactFilter: bundle.artifact,
+            type: processorType
+        };
+        return 
this.httpClient.get(`${ExtensionTypesService.API}/flow/processor-types`, { 
params });
+    }
+
     getControllerServiceTypes(): Observable<any> {
         return 
this.httpClient.get(`${ExtensionTypesService.API}/flow/controller-service-types`);
     }
 
+    getControllerServiceVersionsForType(serviceType: string, bundle: Bundle): 
Observable<any> {
+        const params: any = {
+            serviceBundleGroup: bundle.group,
+            serviceBundleArtifact: bundle.artifact,
+            typeFilter: serviceType
+        };
+        return 
this.httpClient.get(`${ExtensionTypesService.API}/flow/controller-service-types`,
 { params });
+    }
+
     getImplementingControllerServiceTypes(serviceType: string, bundle: 
Bundle): Observable<any> {
         const params: any = {
             serviceType,
@@ -48,6 +66,15 @@ export class ExtensionTypesService {
         return 
this.httpClient.get(`${ExtensionTypesService.API}/flow/reporting-task-types`);
     }
 
+    getReportingTaskVersionsForType(reportingTaskType: string, bundle: 
Bundle): Observable<any> {
+        const params = {
+            serviceBundleGroup: bundle.group,
+            serviceBundleArtifact: bundle.artifact,
+            type: reportingTaskType
+        };
+        return 
this.httpClient.get(`${ExtensionTypesService.API}/flow/reporting-task-types`, { 
params });
+    }
+
     getRegistryClientTypes(): Observable<any> {
         return 
this.httpClient.get(`${ExtensionTypesService.API}/controller/registry-types`);
     }
@@ -60,6 +87,15 @@ export class ExtensionTypesService {
         return 
this.httpClient.get(`${ExtensionTypesService.API}/flow/flow-analysis-rule-types`);
     }
 
+    getFlowAnalysisRuleVersionsForType(flowAnalysisRuleType: string, bundle: 
Bundle): Observable<any> {
+        const params: any = {
+            serviceBundleGroup: bundle.group,
+            serviceBundleArtifact: bundle.artifact,
+            type: flowAnalysisRuleType
+        };
+        return 
this.httpClient.get(`${ExtensionTypesService.API}/flow/flow-analysis-rule-types`,
 { params });
+    }
+
     getParameterProviderTypes(): Observable<any> {
         return 
this.httpClient.get(`${ExtensionTypesService.API}/flow/parameter-provider-types`);
     }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts
index 7ed8312e4a..67c5c90bbd 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/shared/index.ts
@@ -646,3 +646,16 @@ export interface ParameterProviderConfigurationEntity {
     id: string;
     component: ParameterProviderConfiguration;
 }
+
+export interface FetchComponentVersionsRequest {
+    id: string;
+    uri: string;
+    revision: Revision;
+    type: string;
+    bundle: Bundle;
+}
+
+export interface OpenChangeComponentVersionDialogRequest {
+    fetchRequest: FetchComponentVersionsRequest;
+    componentVersions: DocumentedType[];
+}
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/change-component-version-dialog/change-component-version-dialog.html
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/change-component-version-dialog/change-component-version-dialog.html
new file mode 100644
index 0000000000..902669e7ca
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/change-component-version-dialog/change-component-version-dialog.html
@@ -0,0 +1,84 @@
+<!--
+  ~ 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.
+  -->
+
+<h2 mat-dialog-title>Change Version</h2>
+<div class="change-version">
+    <form [formGroup]="changeComponentVersionForm">
+        <mat-dialog-content>
+            <div class="flex flex-col gap-y-4 w-full">
+                <div>
+                    <div>Name</div>
+                    <div class="accent-color font-medium">
+                        {{ getName(selected) }}
+                    </div>
+                </div>
+                <div>
+                    <div>Bundle</div>
+                    <div class="accent-color font-medium">
+                        {{ selected?.bundle?.group }} - {{ 
selected?.bundle?.artifact }}
+                    </div>
+                </div>
+                <mat-form-field subscriptSizing="dynamic">
+                    <mat-label>Version</mat-label>
+                    <mat-select [(value)]="selected" tabindex="0">
+                        @for (version of versions; track version) {
+                            <mat-option [value]="version" 
[disabled]="isCurrent(version)">
+                                {{ version.bundle.version }}
+                            </mat-option>
+                        }
+                    </mat-select>
+                </mat-form-field>
+                @if (selected?.controllerServiceApis) {
+                    <div>
+                        <div>Supports Controller Services</div>
+                        <div class="accent-color font-medium">
+                            <ul>
+                                @for (serviceApi of 
selected?.controllerServiceApis; track serviceApi) {
+                                    <li>
+                                        <controller-service-api
+                                            [type]="serviceApi.type"
+                                            
[bundle]="serviceApi.bundle"></controller-service-api>
+                                    </li>
+                                }
+                            </ul>
+                        </div>
+                    </div>
+                }
+                <div>
+                    <div>Restriction</div>
+                    @if (selected?.usageRestriction) {
+                        <div class="accent-color font-medium">
+                            {{ selected?.usageRestriction }}
+                        </div>
+                    } @else {
+                        <div class="unset nifi-surface-default">Not 
Restricted</div>
+                    }
+                </div>
+                <div>
+                    <div>Description</div>
+                    <div class="accent-color font-medium">{{ 
selected?.description }}</div>
+                </div>
+            </div>
+        </mat-dialog-content>
+        <mat-dialog-actions align="end">
+            <button mat-button mat-dialog-close>Cancel</button>
+            <button type="button" color="primary" mat-button (click)="apply()" 
[disabled]="isCurrent(selected)">
+                Apply
+            </button>
+        </mat-dialog-actions>
+    </form>
+</div>
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/change-component-version-dialog/change-component-version-dialog.scss
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/change-component-version-dialog/change-component-version-dialog.scss
new file mode 100644
index 0000000000..b33f7cac34
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/change-component-version-dialog/change-component-version-dialog.scss
@@ -0,0 +1,16 @@
+/*!
+ * 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.
+ */
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/change-component-version-dialog/change-component-version-dialog.spec.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/change-component-version-dialog/change-component-version-dialog.spec.ts
new file mode 100644
index 0000000000..66b136dce6
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/change-component-version-dialog/change-component-version-dialog.spec.ts
@@ -0,0 +1,86 @@
+/*
+ * 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 { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ChangeComponentVersionDialog } from 
'./change-component-version-dialog';
+import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { OpenChangeComponentVersionDialogRequest } from 
'../../../state/shared';
+
+describe('ChangeComponentVersionDialog', () => {
+    let component: ChangeComponentVersionDialog;
+    let fixture: ComponentFixture<ChangeComponentVersionDialog>;
+    const data: OpenChangeComponentVersionDialogRequest = {
+        fetchRequest: {
+            id: 'd3acc2a0-018e-1000-e4c1-1fd32cb579b6',
+            uri: 
'https://localhost:4200/nifi-api/processors/d3acc2a0-018e-1000-e4c1-1fd32cb579b6',
+            revision: {
+                clientId: 'e23146d1-018e-1000-9d09-6a3c09c72420',
+                version: 8
+            },
+            type: 'org.apache.nifi.processors.standard.GenerateFlowFile',
+            bundle: {
+                group: 'org.apache.nifi',
+                artifact: 'nifi-standard-nar',
+                version: '2.0.0-M2'
+            }
+        },
+        componentVersions: [
+            {
+                type: 'org.apache.nifi.processors.standard.GenerateFlowFile',
+                bundle: {
+                    group: 'org.apache.nifi',
+                    artifact: 'nifi-standard-nar',
+                    version: '2.0.0-SNAPSHOT'
+                },
+                description:
+                    'This processor creates FlowFiles with random data or 
custom content. GenerateFlowFile is useful for load testing, configuration, and 
simulation. Also see DuplicateFlowFile for additional load testing.',
+                restricted: false,
+                tags: ['random', 'test', 'load', 'generate']
+            },
+            {
+                type: 'org.apache.nifi.processors.standard.GenerateFlowFile',
+                bundle: {
+                    group: 'org.apache.nifi',
+                    artifact: 'nifi-standard-nar',
+                    version: '2.0.0-M2'
+                },
+                description:
+                    'This processor creates FlowFiles with random data or 
custom content. GenerateFlowFile is useful for load testing, configuration, and 
simulation. Also see DuplicateFlowFile for additional load testing.',
+                restricted: false,
+                tags: ['random', 'test', 'load', 'generate']
+            }
+        ]
+    };
+
+    beforeEach(async () => {
+        await TestBed.configureTestingModule({
+            imports: [ChangeComponentVersionDialog, MatDialogModule, 
NoopAnimationsModule, MatFormFieldModule],
+            providers: [{ provide: MAT_DIALOG_DATA, useValue: data }]
+        }).compileComponents();
+
+        fixture = TestBed.createComponent(ChangeComponentVersionDialog);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/change-component-version-dialog/change-component-version-dialog.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/change-component-version-dialog/change-component-version-dialog.ts
new file mode 100644
index 0000000000..61341f04c3
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/change-component-version-dialog/change-component-version-dialog.ts
@@ -0,0 +1,85 @@
+/*
+ * 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, EventEmitter, Inject, Output } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
+import { MatButton } from '@angular/material/button';
+import { Bundle, DocumentedType, OpenChangeComponentVersionDialogRequest } 
from '../../../state/shared';
+import { MatFormField, MatLabel, MatOption, MatSelect } from 
'@angular/material/select';
+import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators 
} from '@angular/forms';
+import { TextTip } from '../tooltips/text-tip/text-tip.component';
+import { NifiTooltipDirective } from '../tooltips/nifi-tooltip.directive';
+import { NiFiCommon } from '../../../service/nifi-common.service';
+import { ControllerServiceApi } from 
'../controller-service/controller-service-api/controller-service-api.component';
+
+@Component({
+    selector: 'change-component-version-dialog',
+    standalone: true,
+    imports: [
+        MatDialogModule,
+        MatButton,
+        MatSelect,
+        MatLabel,
+        MatOption,
+        MatFormField,
+        ReactiveFormsModule,
+        NifiTooltipDirective,
+        ControllerServiceApi
+    ],
+    templateUrl: './change-component-version-dialog.html',
+    styleUrl: './change-component-version-dialog.scss'
+})
+export class ChangeComponentVersionDialog {
+    versions: DocumentedType[];
+    selected: DocumentedType | null = null;
+    changeComponentVersionForm: FormGroup;
+    private currentBundle: Bundle;
+
+    @Output() changeVersion: EventEmitter<DocumentedType> = new 
EventEmitter<DocumentedType>();
+
+    constructor(
+        @Inject(MAT_DIALOG_DATA) private dialogRequest: 
OpenChangeComponentVersionDialogRequest,
+        private formBuilder: FormBuilder,
+        private nifiCommon: NiFiCommon
+    ) {
+        this.versions = dialogRequest.componentVersions;
+        this.currentBundle = dialogRequest.fetchRequest.bundle;
+        const idx = this.versions.findIndex(
+            (version: DocumentedType) => version.bundle.version === 
this.currentBundle.version
+        );
+        this.selected = this.versions[idx > 0 ? idx : 0];
+        this.changeComponentVersionForm = this.formBuilder.group({
+            bundle: new FormControl(this.selected, [Validators.required])
+        });
+    }
+
+    apply(): void {
+        if (this.selected) {
+            this.changeVersion.next(this.selected);
+        }
+    }
+
+    isCurrent(selection: DocumentedType | null): boolean {
+        return selection?.bundle.version === this.currentBundle.version;
+    }
+
+    getName(selected: DocumentedType | null): string {
+        return this.nifiCommon.substringAfterLast(selected?.type || '', '.');
+    }
+
+    protected readonly TextTip = TextTip;
+}
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.html
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.html
index 5fc5b47ca4..1771491070 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.html
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.html
@@ -150,7 +150,10 @@
                                     title="Enable"></div>
                             }
                             @if (canChangeVersion(item)) {
-                                <div class="pointer fa fa-exchange 
primary-color" title="Change Version"></div>
+                                <div
+                                    class="pointer fa fa-exchange 
primary-color"
+                                    (click)="changeVersionClicked(item)"
+                                    title="Change Version"></div>
                             }
                             @if (canDelete(item)) {
                                 <div
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.ts
index 2080ca5ef3..b095b664f0 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/controller-service-table/controller-service-table.component.ts
@@ -85,6 +85,8 @@ export class ControllerServiceTable {
         new EventEmitter<ControllerServiceEntity>();
     @Output() viewStateControllerService: 
EventEmitter<ControllerServiceEntity> =
         new EventEmitter<ControllerServiceEntity>();
+    @Output() changeControllerServiceVersion: 
EventEmitter<ControllerServiceEntity> =
+        new EventEmitter<ControllerServiceEntity>();
 
     protected readonly TextTip = TextTip;
     protected readonly BulletinsTip = BulletinsTip;
@@ -266,6 +268,10 @@ export class ControllerServiceTable {
         this.deleteControllerService.next(entity);
     }
 
+    changeVersionClicked(entity: ControllerServiceEntity) {
+        this.changeControllerServiceVersion.next(entity);
+    }
+
     canViewState(entity: ControllerServiceEntity): boolean {
         return this.canRead(entity) && this.canWrite(entity) && 
entity.component.persistsState === true;
     }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.ts
index d7a3338640..e2f6c7355e 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/extension-creation/extension-creation.component.ts
@@ -159,8 +159,20 @@ export class ExtensionCreation {
     }
 
     isSelected(documentedType: DocumentedType): boolean {
-        if (this.selectedType) {
-            return documentedType.type == this.selectedType.type;
+        return this.areDocumentedTypesTheSame(this.selectedType, 
documentedType);
+    }
+
+    private areDocumentedTypesTheSame(type1: DocumentedType | null, type2: 
DocumentedType | null): boolean {
+        if (type1 == type2) {
+            return true;
+        }
+        if (type1 && type2) {
+            return (
+                type1.type === type2.type &&
+                type1.bundle.version === type2.bundle.version &&
+                type1.bundle.group === type2.bundle.group &&
+                type1.bundle.artifact === type2.bundle.artifact
+            );
         }
         return false;
     }
@@ -197,8 +209,8 @@ export class ExtensionCreation {
     private selectRow(offset: number) {
         if (this.selectedType && this.dataSource.filteredData.length > 0) {
             // find the index of the currently selected row
-            const selectedIndex = this.dataSource.filteredData.findIndex(
-                (data) => data.type === this.selectedType?.type
+            const selectedIndex = 
this.dataSource.filteredData.findIndex((data) =>
+                this.areDocumentedTypesTheSame(this.selectedType, data)
             );
 
             if (selectedIndex > -1) {

Reply via email to