You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by rf...@apache.org on 2024/04/19 21:58:32 UTC
(nifi) branch main updated: NIFI-13065: Adding initial bend points for self looping connections and connections that collide with existing connections (#8671)
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 <ma...@gmail.com>
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: {