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