rfellows commented on code in PR #11263:
URL: https://github.com/apache/nifi/pull/11263#discussion_r3289049829


##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/connectors/state/connector-canvas/connector-canvas.effects.ts:
##########
@@ -162,6 +175,88 @@ export class ConnectorCanvasEffects {
         )
     );
 
+    /**
+     * Request the parameter context bound to the loaded process group 
whenever the
+     * connector canvas or controller-services page reports a successful load. 
The
+     * backend returns 204 No Content when no bound context exists, which 
surfaces as
+     * a null parameter context on the canvas state.
+     *
+     * The controller-services route is a sibling of the canvas route, not a 
child, so
+     * deep-linking directly to 
`/connectors/:id/canvas/:pgId/controller-services`
+     * never activates the canvas component and thus never emits
+     * {@link loadConnectorFlowSuccess}. Listening to both trigger actions in 
a single
+     * pipeline keeps that deep-link case covered. {@link 
distinctUntilChanged} keyed
+     * on connectorId + processGroupId dedupes the polling refire on the 
canvas page
+     * and the canvas → controller-services transition within the same process 
group;
+     * navigating to a different process group changes the key and triggers a 
fresh
+     * fetch.
+     */
+    loadConnectorParameterContextOnLoadSuccess$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(
+                ConnectorCanvasActions.loadConnectorFlowSuccess,
+                
ConnectorControllerServicesActions.loadConnectorControllerServicesSuccess
+            ),
+            map((action) => {
+                if (action.type === 
ConnectorCanvasActions.loadConnectorFlowSuccess.type) {
+                    return {
+                        connectorId: action.connectorId,
+                        processGroupId: action.processGroupId,
+                        errorContext: ErrorContextKey.CONNECTOR_CANVAS
+                    };
+                }
+                return {
+                    connectorId: action.response.connectorId,
+                    processGroupId: action.response.processGroupId,
+                    errorContext: ErrorContextKey.CONTROLLER_SERVICES
+                };
+            }),
+            filter(
+                (target): target is { connectorId: string; processGroupId: 
string; errorContext: ErrorContextKey } =>
+                    target.processGroupId !== null && target.processGroupId 
!== undefined
+            ),
+            distinctUntilChanged(
+                (previous, current) =>
+                    previous.connectorId === current.connectorId && 
previous.processGroupId === current.processGroupId
+            ),

Review Comment:
   NgRx effects are application-level singletons — they're created once when 
the module initializes and run forever. They are never destroyed or recreated 
when a component mounts/unmounts.
   
   The `distinctUntilChanged` inside the effect accumulates its previous value 
for the entire app lifetime. This results in the parameter-context lookup to 
only get loaded when the browser loads the connector canvas directly (deep 
link). If you navigate to it from the connector listing, the parameters don't 
get loaded.
   
   The store resets (`resetConnectorCanvasState` in `ngOnDestroy`) clear 
`parameterContext` from the state, but that does nothing to clear the effect's 
internal previous reference. From the effect's perspective the (connectorId, 
processGroupId) pair hasn't changed, so it silently suppresses the re-fetch.
   
   
   **The fix**
   The effect needs to restart its inner `distinctUntilChanged` whenever the 
canvas state is reset. The idiomatic RxJS pattern for this is `startWith` + 
`switchMap` on the reset action:
   
   ```typescript
   loadConnectorParameterContextOnLoadSuccess$ = createEffect(() =>
       this.actions$.pipe(
           ofType(ConnectorCanvasActions.resetConnectorCanvasState),
           startWith(null),   // kick off the first inner subscription 
immediately
           switchMap(() =>    // each reset discards the old inner sub (and its 
stale distinctUntilChanged state) and starts fresh
               this.actions$.pipe(
                   ofType(
                       ConnectorCanvasActions.loadConnectorFlowSuccess,
                       
ConnectorControllerServicesActions.loadConnectorControllerServicesSuccess
                   ),
                   map((action) => { /* normalize */ }),
                   filter((target): target is ... => target.processGroupId != 
null),
                   distinctUntilChanged(
                       (previous, current) =>
                           previous.connectorId === current.connectorId &&
                           previous.processGroupId === current.processGroupId
                   ),
                   map((target) => 
ConnectorCanvasActions.loadConnectorParameterContext(target))
               )
           )
       )
   );
   ```
   
   When resetConnectorCanvasState fires (component ngOnDestroy), switchMap 
cancels the inner observable — clearing the stale previous — and subscribes to 
a fresh one. The next loadConnectorFlowSuccess is then the first emission in 
that new inner stream, so distinctUntilChanged lets it through.
   
   The startWith(null) is essential: without it, the effect wouldn't start 
listening for load-success actions until the first reset fires.



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