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 13c70c0f30 NIFI-12737: Removing all transitions when updating the
Canvas transform (#8355)
13c70c0f30 is described below
commit 13c70c0f30a87b87d58ba85843df364a61e22385
Author: Matt Gilman <[email protected]>
AuthorDate: Tue Feb 6 15:04:35 2024 -0500
NIFI-12737: Removing all transitions when updating the Canvas transform
(#8355)
* NIFI-12737:
- Recording last canvas URL in local storage.
- Using last canvas URL in global menu Canvas item.
- Conditionally applying transitions when centering components.
- Always applying transitions during zoom events (1:1, fit, zoom in/out).
- Adding support to center more than one component.
* NIFI-12737:
- Fixing bug when attempting to click on the search result of the currently
selected component.
- Handling centering of a single selection different from a bulk selection
as it performs betters with Connections.
This closes #8355
---
.../service/canvas-context-menu.service.ts | 6 +-
.../flow-designer/service/canvas-utils.service.ts | 23 +++
.../flow-designer/service/canvas-view.service.ts | 214 +++++++++++++++------
.../service/manager/connection-manager.service.ts | 10 +-
.../pages/flow-designer/state/flow/flow.actions.ts | 24 ++-
.../pages/flow-designer/state/flow/flow.effects.ts | 25 ++-
.../pages/flow-designer/state/flow/flow.reducer.ts | 16 +-
.../flow-designer/state/flow/flow.selectors.ts | 4 +-
.../app/pages/flow-designer/state/flow/index.ts | 5 +
.../flow-designer/ui/canvas/canvas.component.ts | 33 +++-
.../flow-status/flow-status.component.spec.ts | 18 +-
.../ui/canvas/header/search/search.component.html | 15 +-
.../canvas/header/search/search.component.spec.ts | 7 +
.../ui/canvas/header/search/search.component.ts | 29 ++-
.../ui/common/navigation/navigation.component.html | 2 +-
.../ui/common/navigation/navigation.component.ts | 7 +
16 files changed, 340 insertions(+), 98 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/canvas-context-menu.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/canvas-context-menu.service.ts
index 5d083166e1..c819095656 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-context-menu.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/canvas-context-menu.service.ts
@@ -20,7 +20,7 @@ import { CanvasUtils } from './canvas-utils.service';
import { Store } from '@ngrx/store';
import { CanvasState } from '../state';
import {
- centerSelectedComponent,
+ centerSelectedComponents,
deleteComponents,
enterProcessGroup,
getParameterContextsAndOpenGroupComponentsDialog,
@@ -928,12 +928,12 @@ export class CanvasContextMenu implements
ContextMenuDefinitionProvider {
},
{
condition: (selection: any) => {
- return selection.size() === 1 &&
!this.canvasUtils.isConnection(selection);
+ return !selection.empty();
},
clazz: 'fa fa-crosshairs',
text: 'Center in view',
action: () => {
- this.store.dispatch(centerSelectedComponent());
+ this.store.dispatch(centerSelectedComponents({ request: {
allowTransition: true } }));
}
},
{
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/canvas-utils.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/canvas-utils.service.ts
index fa43903e36..4f3c284e82 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-utils.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/canvas-utils.service.ts
@@ -590,6 +590,28 @@ export class CanvasUtils {
});
}
+ /**
+ * Returns the position for centering a connection based on the presence
of bends.
+ *
+ * @param d connection data
+ */
+ public getPositionForCenteringConnection(d: any): Position {
+ let x, y;
+ if (d.bends.length > 0) {
+ const i: number = Math.min(Math.max(0, d.labelIndex),
d.bends.length - 1);
+ x = d.bends[i].x;
+ y = d.bends[i].y;
+ } else {
+ x = (d.start.x + d.end.x) / 2;
+ y = (d.start.y + d.end.y) / 2;
+ }
+
+ return {
+ x,
+ y
+ };
+ }
+
/**
* Returns the component id of the source of this processor. If the
connection is attached
* to a port in a [sub|remote] group, the component id will be that of the
group. Otherwise
@@ -1484,6 +1506,7 @@ export class CanvasUtils {
});
return canStopTransmitting;
}
+
/**
* Determines whether the components in the specified selection can be
operated.
*
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/canvas-view.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/canvas-view.service.ts
index ed70a7191c..699656dd86 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/canvas-view.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/canvas-view.service.ts
@@ -51,6 +51,7 @@ export class CanvasView {
private behavior: any;
private birdseyeTranslateInProgress = false;
+ private allowTransition = false;
constructor(
private store: Store<CanvasState>,
@@ -85,6 +86,10 @@ export class CanvasView {
this.svg = svg;
this.canvas = canvas;
+ this.k = INITIAL_SCALE;
+ this.x = INITIAL_TRANSLATE.x;
+ this.y = INITIAL_TRANSLATE.y;
+
this.labelManager.init();
this.funnelManager.init();
this.portManager.init(viewContainerRef);
@@ -118,7 +123,7 @@ export class CanvasView {
// refresh the canvas
refreshed = self.refresh({
- transition: self.shouldTransition(event.sourceEvent),
+ transition: self.shouldTransition(),
refreshComponents: false,
refreshBirdseye: false
});
@@ -170,18 +175,73 @@ export class CanvasView {
return this.birdseyeTranslateInProgress;
}
- // see if the scale has changed during this zoom event,
- // we want to only transition when zooming in/out as running
- // the transitions during pan events is undesirable
- private shouldTransition(sourceEvent: any): boolean {
+ private shouldTransition(): boolean {
if (this.birdseyeTranslateInProgress) {
return false;
}
- if (sourceEvent) {
- return sourceEvent.type === 'wheel' || sourceEvent.type ===
'mousewheel';
+ return this.allowTransition;
+ }
+
+ public isSelectedComponentOnScreen(): boolean {
+ const canvasContainer: any =
document.getElementById('canvas-container');
+
+ if (canvasContainer == null) {
+ return false;
+ }
+
+ const selection: any = this.canvasUtils.getSelection();
+ if (selection.size() !== 1) {
+ return false;
+ }
+ const d = selection.datum();
+
+ let translate = [this.x, this.y];
+ const scale = this.k;
+
+ // scale the translation
+ translate = [translate[0] / scale, translate[1] / scale];
+
+ // get the normalized screen width and height
+ const screenWidth = canvasContainer.offsetWidth / scale;
+ const screenHeight = canvasContainer.offsetHeight / scale;
+
+ // calculate the screen bounds one screens worth in each direction
+ const screenLeft = -translate[0];
+ const screenTop = -translate[1];
+ const screenRight = screenLeft + screenWidth;
+ const screenBottom = screenTop + screenHeight;
+
+ if (this.canvasUtils.isConnection(selection)) {
+ let connectionX, connectionY;
+ if (d.bends.length > 0) {
+ const i: number = Math.min(Math.max(0, d.labelIndex),
d.bends.length - 1);
+ connectionX = d.bends[i].x;
+ connectionY = d.bends[i].y;
+ } else {
+ connectionX = (d.start.x + d.end.x) / 2;
+ connectionY = (d.start.y + d.end.y) / 2;
+ }
+
+ return (
+ screenLeft < connectionX &&
+ screenRight > connectionX &&
+ screenTop < connectionY &&
+ screenBottom > connectionY
+ );
} else {
- return true;
+ const componentLeft: number = d.position.x;
+ const componentTop: number = d.position.y;
+ const componentRight: number = componentLeft + d.dimensions.width;
+ const componentBottom: number = componentTop + d.dimensions.height;
+
+ // determine if the component is now visible
+ return (
+ screenLeft < componentRight &&
+ screenRight > componentLeft &&
+ screenTop < componentBottom &&
+ screenBottom > componentTop
+ );
}
}
@@ -230,15 +290,7 @@ export class CanvasView {
return false;
}
- let x, y;
- if (d.bends.length > 0) {
- const i: number = Math.min(Math.max(0, d.labelIndex),
d.bends.length - 1);
- x = d.bends[i].x;
- y = d.bends[i].y;
- } else {
- x = (d.start.x + d.end.x) / 2;
- y = (d.start.y + d.end.y) / 2;
- }
+ const { x, y } =
self.canvasUtils.getPositionForCenteringConnection(d);
return screenLeft < x && screenRight > x && screenTop < y &&
screenBottom > y;
};
@@ -293,7 +345,7 @@ export class CanvasView {
}
/**
- * Whether or not a component should be rendered based solely on the
current scale.
+ * Whether a component should be rendered based solely on the current
scale.
*
* @returns {Boolean}
*/
@@ -301,45 +353,93 @@ export class CanvasView {
return this.k >= CanvasView.MIN_SCALE_TO_RENDER;
}
- public centerSelectedComponent(): void {
+ public centerSelectedComponents(allowTransition: boolean): void {
+ const canvasContainer: any =
document.getElementById('canvas-container');
+ if (canvasContainer == null) {
+ return;
+ }
+
const selection: any = this.canvasUtils.getSelection();
+ if (selection.empty()) {
+ return;
+ }
+
+ let bbox;
if (selection.size() === 1) {
- let box;
- if (this.canvasUtils.isConnection(selection)) {
- let x, y;
- const d = selection.datum();
-
- // get the position of the connection label
- if (d.bends.length > 0) {
- const i: number = Math.min(Math.max(0, d.labelIndex),
d.bends.length - 1);
- x = d.bends[i].x;
- y = d.bends[i].y;
- } else {
- x = (d.start.x + d.end.x) / 2;
- y = (d.start.y + d.end.y) / 2;
- }
+ bbox = this.getSingleSelectionBoundingClientRect(selection);
+ } else {
+ bbox = this.getBulkSelectionBoundingClientRect(selection,
canvasContainer);
+ }
- box = {
- x: x,
- y: y,
- width: 1,
- height: 1
- };
- } else {
- const selectionData = selection.datum();
- const selectionPosition = selectionData.position;
-
- box = {
- x: selectionPosition.x,
- y: selectionPosition.y,
- width: selectionData.dimensions.width,
- height: selectionData.dimensions.height
- };
- }
+ this.allowTransition = allowTransition;
+ this.centerBoundingBox(bbox);
+ this.allowTransition = false;
+ }
+
+ private getSingleSelectionBoundingClientRect(selection: any): any {
+ let bbox;
+ if (this.canvasUtils.isConnection(selection)) {
+ const d = selection.datum();
+
+ // get the position of the connection label
+ const { x, y } =
this.canvasUtils.getPositionForCenteringConnection(d);
- // center on the component
- this.centerBoundingBox(box);
+ bbox = {
+ x: x,
+ y: y,
+ width: 1,
+ height: 1
+ };
+ } else {
+ const selectionData = selection.datum();
+ const selectionPosition = selectionData.position;
+
+ bbox = {
+ x: selectionPosition.x,
+ y: selectionPosition.y,
+ width: selectionData.dimensions.width,
+ height: selectionData.dimensions.height
+ };
}
+
+ return bbox;
+ }
+
+ /**
+ * Get a BoundingClientRect, normalized to the canvas, that encompasses
all nodes in a given selection.
+ */
+ private getBulkSelectionBoundingClientRect(selection: any,
canvasContainer: any): any {
+ const canvasBoundingBox: any = canvasContainer.getBoundingClientRect();
+
+ const initialBBox: any = {
+ x: Number.MAX_VALUE,
+ y: Number.MAX_VALUE,
+ right: Number.MIN_VALUE,
+ bottom: Number.MIN_VALUE
+ };
+
+ const bbox = selection.nodes().reduce((aggregateBBox: any, node: any)
=> {
+ const rect = node.getBoundingClientRect();
+ aggregateBBox.x = Math.min(rect.x, aggregateBBox.x);
+ aggregateBBox.y = Math.min(rect.y, aggregateBBox.y);
+ aggregateBBox.right = Math.max(rect.right, aggregateBBox.right);
+ aggregateBBox.bottom = Math.max(rect.bottom, aggregateBBox.bottom);
+
+ return aggregateBBox;
+ }, initialBBox);
+
+ // normalize the bounding box with scale and translate
+ bbox.x = (bbox.x - this.x) / this.k;
+ bbox.y = (bbox.y - canvasBoundingBox.top - this.y) / this.k;
+ bbox.right = (bbox.right - this.x) / this.k;
+ bbox.bottom = (bbox.bottom - canvasBoundingBox.top - this.y) / this.k;
+
+ bbox.width = bbox.right - bbox.x;
+ bbox.height = bbox.bottom - bbox.y;
+ bbox.top = bbox.y;
+ bbox.left = bbox.x;
+
+ return bbox;
}
private centerBoundingBox(boundingBox: any): void {
@@ -430,14 +530,18 @@ export class CanvasView {
* Zooms in a single zoom increment.
*/
public zoomIn(): void {
+ this.allowTransition = true;
this.scale(CanvasView.INCREMENT);
+ this.allowTransition = false;
}
/**
* Zooms out a single zoom increment.
*/
public zoomOut(): void {
+ this.allowTransition = true;
this.scale(1 / CanvasView.INCREMENT);
+ this.allowTransition = false;
}
/**
@@ -476,7 +580,7 @@ export class CanvasView {
graphTop -= 50;
}
- // center as appropriate
+ this.allowTransition = true;
this.centerBoundingBox({
x: graphLeft - translate[0] / scale,
y: graphTop - translate[1] / scale,
@@ -484,6 +588,7 @@ export class CanvasView {
height: canvasHeight / newScale,
scale: newScale
});
+ this.allowTransition = false;
}
/**
@@ -530,8 +635,9 @@ export class CanvasView {
};
}
- // center as appropriate
+ this.allowTransition = true;
this.centerBoundingBox(box);
+ this.allowTransition = false;
}
/**
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 b2e23f111b..1fbc0d7f03 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
@@ -109,15 +109,7 @@ export class ConnectionManager {
private getLabelPosition(connectionLabel: any): Position {
const d = connectionLabel.datum();
- let x, y;
- if (d.bends.length > 0) {
- const i: number = Math.min(Math.max(0, d.labelIndex),
d.bends.length - 1);
- x = d.bends[i].x;
- y = d.bends[i].y;
- } else {
- x = (d.start.x + d.end.x) / 2;
- y = (d.start.y + d.end.y) / 2;
- }
+ let { x, y } = this.canvasUtils.getPositionForCenteringConnection(d);
// offset to account for the label dimensions
x -= ConnectionManager.DIMENSIONS.width / 2;
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 2135726913..53f7f10f2d 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
@@ -70,7 +70,8 @@ import {
UploadProcessGroupRequest,
NavigateToQueueListing,
StartProcessGroupResponse,
- StopProcessGroupResponse
+ StopProcessGroupResponse,
+ CenterComponentRequest
} from './index';
import { StatusHistoryRequest } from '../../../../state/status-history';
@@ -186,7 +187,10 @@ export const removeSelectedComponents = createAction(
props<{ request: SelectComponentsRequest }>()
);
-export const centerSelectedComponent = createAction(`${CANVAS_PREFIX} Center
Selected Component`);
+export const centerSelectedComponents = createAction(
+ `${CANVAS_PREFIX} Center Selected Components`,
+ props<{ request: CenterComponentRequest }>()
+);
/*
Create Component Actions
@@ -425,11 +429,27 @@ export const setTransitionRequired = createAction(
props<{ transitionRequired: boolean }>()
);
+/**
+ * skipTransform is used when handling URL events for loading the current PG
and component [bulk] selection. since the
+ * URL is the source of truth we need to indicate skipTransform when the URL
changes based on the user selection on
+ * the canvas. However, we do not want the transform skipped when using link
to open or a particular part of the flow.
+ * In these cases, we want the transform to be applied so the viewport is
restored or the component(s) is centered.
+ */
export const setSkipTransform = createAction(
`${CANVAS_PREFIX} Set Skip Transform`,
props<{ skipTransform: boolean }>()
);
+/**
+ * allowTransition is a flag that can be set that indicates if a transition
should be used when applying a transform.
+ * By default, restoring the viewport or selecting/centering components will
not use a transition unless explicitly
+ * specified. Zoom based transforms (like fit or 1:1) will always use a
transition.
+ */
+export const setAllowTransition = createAction(
+ `${CANVAS_PREFIX} Set Allow Transition`,
+ props<{ allowTransition: boolean }>()
+);
+
export const navigateToComponent = createAction(
`${CANVAS_PREFIX} Navigate To Component`,
props<{ request: NavigateToComponentRequest }>()
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 0fbd0a6289..05ca83d1f3 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
@@ -636,7 +636,12 @@ export class FlowEffects {
map((action) => action.request),
concatLatestFrom(() =>
this.store.select(selectCurrentProcessGroupId)),
tap(([request, processGroupId]) => {
- this.router.navigate(['/process-groups', processGroupId,
request.type, request.id, 'edit']);
+ const url = ['/process-groups', processGroupId,
request.type, request.id, 'edit'];
+ if (this.canvasView.isSelectedComponentOnScreen()) {
+
this.store.dispatch(FlowActions.navigateWithoutTransform({ url }));
+ } else {
+ this.router.navigate(url);
+ }
})
),
{ dispatch: false }
@@ -1771,15 +1776,15 @@ export class FlowEffects {
{ dispatch: false }
);
- centerSelectedComponent$ = createEffect(
- () =>
- this.actions$.pipe(
- ofType(FlowActions.centerSelectedComponent),
- tap(() => {
- this.canvasView.centerSelectedComponent();
- })
- ),
- { dispatch: false }
+ centerSelectedComponents$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowActions.centerSelectedComponents),
+ map((action) => action.request),
+ tap((request) => {
+
this.canvasView.centerSelectedComponents(request.allowTransition);
+ }),
+ switchMap(() => of(FlowActions.setAllowTransition({
allowTransition: false })))
+ )
);
navigateToProvenanceForComponent$ = createEffect(
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.reducer.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.reducer.ts
index 1b5a54d707..6daf2432d9 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.reducer.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.reducer.ts
@@ -41,6 +41,7 @@ import {
resetFlowState,
runOnce,
runOnceSuccess,
+ setAllowTransition,
setDragging,
setNavigationCollapsed,
setOperationCollapsed,
@@ -137,6 +138,7 @@ export const initialState: FlowState = {
saving: false,
transitionRequired: false,
skipTransform: false,
+ allowTransition: false,
navigationCollapsed: false,
operationCollapsed: false,
error: null,
@@ -297,15 +299,19 @@ export const flowReducer = createReducer(
}),
on(setDragging, (state, { dragging }) => ({
...state,
- dragging: dragging
+ dragging
})),
on(setTransitionRequired, (state, { transitionRequired }) => ({
...state,
- transitionRequired: transitionRequired
+ transitionRequired
})),
on(setSkipTransform, (state, { skipTransform }) => ({
...state,
- skipTransform: skipTransform
+ skipTransform
+ })),
+ on(setAllowTransition, (state, { allowTransition }) => ({
+ ...state,
+ allowTransition
})),
on(navigateWithoutTransform, (state) => ({
...state,
@@ -313,11 +319,11 @@ export const flowReducer = createReducer(
})),
on(setNavigationCollapsed, (state, { navigationCollapsed }) => ({
...state,
- navigationCollapsed: navigationCollapsed
+ navigationCollapsed
})),
on(setOperationCollapsed, (state, { operationCollapsed }) => ({
...state,
- operationCollapsed: operationCollapsed
+ operationCollapsed
})),
on(startComponentSuccess, stopComponentSuccess, (state, { response }) => {
return produce(state, (draftState) => {
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.selectors.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.selectors.ts
index 5c2c3d1b25..e940b480ff 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.selectors.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.selectors.ts
@@ -80,7 +80,7 @@ export const selectAnySelectedComponentIds =
createSelector(selectCurrentRoute,
export const selectBulkSelectedComponentIds =
createSelector(selectCurrentRoute, (route) => {
const ids: string[] = [];
- // only handle either bulk component route
+ // only handle bulk component route
if (route?.params.ids) {
ids.push(...route.params.ids.split(','));
}
@@ -140,6 +140,8 @@ export const selectDragging =
createSelector(selectFlowState, (state: FlowState)
export const selectSkipTransform = createSelector(selectFlowState, (state:
FlowState) => state.skipTransform);
+export const selectAllowTransition = createSelector(selectFlowState, (state:
FlowState) => state.allowTransition);
+
export const selectFunnels = createSelector(
selectFlowState,
(state: FlowState) => state.flow.processGroupFlow?.flow.funnels
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 c19437aa9c..1352c36e2b 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
@@ -40,6 +40,10 @@ export interface SelectComponentsRequest {
components: SelectedComponent[];
}
+export interface CenterComponentRequest {
+ allowTransition: boolean;
+}
+
/*
Load Process Group
*/
@@ -473,6 +477,7 @@ export interface FlowState {
dragging: boolean;
transitionRequired: boolean;
skipTransform: boolean;
+ allowTransition: boolean;
saving: boolean;
navigationCollapsed: boolean;
operationCollapsed: boolean;
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/canvas.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/canvas.component.ts
index 62a1779df8..cb10ccb727 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/canvas.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/canvas.component.ts
@@ -20,7 +20,7 @@ import { CanvasState } from '../../state';
import { Position } from '../../state/shared';
import { Store } from '@ngrx/store';
import {
- centerSelectedComponent,
+ centerSelectedComponents,
deselectAllComponents,
editComponent,
editCurrentProcessGroup,
@@ -38,6 +38,7 @@ import { selectTransform } from
'../../state/transform/transform.selectors';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { SelectedComponent } from '../../state/flow';
import {
+ selectAllowTransition,
selectBulkSelectedComponentIds,
selectConnection,
selectCurrentProcessGroupId,
@@ -57,13 +58,15 @@ import {
selectViewStatusHistoryComponent
} from '../../state/flow/flow.selectors';
import { filter, map, switchMap, take } from 'rxjs';
-import { restoreViewport, zoomFit } from
'../../state/transform/transform.actions';
+import { restoreViewport } from '../../state/transform/transform.actions';
import { ComponentType, isDefinedAndNotNull } from '../../../../state/shared';
import { initialState } from '../../state/flow/flow.reducer';
import { CanvasContextMenu } from '../../service/canvas-context-menu.service';
import { getStatusHistoryAndOpenDialog } from
'../../../../state/status-history/status-history.actions';
import { loadFlowConfiguration } from
'../../../../state/flow-configuration/flow-configuration.actions';
import { concatLatestFrom } from '@ngrx/effects';
+import { selectUrl } from '../../../../state/router/router.selectors';
+import { Storage } from '../../../../service/storage.service';
@Component({
selector: 'fd-canvas',
@@ -81,6 +84,7 @@ export class Canvas implements OnInit, OnDestroy {
private viewContainerRef: ViewContainerRef,
private store: Store<CanvasState>,
private canvasView: CanvasView,
+ private storage: Storage,
public canvasContextMenu: CanvasContextMenu
) {
this.store
@@ -90,6 +94,13 @@ export class Canvas implements OnInit, OnDestroy {
this.scale = transform.scale;
});
+ this.store
+ .select(selectUrl)
+ .pipe(takeUntilDestroyed())
+ .subscribe((route) => {
+ this.storage.setItem('current-canvas-route', route);
+ });
+
// load the process group from the route
this.store
.select(selectProcessGroupIdFromRoute)
@@ -133,14 +144,17 @@ export class Canvas implements OnInit, OnDestroy {
filter((processGroupId) => processGroupId != initialState.id),
switchMap(() =>
this.store.select(selectSingleSelectedComponent)),
filter((selectedComponent) => selectedComponent != null),
- concatLatestFrom(() => this.store.select(selectSkipTransform)),
+ concatLatestFrom(() => [
+ this.store.select(selectSkipTransform),
+ this.store.select(selectAllowTransition)
+ ]),
takeUntilDestroyed()
)
- .subscribe(([, skipTransform]) => {
+ .subscribe(([, skipTransform, allowTransition]) => {
if (skipTransform) {
this.store.dispatch(setSkipTransform({ skipTransform:
false }));
} else {
- this.store.dispatch(centerSelectedComponent());
+ this.store.dispatch(centerSelectedComponents({ request: {
allowTransition } }));
}
});
@@ -151,14 +165,17 @@ export class Canvas implements OnInit, OnDestroy {
filter((processGroupId) => processGroupId != initialState.id),
switchMap(() =>
this.store.select(selectBulkSelectedComponentIds)),
filter((ids) => ids.length > 0),
- concatLatestFrom(() => this.store.select(selectSkipTransform)),
+ concatLatestFrom(() => [
+ this.store.select(selectSkipTransform),
+ this.store.select(selectAllowTransition)
+ ]),
takeUntilDestroyed()
)
- .subscribe(([, skipTransform]) => {
+ .subscribe(([, skipTransform, allowTransition]) => {
if (skipTransform) {
this.store.dispatch(setSkipTransform({ skipTransform:
false }));
} else {
- this.store.dispatch(zoomFit());
+ this.store.dispatch(centerSelectedComponents({ request: {
allowTransition } }));
}
});
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/header/flow-status/flow-status.component.spec.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/header/flow-status/flow-status.component.spec.ts
index 06ad1f4a40..30b37b5898 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/header/flow-status/flow-status.component.spec.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/header/flow-status/flow-status.component.spec.ts
@@ -18,27 +18,41 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FlowStatus } from './flow-status.component';
-import { Search } from '../search/search.component';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { Component } from '@angular/core';
+import { provideMockStore } from '@ngrx/store/testing';
+import { initialState } from '../../../../state/flow/flow.reducer';
describe('FlowStatus', () => {
let component: FlowStatus;
let fixture: ComponentFixture<FlowStatus>;
+ @Component({
+ selector: 'search',
+ standalone: true,
+ template: ''
+ })
+ class MockSearch {}
+
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
FlowStatus,
- Search,
+ MockSearch,
HttpClientTestingModule,
CdkOverlayOrigin,
CdkConnectedOverlay,
MatAutocompleteModule,
FormsModule,
ReactiveFormsModule
+ ],
+ providers: [
+ provideMockStore({
+ initialState
+ })
]
});
fixture = TestBed.createComponent(FlowStatus);
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/header/search/search.component.html
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/header/search/search.component.html
index fc124d06a5..62257addd4 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/header/search/search.component.html
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/header/search/search.component.html
@@ -186,10 +186,21 @@
</li>
<!-- TODO - Consider showing more context of match like existing
UI -->
<li *ngFor="let result of results" class="ml-2 py-1">
- <a *ngIf="!result.parentGroup; else resultLink"
[routerLink]="['/process-groups', result.id]">
+ <a *ngIf="!result.parentGroup; else componentLink"
[routerLink]="['/process-groups', result.id]">
{{ result.name }}
</a>
- <ng-template #resultLink>
+ <ng-template #componentLink>
+ <a
+ *ngIf="
+ result.parentGroup.id == currentProcessGroupId;
+ else componentInDifferentProcessGroupLink
+ "
+ (click)="componentLinkClicked(path, result.id)"
+ [routerLink]="['/process-groups',
result.parentGroup.id, path, result.id]">
+ {{ result.name ? result.name : result.id }}
+ </a>
+ </ng-template>
+ <ng-template #componentInDifferentProcessGroupLink>
<a [routerLink]="['/process-groups',
result.parentGroup.id, path, result.id]">
{{ result.name ? result.name : result.id }}
</a>
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/header/search/search.component.spec.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/header/search/search.component.spec.ts
index 6cabc8a946..e53daf63b7 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/header/search/search.component.spec.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/header/search/search.component.spec.ts
@@ -22,6 +22,8 @@ import { HttpClientTestingModule } from
'@angular/common/http/testing';
import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
+import { provideMockStore } from '@ngrx/store/testing';
+import { initialState } from '../../../../state/flow/flow.reducer';
describe('Search', () => {
let component: Search;
@@ -37,6 +39,11 @@ describe('Search', () => {
ReactiveFormsModule,
CdkConnectedOverlay,
MatAutocompleteModule
+ ],
+ providers: [
+ provideMockStore({
+ initialState
+ })
]
});
fixture = TestBed.createComponent(Search);
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/header/search/search.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/header/search/search.component.ts
index 4e7b2eef5f..402ed7460d 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/header/search/search.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/header/search/search.component.ts
@@ -32,6 +32,11 @@ import { NgForOf, NgIf, NgTemplateOutlet } from
'@angular/common';
import { RouterLink } from '@angular/router';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
+import { CanvasState } from '../../../../state';
+import { Store } from '@ngrx/store';
+import { centerSelectedComponents, setAllowTransition } from
'../../../../state/flow/flow.actions';
+import { selectCurrentRoute } from
'../../../../../../state/router/router.selectors';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'search',
@@ -86,11 +91,25 @@ export class Search implements OnInit {
parameterProviderNodeResults: ComponentSearchResult[] = [];
parameterResults: ComponentSearchResult[] = [];
+ selectedComponentType: ComponentType | null = null;
+ selectedComponentId: string | null = null;
+
constructor(
private formBuilder: FormBuilder,
- private searchService: SearchService
+ private searchService: SearchService,
+ private store: Store<CanvasState>
) {
this.searchForm = this.formBuilder.group({ searchBar: '' });
+
+ this.store
+ .select(selectCurrentRoute)
+ .pipe(takeUntilDestroyed())
+ .subscribe((route) => {
+ if (route?.params) {
+ this.selectedComponentId = route.params.id;
+ this.selectedComponentType = route.params.type;
+ }
+ });
}
ngOnInit(): void {
@@ -169,4 +188,12 @@ export class Search implements OnInit {
this.parameterProviderNodeResults = [];
this.parameterResults = [];
}
+
+ componentLinkClicked(componentType: ComponentType, id: string): void {
+ if (componentType == this.selectedComponentType && id ==
this.selectedComponentId) {
+ this.store.dispatch(centerSelectedComponents({ request: {
allowTransition: true } }));
+ } else {
+ this.store.dispatch(setAllowTransition({ allowTransition: true }));
+ }
+ }
}
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.html
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.html
index 16450e9bf7..0684c760a9 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.html
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.html
@@ -43,7 +43,7 @@
<i class="fa fa-navicon"></i>
</button>
<mat-menu #globalMenu="matMenu" xPosition="before">
- <button mat-menu-item class="global-menu-item"
[routerLink]="['/']">
+ <button mat-menu-item class="global-menu-item"
[routerLink]="getCanvasLink()">
<i class="icon icon-drop mr-2"></i>
Canvas
</button>
diff --git
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.ts
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.ts
index 0f6bbd0a54..617139733b 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.ts
+++
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/navigation/navigation.component.ts
@@ -32,6 +32,7 @@ import { MatButtonModule } from '@angular/material/button';
import { NiFiState } from '../../../state';
import { selectFlowConfiguration } from
'../../../state/flow-configuration/flow-configuration.selectors';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
+import { Storage } from '../../../service/storage.service';
@Component({
selector: 'navigation',
@@ -59,6 +60,7 @@ export class Navigation {
private store: Store<NiFiState>,
private authStorage: AuthStorage,
private authService: AuthService,
+ private storage: Storage,
@Inject(DOCUMENT) private _document: Document
) {}
@@ -94,6 +96,11 @@ export class Navigation {
);
}
+ getCanvasLink(): string {
+ const canvasRoute = this.storage.getItem('current-canvas-route');
+ return canvasRoute || '/';
+ }
+
toggleTheme(value = !this.isDarkMode) {
this.isDarkMode = value;
this._document.body.classList.toggle('dark-theme', value);