markap14 commented on code in PR #11677:
URL: https://github.com/apache/nifi/pull/11677#discussion_r4029453003


##########
nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java:
##########
@@ -5409,7 +5409,19 @@ public ConnectorDTO createConnectorDto(final 
ConnectorNode connector) {
         dto.setType(connector.getCanonicalClassName());
         dto.setExtensionMissing(connector.isExtensionMissing());
 
-        dto.setBundle(createBundleDto(connector.getBundleCoordinate()));
+        final BundleCoordinate bundleCoordinate = 
connector.getBundleCoordinate();
+        final List<Bundle> availableBundles = 
extensionManager.getBundles(connector.getCanonicalClassName());
+        int compatibleBundleCount = 0;
+        for (final Bundle bundle : availableBundles) {
+            final BundleCoordinate coordinate = 
bundle.getBundleDetails().getCoordinate();
+            if (bundleCoordinate.getGroup().equals(coordinate.getGroup()) && 
bundleCoordinate.getId().equals(coordinate.getId())) {
+                compatibleBundleCount++;
+            }
+        }
+
+        dto.setMultipleVersionsAvailable(connector.isExtensionMissing() ? 
compatibleBundleCount > 0 : compatibleBundleCount > 1);

Review Comment:
   Addressed in 269a772c478. `ConnectorEntityMerger` now follows the processor 
merger pattern and reports `multipleVersionsAvailable=false` when any node 
reports false or null. The merger test covers false, null, and all-true node 
results.



##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/connectors/state/connectors-listing/connectors-listing.effects.ts:
##########
@@ -433,6 +440,82 @@ export class ConnectorsListingEffects {
         )
     );
 
+    openChangeConnectorVersionDialog$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(openChangeConnectorVersionDialog),
+                map((action) => action.request),
+                switchMap((request) =>
+                    
from(this.extensionTypesService.getConnectorVersionsForType(request.type, 
request.bundle)).pipe(
+                        map(
+                            (response) =>
+                                ({
+                                    fetchRequest: request,
+                                    componentVersions: response.connectorTypes
+                                }) as OpenChangeComponentVersionDialogRequest
+                        ),
+                        catchError((errorResponse: HttpErrorResponse) => {
+                            this.store.dispatch(
+                                ErrorActions.snackBarError({
+                                    error: 
this.errorHelper.getErrorString(errorResponse)
+                                })
+                            );
+                            return EMPTY;
+                        })
+                    )
+                ),
+                tap((request) => {
+                    const dialogRequest = 
this.dialog.open(ChangeComponentVersionDialog, {
+                        ...LARGE_DIALOG,
+                        data: request,
+                        autoFocus: false
+                    });
+
+                    
dialogRequest.componentInstance.changeVersion.pipe(take(1)).subscribe((newVersion)
 => {
+                        this.store.dispatch(
+                            changeConnectorVersion({
+                                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 }
+    );
+
+    changeConnectorVersion$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(changeConnectorVersion),
+            map((action) => action.request),
+            concatMap((request) =>

Review Comment:
   Addressed in 269a772c478. The listing now passes its `saving` state to the 
connector table, and Change Version is unavailable while that state is true. 
The table test covers the pending-request case.



##########
nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java:
##########
@@ -1310,6 +1318,126 @@ public Connector getConnector() {
         return connectorDetails.getConnector();
     }
 
+    @Override
+    public void verifyCanReload() {
+        if (!isStopped()) {
+            throw new IllegalStateException("Cannot reload " + this + " 
because its state is " + getCurrentState());
+        }
+    }
+
+    @Override
+    public void verifyCanUpdateBundle(final BundleCoordinate 
incomingCoordinate) {
+        final BundleCoordinate currentCoordinate = getBundleCoordinate();
+        if 
(!currentCoordinate.getGroup().equals(incomingCoordinate.getGroup()) || 
!currentCoordinate.getId().equals(incomingCoordinate.getId())) {
+            throw new IllegalArgumentException("Cannot update " + this + " 
from " + currentCoordinate.getCoordinate() + " to "
+                + incomingCoordinate.getCoordinate() + " because the bundle 
group and artifact must be unchanged");
+        }
+    }
+
+    @Override
+    public void replaceConnector(final Connector replacement, final 
BundleCoordinate replacementCoordinate, final ComponentLog replacementLog) 
throws FlowUpdateException {
+        verifyCanReload();
+
+        final FrameworkConnectorInitializationContext 
replacementInitializationContext = new 
StandardConnectorInitializationContext.Builder()
+            .identifier(identifier)
+            .name(name)
+            .componentLog(replacementLog)
+            .secretsManager(initializationContext.getSecretsManager())
+            .assetManager(initializationContext.getAssetManager())
+            
.componentBundleLookup(initializationContext.getComponentBundleLookup())
+            .build();
+
+        try (final NarCloseable ignored = 
NarCloseable.withComponentNarLoader(replacement.getClass().getClassLoader())) {
+            replacement.initialize(replacementInitializationContext);
+        }
+
+        final WorkingFlowContextState workingContextState = 
acquireWorkingFlowContext();
+        final FrameworkFlowContext workingContext = 
workingContextState.getContext();
+        boolean workingContextReleased = false;
+        try {
+            final Map<String, StepConfiguration> originalActiveConfiguration = 
toConfigurationMap(activeFlowContext.getConfigurationContext().toConnectorConfiguration());
+            final Map<String, StepConfiguration> originalWorkingConfiguration 
= workingContext == null ? originalActiveConfiguration
+                : 
toConfigurationMap(workingContext.getConfigurationContext().toConnectorConfiguration());
+
+            final Map<String, StepConfiguration> activeConfiguration = 
migrateProperties(replacement, originalActiveConfiguration);
+            final Map<String, StepConfiguration> workingConfiguration = 
migrateProperties(replacement, originalWorkingConfiguration);
+            final Bundle bundle = new Bundle(replacementCoordinate.getGroup(), 
replacementCoordinate.getId(), replacementCoordinate.getVersion());
+
+            replaceConfiguration(activeFlowContext.getConfigurationContext(), 
activeConfiguration);

Review Comment:
   Addressed in 269a772c478. Configuration migration is now staged in separate 
contexts before either live context is changed. `StandardFlowContext` publishes 
configuration, bundle, log, and root facade through one reload state, and 
connector metadata is published only after both context reloads and callbacks 
succeed. Failures restore the original working context. Tests cover 
configuration resolution, working-context reload, callback, and active-context 
reload failures.



##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/connectors/ui/connector-table/connector-table.component.ts:
##########
@@ -157,6 +159,20 @@ export class ConnectorTable {
         return isConnectorActionAllowed(entity, 'PURGE_FLOWFILES');
     }
 
+    canChangeVersion(entity: ConnectorEntity): boolean {
+        const versionChangeEligibleStates = [
+            ConnectorState.STOPPED,
+            ConnectorState.UPDATED,
+            ConnectorState.UPDATE_FAILED
+        ];
+        return (
+            this.canRead(entity) &&
+            this.canModify(entity) &&
+            versionChangeEligibleStates.includes(entity.component.state as 
ConnectorState) &&

Review Comment:
   Addressed in 269a772c478. `UPDATED` and `UPDATE_FAILED` now require an 
aggregate active-thread count of exactly zero; `STOPPED` retains its prior 
behavior. Tests cover active and quiescent cases for both update states.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to