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]