This is an automated email from the ASF dual-hosted git repository.
rfellows 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 91c0c920c75 NIFI-16406: Avoid sanitizing connection positions (#11734)
91c0c920c75 is described below
commit 91c0c920c75d8254b319b07cb6f075671f19bee1
Author: Matt Gilman <[email protected]>
AuthorDate: Thu Oct 1 12:54:52 2026 -0400
NIFI-16406: Avoid sanitizing connection positions (#11734)
* NIFI-16406: Avoid sanitizing connection positions
Skip nonexistent position sanitization for connections while preserving
bend coordinate validation in the flow designer and connector canvas.
* NIFI-16406: Addressing review feedback.
---
.../connector-canvas.effects.spec.ts | 51 +++++++++++++++++++---
.../connector-canvas/connector-canvas.effects.ts | 23 +++++-----
.../flow-designer/state/flow/flow.effects.spec.ts | 51 ++++++++++++++++++----
.../pages/flow-designer/state/flow/flow.effects.ts | 25 +++++------
4 files changed, 110 insertions(+), 40 deletions(-)
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/connectors/state/connector-canvas/connector-canvas.effects.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/connectors/state/connector-canvas/connector-canvas.effects.spec.ts
index f02aea44407..7bb534cacc6 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/connectors/state/connector-canvas/connector-canvas.effects.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/connectors/state/connector-canvas/connector-canvas.effects.spec.ts
@@ -1552,6 +1552,22 @@ describe('ConnectorCanvasEffects', () => {
};
}
+ function makeConnection(
+ id: string,
+ bendX: number,
+ bendY: number,
+ componentBendX = bendX,
+ componentBendY = bendY
+ ): any {
+ return {
+ id,
+ permissions: { canRead: true, canWrite: true },
+ revision: { version: 0 },
+ bends: [{ x: bendX, y: bendY }],
+ component: { bends: [{ x: componentBendX, y: componentBendY }]
}
+ };
+ }
+
function makeFlowResponse(overrides: { processors?: any[];
connections?: any[] } = {}): any {
return {
processGroupFlow: {
@@ -1600,14 +1616,29 @@ describe('ConnectorCanvasEffects', () => {
warnSpy.mockRestore();
});
- it('clamps catastrophic-finite connection bends to (0, 0)', async ()
=> {
+ it('does not sanitize a nonexistent connection position', async () => {
+ const { effects, actions$, mockConnectorService } = await setup();
+ const connection = makeConnection('conn-valid', 100, 200, 300,
400);
+ (mockConnectorService.getConnectorFlow as Mock).mockReturnValue(
+ of(makeFlowResponse({ connections: [connection] }))
+ );
+
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() =>
undefined);
+ actions$(of(loadConnectorFlow({ connectorId: 'c-1',
processGroupId: 'pg-1' })));
+
+ const action: any = await
firstValueFrom(effects.loadConnectorFlow$);
+ expect(action.connections[0]).not.toHaveProperty('position');
+ expect(action.connections[0].bends[0]).toEqual({ x: 100, y: 200 });
+ expect(action.connections[0].component.bends[0]).toEqual({ x: 300,
y: 400 });
+ expect(warnSpy).not.toHaveBeenCalled();
+ warnSpy.mockRestore();
+ });
+
+ it('clamps top-level and nested catastrophic-finite connection bends
to (0, 0)', async () => {
const { effects, actions$, mockConnectorService } = await setup();
const connection = {
- id: 'conn-bad',
- permissions: { canRead: true, canWrite: true },
- revision: { version: 0 },
- position: { x: 0, y: 0 },
- component: { bends: [{ x: 9e307, y: 0 }] }
+ ...makeConnection('conn-bad', 9e307, 0, 0, 8e307),
+ position: { x: 7e307, y: 0 }
};
(mockConnectorService.getConnectorFlow as Mock).mockReturnValue(
of(makeFlowResponse({ connections: [connection] }))
@@ -1617,7 +1648,15 @@ describe('ConnectorCanvasEffects', () => {
actions$(of(loadConnectorFlow({ connectorId: 'c-1',
processGroupId: 'pg-1' })));
const action: any = await
firstValueFrom(effects.loadConnectorFlow$);
+ expect(action.connections[0].bends[0]).toEqual({ x: 0, y: 0 });
expect(action.connections[0].component.bends[0]).toEqual({ x: 0,
y: 0 });
+ expect(action.connections[0].position).toEqual({ x: 7e307, y: 0 });
+ expect(warnSpy).toHaveBeenCalledTimes(1);
+ expect(warnSpy).toHaveBeenCalledWith(
+ expect.stringContaining('Component Connection bend
conn-bad:bend:0 has an out-of-range position'),
+ { x: 9e307, y: 0 },
+ expect.stringContaining('falling back to (0, 0)')
+ );
warnSpy.mockRestore();
});
});
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/connectors/state/connector-canvas/connector-canvas.effects.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/connectors/state/connector-canvas/connector-canvas.effects.ts
index f75e4481e4d..a6d68d55b21 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/connectors/state/connector-canvas/connector-canvas.effects.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/connectors/state/connector-canvas/connector-canvas.effects.ts
@@ -40,6 +40,7 @@ import {
LARGE_DIALOG,
MEDIUM_DIALOG,
NiFiCommon,
+ Position,
sanitizePosition,
XL_DIALOG
} from '@nifi/shared';
@@ -829,23 +830,21 @@ export class ConnectorCanvasEffects {
warnedIds: this.warnedPositionIds
})
});
+ const sanitizeBends = (entityId: string, bends: Position[] |
undefined): Position[] | undefined =>
+ bends?.map((bend: Position, index: number) =>
+ sanitizePosition(bend, {
+ componentId: `${entityId}:bend:${index}`,
+ componentKind: 'Connection bend',
+ warnedIds: this.warnedPositionIds
+ })
+ );
const sanitizeConnection = (entity: any) => ({
...entity,
- position: sanitizePosition(entity.position, {
- componentId: entity.id,
- componentKind: 'Connection',
- warnedIds: this.warnedPositionIds
- }),
+ bends: sanitizeBends(entity.id, entity.bends),
component: entity.component
? {
...entity.component,
- bends: entity.component.bends?.map((bend: any, index:
number) =>
- sanitizePosition(bend, {
- componentId: `${entity.id}:bend:${index}`,
- componentKind: 'Connection bend',
- warnedIds: this.warnedPositionIds
- })
- )
+ bends: sanitizeBends(entity.id, entity.component.bends)
}
: entity.component
});
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.spec.ts
index 4dc36a34dd7..fbbe44253a9 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.spec.ts
@@ -1439,13 +1439,19 @@ describe('FlowEffects', () => {
};
}
- function makeConnection(id: string, bendX: number, bendY: number): any
{
+ function makeConnection(
+ id: string,
+ bendX: number,
+ bendY: number,
+ componentBendX = bendX,
+ componentBendY = bendY
+ ): any {
return {
id,
permissions: { canRead: true, canWrite: true },
revision: { version: 0 },
- position: { x: 0, y: 0 },
- component: { bends: [{ x: bendX, y: bendY }] }
+ bends: [{ x: bendX, y: bendY }],
+ component: { bends: [{ x: componentBendX, y: componentBendY }]
}
};
}
@@ -1456,14 +1462,14 @@ describe('FlowEffects', () => {
parentGroupId: null,
breadcrumb: {},
flow: {
- processors: overrides.processors ?? [],
+ processors: overrides['processors'] ?? [],
processGroups: [],
remoteProcessGroups: [],
inputPorts: [],
outputPorts: [],
labels: [],
funnels: [],
- connections: overrides.connections ?? []
+ connections: overrides['connections'] ?? []
}
}
};
@@ -1504,16 +1510,43 @@ describe('FlowEffects', () => {
warnSpy.mockRestore();
});
- it('clamps catastrophic-finite connection bends to (0, 0)', async ()
=> {
- const connection = makeConnection('conn-1', 9e307, 0);
+ it('does not sanitize a nonexistent connection position', async () => {
+ const connection = makeConnection('conn-valid', 100, 200, 300,
400);
(flowService as any).getFlow = vi.fn(() => of(buildFlowResponse({
connections: [connection] })));
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() =>
undefined);
action$.next(FlowActions.loadProcessGroup({ request: { id: 'pg-1',
transitionRequired: false } }));
const result: any = await
firstValueFrom(effects.loadProcessGroup$.pipe(take(1)));
- const bend =
result.response.flow.processGroupFlow.flow.connections[0].component.bends[0];
- expect(bend).toEqual({ x: 0, y: 0 });
+ const sanitizedConnection =
result.response.flow.processGroupFlow.flow.connections[0];
+ expect(sanitizedConnection).not.toHaveProperty('position');
+ expect(sanitizedConnection.bends[0]).toEqual({ x: 100, y: 200 });
+ expect(sanitizedConnection.component.bends[0]).toEqual({ x: 300,
y: 400 });
+ expect(warnSpy).not.toHaveBeenCalled();
+ warnSpy.mockRestore();
+ });
+
+ it('clamps top-level and nested catastrophic-finite connection bends
to (0, 0)', async () => {
+ const connection = {
+ ...makeConnection('conn-1', 9e307, 0, 0, 8e307),
+ position: { x: 7e307, y: 0 }
+ };
+ (flowService as any).getFlow = vi.fn(() => of(buildFlowResponse({
connections: [connection] })));
+
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() =>
undefined);
+ action$.next(FlowActions.loadProcessGroup({ request: { id: 'pg-1',
transitionRequired: false } }));
+
+ const result: any = await
firstValueFrom(effects.loadProcessGroup$.pipe(take(1)));
+ const sanitizedConnection =
result.response.flow.processGroupFlow.flow.connections[0];
+ expect(sanitizedConnection.bends[0]).toEqual({ x: 0, y: 0 });
+ expect(sanitizedConnection.component.bends[0]).toEqual({ x: 0, y:
0 });
+ expect(sanitizedConnection.position).toEqual({ x: 7e307, y: 0 });
+ expect(warnSpy).toHaveBeenCalledTimes(1);
+ expect(warnSpy).toHaveBeenCalledWith(
+ expect.stringContaining('Component Connection bend
conn-1:bend:0 has an out-of-range position'),
+ { x: 9e307, y: 0 },
+ expect.stringContaining('falling back to (0, 0)')
+ );
warnSpy.mockRestore();
});
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
index cd1fa9dd574..3858f03b346 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/state/flow/flow.effects.ts
@@ -5075,6 +5075,7 @@ export class FlowEffects {
*/
private sanitizeFlowPositions(flow: ProcessGroupFlowEntity):
ProcessGroupFlowEntity {
const f = flow.processGroupFlow.flow;
+ type ConnectionWithBends = ConnectionEntity & { bends?: Position[] };
const sanitize = (entity: ComponentEntity, kind: string):
ComponentEntity => ({
...entity,
position: sanitizePosition(entity.position, {
@@ -5083,23 +5084,21 @@ export class FlowEffects {
warnedIds: this.warnedPositionIds
})
});
- const sanitizeConnection = (entity: ConnectionEntity):
ConnectionEntity => ({
+ const sanitizeBends = (entityId: string, bends: Position[] |
undefined): Position[] | undefined =>
+ bends?.map((bend: Position, index: number) =>
+ sanitizePosition(bend, {
+ componentId: `${entityId}:bend:${index}`,
+ componentKind: 'Connection bend',
+ warnedIds: this.warnedPositionIds
+ })
+ );
+ const sanitizeConnection = (entity: ConnectionWithBends):
ConnectionWithBends => ({
...entity,
- position: sanitizePosition(entity.position, {
- componentId: entity.id,
- componentKind: 'Connection',
- warnedIds: this.warnedPositionIds
- }),
+ bends: sanitizeBends(entity.id, entity.bends),
component: entity.component
? {
...entity.component,
- bends: entity.component.bends?.map((bend: Position,
index: number) =>
- sanitizePosition(bend, {
- componentId: `${entity.id}:bend:${index}`,
- componentKind: 'Connection bend',
- warnedIds: this.warnedPositionIds
- })
- )
+ bends: sanitizeBends(entity.id, entity.component.bends)
}
: entity.component
});