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 d9e48f8645 NIFI-13065: Adding initial bend points for self looping 
connections and connections that collide with existing connections (#8671)
d9e48f8645 is described below

commit d9e48f8645a66cd9088b6566e706dfdda775e300
Author: Matt Gilman <[email protected]>
AuthorDate: Fri Apr 19 17:58:26 2024 -0400

    NIFI-13065: Adding initial bend points for self looping connections and 
connections that collide with existing connections (#8671)
    
    * NIFI-13065:
    - Adding initial bend points for self looping connections and connections 
that collide with existing connections.
    - Merging two actions into one for opening the new connection dialog.
    
    * NIFI-13065:
    - Only considering self looping connections when automatically moving bends 
when the source component moves.
    
    * NIFI-13065:
    - Making collision check for more lenient.
    - Setting initial label index to 0.
    
    This closes #8671
---
 .../behavior/connectable-behavior.service.ts       | 181 +++++++++++++++++++--
 .../service/behavior/draggable-behavior.service.ts |  34 ++--
 .../service/manager/connection-manager.service.ts  |   3 +
 .../pages/flow-designer/state/flow/flow.actions.ts |   8 +-
 .../pages/flow-designer/state/flow/flow.effects.ts |  41 +++--
 .../app/pages/flow-designer/state/flow/index.ts    |   1 +
 .../create-connection.component.ts                 |   5 +
 7 files changed, 214 insertions(+), 59 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/behavior/connectable-behavior.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/behavior/connectable-behavior.service.ts
index b553a1ad7b..31a6f1d76a 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/behavior/connectable-behavior.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/behavior/connectable-behavior.service.ts
@@ -20,9 +20,11 @@ import * as d3 from 'd3';
 import { CanvasUtils } from '../canvas-utils.service';
 import { Store } from '@ngrx/store';
 import { CanvasState } from '../../state';
-import { getDefaultsAndOpenNewConnectionDialog, selectComponents } from 
'../../state/flow/flow.actions';
+import { openNewConnectionDialog, selectComponents } from 
'../../state/flow/flow.actions';
 import { ConnectionManager } from '../manager/connection-manager.service';
 import { Position } from '../../state/shared';
+import { CreateConnectionRequest } from '../../state/flow';
+import { NiFiCommon } from '../../../../service/nifi-common.service';
 
 @Injectable({
     providedIn: 'root'
@@ -33,7 +35,8 @@ export class ConnectableBehavior {
 
     constructor(
         private store: Store<CanvasState>,
-        private canvasUtils: CanvasUtils
+        private canvasUtils: CanvasUtils,
+        private nifiCommon: NiFiCommon
     ) {
         const self: ConnectableBehavior = this;
 
@@ -222,26 +225,174 @@ export class ConnectableBehavior {
                     // create the connection
                     const destinationData = destination.datum();
 
+                    // prepare the connection request
+                    const request: CreateConnectionRequest = {
+                        source: {
+                            id: sourceData.id,
+                            componentType: sourceData.type,
+                            entity: sourceData
+                        },
+                        destination: {
+                            id: destinationData.id,
+                            componentType: destinationData.type,
+                            entity: destinationData
+                        }
+                    };
+
+                    // add initial bend points if necessary
+                    const bends: Position[] = 
self.calculateInitialBendPoints(sourceData, destinationData);
+                    if (bends) {
+                        request.bends = bends;
+                    }
+
                     self.store.dispatch(
-                        getDefaultsAndOpenNewConnectionDialog({
-                            request: {
-                                source: {
-                                    id: sourceData.id,
-                                    componentType: sourceData.type,
-                                    entity: sourceData
-                                },
-                                destination: {
-                                    id: destinationData.id,
-                                    componentType: destinationData.type,
-                                    entity: destinationData
-                                }
-                            }
+                        openNewConnectionDialog({
+                            request
                         })
                     );
                 }
             });
     }
 
+    /**
+     * Calculate bend points for a new Connection if necessary.
+     *
+     * @param sourceData
+     * @param destinationData
+     */
+    private calculateInitialBendPoints(sourceData: any, destinationData: any): 
Position[] {
+        const bends: Position[] = [];
+
+        if (sourceData.id == destinationData.id) {
+            const rightCenter: Position = {
+                x: sourceData.position.x + sourceData.dimensions.width,
+                y: sourceData.position.y + sourceData.dimensions.height / 2
+            };
+
+            const xOffset = ConnectionManager.SELF_LOOP_X_OFFSET;
+            const yOffset = ConnectionManager.SELF_LOOP_Y_OFFSET;
+            bends.push({
+                x: rightCenter.x + xOffset,
+                y: rightCenter.y - yOffset
+            });
+            bends.push({
+                x: rightCenter.x + xOffset,
+                y: rightCenter.y + yOffset
+            });
+        } else {
+            const existingConnections: any[] = [];
+
+            // get all connections for the source component
+            const connectionsForSourceComponent: any[] = 
this.canvasUtils.getComponentConnections(sourceData.id);
+            
connectionsForSourceComponent.forEach((connectionForSourceComponent) => {
+                // get the id for the source/destination component
+                const connectionSourceComponentId =
+                    
this.canvasUtils.getConnectionSourceComponentId(connectionForSourceComponent);
+                const connectionDestinationComponentId =
+                    
this.canvasUtils.getConnectionDestinationComponentId(connectionForSourceComponent);
+
+                // if the connection is between these same components, 
consider it for collisions
+                if (
+                    (connectionSourceComponentId === sourceData.id &&
+                        connectionDestinationComponentId === 
destinationData.id) ||
+                    (connectionDestinationComponentId === sourceData.id &&
+                        connectionSourceComponentId === destinationData.id)
+                ) {
+                    // record all connections between these two components in 
question
+                    existingConnections.push(connectionForSourceComponent);
+                }
+            });
+
+            // if there are existing connections between these components, 
ensure the new connection won't collide
+            if (existingConnections) {
+                const avoidCollision = 
existingConnections.some((existingConnection) => {
+                    // only consider multiple connections with no bend points 
a collision, the existence of
+                    // bend points suggests that the user has placed the 
connection into a desired location
+                    return this.nifiCommon.isEmpty(existingConnection.bends);
+                });
+
+                // if we need to avoid a collision
+                if (avoidCollision) {
+                    // determine the middle of the source/destination 
components
+                    const sourceMiddle: Position = {
+                        x: sourceData.position.x + sourceData.dimensions.width 
/ 2,
+                        y: sourceData.position.y + 
sourceData.dimensions.height / 2
+                    };
+                    const destinationMiddle: Position = {
+                        x: destinationData.position.x + 
destinationData.dimensions.width / 2,
+                        y: destinationData.position.y + 
destinationData.dimensions.height / 2
+                    };
+
+                    // detect if the line is more horizontal or vertical
+                    const slope = (sourceMiddle.y - destinationMiddle.y) / 
(sourceMiddle.x - destinationMiddle.x);
+                    const isMoreHorizontal = slope <= 1 && slope >= -1;
+
+                    // find the midpoint on the connection
+                    const xCandidate = (sourceMiddle.x + destinationMiddle.x) 
/ 2;
+                    const yCandidate = (sourceMiddle.y + destinationMiddle.y) 
/ 2;
+
+                    // attempt to position this connection so it doesn't 
collide
+                    let xStep = isMoreHorizontal ? 0 : 
ConnectionManager.CONNECTION_OFFSET_X_INCREMENT;
+                    let yStep = isMoreHorizontal ? 
ConnectionManager.CONNECTION_OFFSET_Y_INCREMENT : 0;
+
+                    let positioned = false;
+                    while (!positioned) {
+                        // consider above and below, then increment and try 
again (if necessary)
+                        if (!this.collides(existingConnections, xCandidate - 
xStep, yCandidate - yStep)) {
+                            bends.push({
+                                x: xCandidate - xStep,
+                                y: yCandidate - yStep
+                            });
+                            positioned = true;
+                        } else if (!this.collides(existingConnections, 
xCandidate + xStep, yCandidate + yStep)) {
+                            bends.push({
+                                x: xCandidate + xStep,
+                                y: yCandidate + yStep
+                            });
+                            positioned = true;
+                        }
+
+                        if (isMoreHorizontal) {
+                            yStep += 
ConnectionManager.CONNECTION_OFFSET_Y_INCREMENT;
+                        } else {
+                            xStep += 
ConnectionManager.CONNECTION_OFFSET_X_INCREMENT;
+                        }
+                    }
+                }
+            }
+        }
+
+        return bends;
+    }
+
+    /**
+     * Determines if the specified coordinate collides with another connection.
+     *
+     * @param existingConnections
+     * @param x
+     * @param y
+     */
+    private collides(existingConnections: any[], x: number, y: number): 
boolean {
+        return existingConnections.some((existingConnection) => {
+            if (!this.nifiCommon.isEmpty(existingConnection.bends)) {
+                let labelIndex = existingConnection.labelIndex;
+                if (labelIndex >= existingConnection.bends.length) {
+                    labelIndex = 0;
+                }
+
+                // determine collision based on y space or x space depending 
on whether the connection is more horizontal
+                return (
+                    existingConnection.bends[labelIndex].y - 25 < y &&
+                    existingConnection.bends[labelIndex].y + 25 > y &&
+                    existingConnection.bends[labelIndex].x - 100 < x &&
+                    existingConnection.bends[labelIndex].x + 100 > x
+                );
+            }
+
+            return false;
+        });
+    }
+
     /**
      * Determines if we want to allow adding connections in the current state:
      *
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/behavior/draggable-behavior.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/behavior/draggable-behavior.service.ts
index 8dc3082b45..c68f692c87 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/behavior/draggable-behavior.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/behavior/draggable-behavior.service.ts
@@ -176,7 +176,6 @@ export class DraggableBehavior {
      * @param {selection} dragSelection The current drag selection
      */
     private updateComponentsPosition(dragSelection: any): void {
-        const self: DraggableBehavior = this;
         const componentUpdates: Map<string, UpdateComponentRequest> = new 
Map();
         const connectionUpdates: Map<string, UpdateComponentRequest> = new 
Map();
 
@@ -192,8 +191,8 @@ export class DraggableBehavior {
             return;
         }
 
-        const selectedConnections: any = d3.selectAll('g.connection.selected');
-        const selectedComponents: any = d3.selectAll('g.component.selected');
+        const selectedConnections: d3.Selection<any, any, any, any> = 
d3.selectAll('g.connection.selected');
+        const selectedComponents: d3.Selection<any, any, any, any> = 
d3.selectAll('g.component.selected');
 
         // ensure every component is writable
         if (!this.canvasUtils.canModify(selectedConnections) || 
!this.canvasUtils.canModify(selectedComponents)) {
@@ -207,29 +206,34 @@ export class DraggableBehavior {
         }
 
         // go through each selected connection
-        selectedConnections.each(function (d: any) {
-            const connectionUpdate = self.updateConnectionPosition(d, delta);
-            if (connectionUpdate !== null) {
+        selectedConnections.each((d) => {
+            const connectionUpdate = this.updateConnectionPosition(d, delta);
+            if (connectionUpdate) {
                 connectionUpdates.set(d.id, connectionUpdate);
             }
         });
 
         // go through each selected component
-        selectedComponents.each(function (d: any) {
+        selectedComponents.each((d) => {
             // consider any self looping connections
-            const componentConnections = 
self.canvasUtils.getComponentConnections(d.id);
+            const componentConnections = 
this.canvasUtils.getComponentConnections(d.id);
+
+            componentConnections.forEach((connection) => {
+                if (!connectionUpdates.has(connection.id)) {
+                    const sourceId = 
this.canvasUtils.getConnectionSourceComponentId(connection);
+                    const destinationId = 
this.canvasUtils.getConnectionDestinationComponentId(connection);
 
-            componentConnections.forEach((componentConnection) => {
-                if (!connectionUpdates.has(componentConnection.id)) {
-                    const connectionUpdate = 
self.updateConnectionPosition(componentConnection, delta);
-                    if (connectionUpdate !== null) {
-                        connectionUpdates.set(componentConnection.id, 
connectionUpdate);
+                    if (sourceId === destinationId) {
+                        const connectionUpdate = 
this.updateConnectionPosition(connection, delta);
+                        if (connectionUpdate) {
+                            connectionUpdates.set(connection.id, 
connectionUpdate);
+                        }
                     }
                 }
             });
 
             // consider the component itself
-            componentUpdates.set(d.id, self.updateComponentPosition(d, delta));
+            componentUpdates.set(d.id, this.updateComponentPosition(d, delta));
         });
 
         // dispatch the position updates
@@ -247,7 +251,7 @@ export class DraggableBehavior {
     /**
      * Updates the parent group of all selected components.
      *
-     * @param {selection} the destination group
+     * @param group
      */
     private updateComponentsGroup(group: any): void {
         // get the selection and deselect the components being moved
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/manager/connection-manager.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/manager/connection-manager.service.ts
index 50e5f976e2..3cf2c574b5 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/connection-manager.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/manager/connection-manager.service.ts
@@ -72,6 +72,9 @@ export class ConnectionManager {
     public static readonly SELF_LOOP_X_OFFSET: number = 
ConnectionManager.DIMENSIONS.width / 2 + 5;
     public static readonly SELF_LOOP_Y_OFFSET: number = 25;
 
+    public static readonly CONNECTION_OFFSET_Y_INCREMENT: number = 75;
+    public static readonly CONNECTION_OFFSET_X_INCREMENT: number = 250;
+
     private static readonly HEIGHT_FOR_BACKPRESSURE: number = 3;
 
     private static readonly SNAP_ALIGNMENT_PIXELS: number = 8;
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 6d11c676b4..67deb5e17b 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
@@ -26,7 +26,6 @@ import {
     CreateComponentRequest,
     CreateComponentResponse,
     CreateConnection,
-    CreateConnectionDialogRequest,
     CreateConnectionRequest,
     CreatePortRequest,
     CreateProcessGroupDialogRequest,
@@ -305,14 +304,9 @@ export const createProcessor = createAction(
     props<{ request: CreateProcessorRequest }>()
 );
 
-export const getDefaultsAndOpenNewConnectionDialog = createAction(
-    `${CANVAS_PREFIX} Get Defaults And Open New Connection Dialog`,
-    props<{ request: CreateConnectionRequest }>()
-);
-
 export const openNewConnectionDialog = createAction(
     `${CANVAS_PREFIX} Open New Connection Dialog`,
-    props<{ request: CreateConnectionDialogRequest }>()
+    props<{ request: CreateConnectionRequest }>()
 );
 
 export const createConnection = createAction(
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 98b2cbf110..0ca8744b6b 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
@@ -41,6 +41,7 @@ import {
 } from 'rxjs';
 import {
     CopyComponentRequest,
+    CreateConnectionDialogRequest,
     CreateProcessGroupDialogRequest,
     DeleteComponentResponse,
     GroupComponentsDialogRequest,
@@ -610,36 +611,32 @@ export class FlowEffects {
         )
     );
 
-    getDefaultsAndOpenNewConnectionDialog$ = createEffect(() =>
-        this.actions$.pipe(
-            ofType(FlowActions.getDefaultsAndOpenNewConnectionDialog),
-            map((action) => action.request),
-            concatLatestFrom(() => 
this.store.select(selectCurrentProcessGroupId)),
-            switchMap(([request, currentProcessGroupId]) =>
-                
from(this.flowService.getProcessGroup(currentProcessGroupId)).pipe(
-                    map((response) =>
-                        FlowActions.openNewConnectionDialog({
-                            request: {
+    openNewConnectionDialog$ = createEffect(
+        () =>
+            this.actions$.pipe(
+                ofType(FlowActions.openNewConnectionDialog),
+                map((action) => action.request),
+                concatLatestFrom(() => 
this.store.select(selectCurrentProcessGroupId)),
+                switchMap(([request, currentProcessGroupId]) =>
+                    
from(this.flowService.getProcessGroup(currentProcessGroupId)).pipe(
+                        map((response) => {
+                            return {
                                 request,
                                 defaults: {
                                     flowfileExpiration: 
response.component.defaultFlowFileExpiration,
                                     objectThreshold: 
response.component.defaultBackPressureObjectThreshold,
                                     dataSizeThreshold: 
response.component.defaultBackPressureDataSizeThreshold
                                 }
+                            } as CreateConnectionDialogRequest;
+                        }),
+                        tap({
+                            error: (errorResponse: HttpErrorResponse) => {
+                                this.canvasUtils.removeTempEdge();
+                                
this.store.dispatch(FlowActions.flowSnackbarError({ error: errorResponse.error 
}));
                             }
                         })
-                    ),
-                    catchError((error) => of(FlowActions.flowApiError({ error: 
error.error })))
-                )
-            )
-        )
-    );
-
-    openNewConnectionDialog$ = createEffect(
-        () =>
-            this.actions$.pipe(
-                ofType(FlowActions.openNewConnectionDialog),
-                map((action) => action.request),
+                    )
+                ),
                 tap((request) => {
                     const dialogReference = this.dialog.open(CreateConnection, 
{
                         ...LARGE_DIALOG,
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/index.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/index.ts
index 440975350c..c0ce2dd56d 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/index.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/index.ts
@@ -101,6 +101,7 @@ export interface CreateComponentRequest {
 export interface CreateConnectionRequest {
     source: SelectedComponent;
     destination: SelectedComponent;
+    bends?: Position[];
 }
 
 export const loadBalanceStrategies: SelectOption[] = [
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/canvas/items/connection/create-connection/create-connection.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/canvas/items/connection/create-connection/create-connection.component.ts
index 669d8c5be8..034e77c091 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/connection/create-connection/create-connection.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/canvas/items/connection/create-connection/create-connection.component.ts
@@ -241,6 +241,7 @@ export class CreateConnection {
                 flowFileExpiration: 
this.createConnectionForm.get('flowFileExpiration')?.value,
                 loadBalanceStrategy: 
this.createConnectionForm.get('loadBalanceStrategy')?.value,
                 name: this.createConnectionForm.get('name')?.value,
+                labelIndex: 0,
                 prioritizers: 
this.createConnectionForm.get('prioritizers')?.value
             }
         };
@@ -296,6 +297,10 @@ export class CreateConnection {
             payload.component.loadBalanceCompression = 'DO_NOT_COMPRESS';
         }
 
+        if (this.dialogRequest.request.bends) {
+            payload.component.bends = this.dialogRequest.request.bends;
+        }
+
         this.store.dispatch(
             createConnection({
                 request: {

Reply via email to