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 235faf3170..3551dd1841 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 @@ -52,7 +52,8 @@ import { stopComponents, stopCurrentProcessGroup, stopVersionControlRequest, - downloadFlow + downloadFlow, + moveToFront } from '../state/flow/flow.actions'; import { ComponentType } from '../../../state/shared'; import { @@ -73,6 +74,7 @@ import { promptEmptyQueueRequest, promptEmptyQueuesRequest } from '../state/queu import { getComponentStateAndOpenDialog } from '../../../state/component-state/component-state.actions'; import { navigateToComponentDocumentation } from '../../../state/documentation/documentation.actions'; import * as d3 from 'd3'; +import { Client } from '../../../service/client.service'; @Injectable({ providedIn: 'root' }) export class CanvasContextMenu implements ContextMenuDefinitionProvider { @@ -557,7 +559,7 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider { id: d.id, uri: d.uri, type: d.type, - revision: d.revision + revision: this.client.getRevision(d) }); }); this.store.dispatch( @@ -597,7 +599,7 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider { id: d.id, uri: d.uri, type: d.type, - revision: d.revision + revision: this.client.getRevision(d) }); }); this.store.dispatch( @@ -625,7 +627,7 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider { runOnce({ request: { uri: d.uri, - revision: d.revision + revision: this.client.getRevision(d) } }) ); @@ -678,7 +680,7 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider { id: d.id, uri: d.uri, type: d.type, - revision: d.revision + revision: this.client.getRevision(d) }); }); @@ -706,7 +708,7 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider { id: d.id, uri: d.uri, type: d.type, - revision: d.revision + revision: this.client.getRevision(d) }); }); this.store.dispatch( @@ -982,12 +984,12 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider { isSeparator: true }, { - condition: (selection: any) => { + condition: (selection: d3.Selection) => { return this.canvasUtils.isConnection(selection); }, clazz: 'fa fa-long-arrow-left', text: 'Go to source', - action: (selection: any) => { + action: (selection: d3.Selection) => { const selectionData = selection.datum(); const remoteConnectableType: string = this.canvasUtils.getConnectableTypeForSource( ComponentType.RemoteProcessGroup @@ -1023,12 +1025,12 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider { } }, { - condition: (selection: any) => { + condition: (selection: d3.Selection) => { return this.canvasUtils.isConnection(selection); }, clazz: 'fa fa-long-arrow-right', text: 'Go to destination', - action: (selection: any) => { + action: (selection: d3.Selection) => { const selectionData = selection.datum(); const remoteConnectableType: string = this.canvasUtils.getConnectableTypeForDestination( ComponentType.RemoteProcessGroup @@ -1072,18 +1074,29 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider { subMenuId: this.ALIGN.id }, { - condition: (selection: any) => { - // TODO - canMoveToFront - return false; + condition: (selection: d3.Selection) => { + return this.canvasUtils.canMoveToFront(selection); }, clazz: 'fa fa-clone', text: 'Bring to front', - action: () => { - // TODO - toFront + action: (selection: d3.Selection) => { + const selectionData = selection.datum(); + + this.store.dispatch( + moveToFront({ + request: { + componentType: selectionData.type, + id: selectionData.id, + uri: selectionData.uri, + revision: this.client.getRevision(selectionData), + zIndex: selectionData.zIndex + } + }) + ); } }, { - condition: (selection: any) => { + condition: (selection: d3.Selection) => { return !selection.empty(); }, clazz: 'fa fa-crosshairs', @@ -1104,12 +1117,12 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider { } }, { - condition: (selection: any) => { + condition: (selection: d3.Selection) => { return this.canvasUtils.canRead(selection) && this.canvasUtils.isRemoteProcessGroup(selection); }, clazz: 'fa fa-external-link', text: 'Go to', - action: (selection: any) => { + action: (selection: d3.Selection) => { const selectionData = selection.datum(); const uri = selectionData.component.targetUri; @@ -1120,12 +1133,12 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider { isSeparator: true }, { - condition: (selection: any) => { + condition: (selection: d3.Selection) => { return this.canvasUtils.isNotRootGroup(); }, clazz: 'fa fa-arrows', text: 'Move to parent group', - action: (selection: any) => { + action: (selection: d3.Selection) => { const components: MoveComponentRequest[] = []; selection.each(function (d: any) { components.push({ @@ -1300,7 +1313,8 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider { constructor( private store: Store, - private canvasUtils: CanvasUtils + private canvasUtils: CanvasUtils, + private client: Client ) { this.allMenus = new Map(); this.allMenus.set(this.ROOT_MENU.id, this.ROOT_MENU); 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 795b4adc9d..1538a6c1d2 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 @@ -1711,4 +1711,16 @@ export class CanvasUtils { return false; } + + public canMoveToFront(selection: d3.Selection): boolean { + // ensure the correct number of components are selected + if (selection.size() !== 1) { + return false; + } + if (this.canModify(selection) === false) { + return false; + } + + return this.isConnection(selection) || this.isLabel(selection); + } } 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 5debbbc881..78b2bb1425 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 @@ -270,8 +270,8 @@ export class ConnectionManager { * @param {type} connections */ private sort(connections: any[]): void { - connections.sort(function (a, b) { - return a.zIndex === b.zIndex ? 0 : a.zIndex > b.zIndex ? 1 : -1; + connections.sort((a, b) => { + return this.nifiCommon.compareNumber(a.zIndex, b.zIndex); }); } 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/label-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/label-manager.service.ts index 2aa98d4f85..cbdbb086dc 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/label-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/label-manager.service.ts @@ -76,6 +76,17 @@ export class LabelManager { }); } + /** + * Sorts the specified labels according to the z index. + * + * @param {type} labels + */ + private sort(labels: any[]): void { + labels.sort((a, b) => { + return this.nifiCommon.compareNumber(a.zIndex, b.zIndex); + }); + } + private renderLabels(entered: any) { if (entered.empty()) { return entered; @@ -393,6 +404,7 @@ export class LabelManager { // update const updated = selection.merge(entered); this.updateLabels(updated); + this.sort(updated); // position this.positionBehavior.position(updated, this.transitionRequired); 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 7c4ff4e600..ee58813aa7 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 @@ -54,6 +54,7 @@ import { LoadRemoteProcessGroupSuccess, LocalChangesDialogRequest, MoveComponentsRequest, + MoveToFrontRequest, NavigateToComponentRequest, NavigateToControllerServicesRequest, NavigateToManageComponentPoliciesRequest, @@ -766,3 +767,5 @@ export const downloadFlow = createAction( `${CANVAS_PREFIX} Download Flow Request`, props<{ request: DownloadFlowRequest }>() ); + +export const moveToFront = createAction(`${CANVAS_PREFIX} Move To Front`, props<{ request: MoveToFrontRequest }>()); 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 bb8ed4d864..dff3184311 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 @@ -51,6 +51,7 @@ import { StopVersionControlRequest, StopVersionControlResponse, UpdateComponentFailure, + UpdateComponentRequest, UpdateComponentResponse, UpdateConnectionSuccess, UpdateProcessorRequest, @@ -62,6 +63,7 @@ import { selectChangeVersionRequest, selectCurrentParameterContext, selectCurrentProcessGroupId, + selectMaxZIndex, selectParentProcessGroupId, selectProcessGroup, selectProcessor, @@ -113,6 +115,7 @@ import { SaveVersionDialog } from '../../ui/canvas/items/flow/save-version-dialo import { ChangeVersionDialog } from '../../ui/canvas/items/flow/change-version-dialog/change-version-dialog'; import { ChangeVersionProgressDialog } from '../../ui/canvas/items/flow/change-version-progress-dialog/change-version-progress-dialog'; import { LocalChangesDialog } from '../../ui/canvas/items/flow/local-changes-dialog/local-changes-dialog'; +import { ClusterConnectionService } from '../../../../service/cluster-connection.service'; @Injectable() export class FlowEffects { @@ -127,6 +130,7 @@ export class FlowEffects { private canvasView: CanvasView, private birdseyeView: BirdseyeView, private connectionManager: ConnectionManager, + private clusterConnectionService: ClusterConnectionService, private router: Router, private dialog: MatDialog, private propertyTableHelperService: PropertyTableHelperService, @@ -2846,7 +2850,7 @@ export class FlowEffects { version: selectedVersion.version }, processGroupRevision: request.revision, - disconnectedNodeAcknowledged: false + disconnectedNodeAcknowledged: this.clusterConnectionService.isDisconnectionAcknowledged() }; dialogRef.close(); @@ -3153,4 +3157,42 @@ export class FlowEffects { ), { dispatch: false } ); + + moveToFront$ = createEffect(() => + this.actions$.pipe( + ofType(FlowActions.moveToFront), + map((action) => action.request), + concatLatestFrom((request) => this.store.select(selectMaxZIndex(request.componentType))), + filter(([request, maxZIndex]) => request.zIndex < maxZIndex), + switchMap(([request, maxZIndex]) => { + const updateRequest: UpdateComponentRequest = { + id: request.id, + type: request.componentType, + uri: request.uri, + payload: { + revision: request.revision, + disconnectedNodeAcknowledged: this.clusterConnectionService.isDisconnectionAcknowledged(), + component: { + id: request.id, + zIndex: maxZIndex + 1 + } + } + }; + + return from(this.flowService.updateComponent(updateRequest)).pipe( + map((response) => { + const updateResponse: UpdateComponentResponse = { + id: updateRequest.id, + type: updateRequest.type, + response + }; + return FlowActions.updateComponentSuccess({ response: updateResponse }); + }), + catchError((errorResponse: HttpErrorResponse) => + of(FlowActions.flowSnackbarError({ error: errorResponse.error })) + ) + ); + }) + ) + ); } 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 f65666b22c..01a75fc101 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 @@ -19,6 +19,7 @@ import { flowFeatureKey, FlowState, SelectedComponent } from './index'; import { createSelector } from '@ngrx/store'; import { CanvasState, selectCanvasState } from '../index'; import { selectCurrentRoute } from '../../../../state/router/router.selectors'; +import { ComponentType } from '../../../../state/shared'; export const selectFlowState = createSelector(selectCanvasState, (state: CanvasState) => state[flowFeatureKey]); @@ -246,3 +247,15 @@ export const selectNavigationCollapsed = createSelector( ); export const selectOperationCollapsed = createSelector(selectFlowState, (state: FlowState) => state.operationCollapsed); + +export const selectMaxZIndex = (componentType: ComponentType.Connection | ComponentType.Label) => { + if (componentType === ComponentType.Connection) { + return createSelector(selectConnections, (connections: any[]) => + connections.reduce((maxZIndex, connection) => Math.max(maxZIndex, connection.zIndex), -1) + ); + } else { + return createSelector(selectLabels, (labels: any[]) => + labels.reduce((maxZIndex, label) => Math.max(maxZIndex, label.zIndex), -1) + ); + } +}; 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 8fd4d34947..1eb052cf38 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 @@ -739,3 +739,11 @@ export interface DownloadFlowRequest { processGroupId: string; includeReferencedServices: boolean; } + +export interface MoveToFrontRequest { + componentType: ComponentType.Connection | ComponentType.Label; + id: string; + uri: string; + revision: Revision; + zIndex: number; +}