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 562eece6e3 NIFI-13078: Adding support to Enable and Disable through
the context menu and operation control (#8680)
562eece6e3 is described below
commit 562eece6e3c1eec8d3eec15fd15d9fb986cbc433
Author: Matt Gilman <[email protected]>
AuthorDate: Tue Apr 23 16:47:25 2024 -0400
NIFI-13078: Adding support to Enable and Disable through the context menu
and operation control (#8680)
* NIFI-13078:
- Adding support to Enable and Disable through the context menu and
operation control.
* NIFI-13078:
- Addressing review feedback.
This closes #8680
---
.../service/canvas-context-menu.service.ts | 74 +++++-
.../flow-designer/service/canvas-utils.service.ts | 83 +++++++
.../pages/flow-designer/service/flow.service.ts | 40 +++
.../pages/flow-designer/state/flow/flow.actions.ts | 54 ++++
.../pages/flow-designer/state/flow/flow.effects.ts | 272 +++++++++++++++++++--
.../pages/flow-designer/state/flow/flow.reducer.ts | 65 +++--
.../app/pages/flow-designer/state/flow/index.ts | 62 ++++-
.../operation-control.component.ts | 110 ++++++---
8 files changed, 683 insertions(+), 77 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 8e6ec08eee..938152634a 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
@@ -58,13 +58,19 @@ import {
copy,
paste,
terminateThreads,
- navigateToParameterContext
+ navigateToParameterContext,
+ enableCurrentProcessGroup,
+ enableComponents,
+ disableCurrentProcessGroup,
+ disableComponents
} from '../state/flow/flow.actions';
import { ComponentType } from '../../../state/shared';
import {
ConfirmStopVersionControlRequest,
CopyComponentRequest,
DeleteComponentRequest,
+ DisableComponentRequest,
+ EnableComponentRequest,
MoveComponentRequest,
OpenChangeVersionDialogRequest,
OpenLocalChangesDialogRequest,
@@ -573,8 +579,9 @@ export class CanvasContextMenu implements
ContextMenuDefinitionProvider {
// are runnable or can start transmitting. However, if all
the startable components are RGPs, we will defer
// to the Enable Transmission menu option and not show the
start option.
const allRpgs =
+ !startable.empty() &&
startable.filter((d: any) => d.type ===
ComponentType.RemoteProcessGroup).size() ===
- startable.size();
+ startable.size();
return this.canvasUtils.areAnyRunnable(selection) &&
!allRpgs;
},
@@ -613,8 +620,9 @@ export class CanvasContextMenu implements
ContextMenuDefinitionProvider {
// are runnable or can stop transmitting. However, if all
the stoppable components are RGPs, we will defer
// to the Disable Transmission menu option and not show
the start option.
const allRpgs =
+ !stoppable.empty() &&
stoppable.filter((d: any) => d.type ===
ComponentType.RemoteProcessGroup).size() ===
- stoppable.size();
+ stoppable.size();
return this.canvasUtils.areAnyStoppable(selection) &&
!allRpgs;
},
@@ -685,25 +693,65 @@ export class CanvasContextMenu implements
ContextMenuDefinitionProvider {
}
},
{
- condition: (selection: any) => {
- // TODO - canEnable
- return false;
+ condition: (selection: d3.Selection<any, any, any, any>) => {
+ return this.canvasUtils.canEnable(selection);
},
clazz: 'fa fa-flash',
text: 'Enable',
- action: () => {
- // TODO - enable
+ action: (selection: d3.Selection<any, any, any, any>) => {
+ if (selection.empty()) {
+ // attempting to enable the current process group
+ this.store.dispatch(enableCurrentProcessGroup());
+ } else {
+ const components: EnableComponentRequest[] = [];
+ const enableable =
this.canvasUtils.filterEnable(selection);
+ enableable.each((d: any) => {
+ components.push({
+ id: d.id,
+ uri: d.uri,
+ type: d.type,
+ revision: this.client.getRevision(d)
+ });
+ });
+ this.store.dispatch(
+ enableComponents({
+ request: {
+ components
+ }
+ })
+ );
+ }
}
},
{
- condition: (selection: any) => {
- // TODO - canDisable
- return false;
+ condition: (selection: d3.Selection<any, any, any, any>) => {
+ return this.canvasUtils.canDisable(selection);
},
clazz: 'icon icon-enable-false',
text: 'Disable',
- action: () => {
- // TODO - disable
+ action: (selection: d3.Selection<any, any, any, any>) => {
+ if (selection.empty()) {
+ // attempting to disable the current process group
+ this.store.dispatch(disableCurrentProcessGroup());
+ } else {
+ const components: DisableComponentRequest[] = [];
+ const disableable =
this.canvasUtils.filterDisable(selection);
+ disableable.each((d: any) => {
+ components.push({
+ id: d.id,
+ uri: d.uri,
+ type: d.type,
+ revision: this.client.getRevision(d)
+ });
+ });
+ this.store.dispatch(
+ disableComponents({
+ request: {
+ components
+ }
+ })
+ );
+ }
}
},
{
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 d8d2320f51..10c56ff3fd 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
@@ -1440,6 +1440,89 @@ export class CanvasUtils {
return '#ffffff';
}
+ /**
+ * Filters the specified selection for any components that supports enable.
+ *
+ * @argument {selection} selection The selection
+ */
+ public filterEnable(selection: d3.Selection<any, any, any, any>):
d3.Selection<any, any, any, any> {
+ return selection.filter((d, i, nodes) => {
+ const selected = d3.select(nodes[i]);
+
+ // enable always allowed for PGs since they will invoke the /flow
endpoint for enabling all applicable components (based on permissions)
+ if (this.isProcessGroup(selected)) {
+ return true;
+ }
+
+ // not a PG, verify permissions to modify
+ if (!this.canOperate(selected)) {
+ return false;
+ }
+
+ // ensure it's a processor, input port, or output port and
supports modification and is disabled (can enable)
+ return (
+ (this.isProcessor(selected) || this.isInputPort(selected) ||
this.isOutputPort(selected)) &&
+ this.supportsModification(selected) &&
+ d.status.aggregateSnapshot.runStatus === 'Disabled'
+ );
+ });
+ }
+
+ /**
+ * Determines if the specified selection contains any components that
supports enable.
+ *
+ * @argument {selection} selection The selection
+ */
+ public canEnable(selection: d3.Selection<any, any, any, any>): boolean {
+ if (selection.empty()) {
+ return true;
+ }
+
+ return this.filterEnable(selection).size() === selection.size();
+ }
+
+ /**
+ * Filters the specified selection for any components that supports
disable.
+ *
+ * @argument {selection} selection The selection
+ */
+ public filterDisable(selection: d3.Selection<any, any, any, any>):
d3.Selection<any, any, any, any> {
+ return selection.filter((d, i, nodes) => {
+ const selected = d3.select(nodes[i]);
+
+ // disable always allowed for PGs since they will invoke the /flow
endpoint for disabling all applicable components (based on permissions)
+ if (this.isProcessGroup(selected)) {
+ return true;
+ }
+
+ // not a PG, verify permissions to modify
+ if (!this.canOperate(selected)) {
+ return false;
+ }
+
+ // ensure it's a processor, input port, or output port and
supports modification and is stopped (can disable)
+ return (
+ (this.isProcessor(selected) || this.isInputPort(selected) ||
this.isOutputPort(selected)) &&
+ this.supportsModification(selected) &&
+ (d.status.aggregateSnapshot.runStatus === 'Stopped' ||
+ d.status.aggregateSnapshot.runStatus === 'Invalid')
+ );
+ });
+ }
+
+ /**
+ * Determines if the specified selection contains any components that
supports disable.
+ *
+ * @argument {selection} selection The selection
+ */
+ public canDisable(selection: d3.Selection<any, any, any, any>): boolean {
+ if (selection.empty()) {
+ return true;
+ }
+
+ return this.filterDisable(selection).size() === selection.size();
+ }
+
/**
* Determines if the components in the specified selection are runnable.
*
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/flow.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/flow.service.ts
index aee0657396..9207f59507 100644
---
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/flow.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/flow.service.ts
@@ -27,7 +27,11 @@ import {
CreateProcessorRequest,
CreateRemoteProcessGroupRequest,
DeleteComponentRequest,
+ DisableComponentRequest,
+ DisableProcessGroupRequest,
DownloadFlowRequest,
+ EnableComponentRequest,
+ EnableProcessGroupRequest,
FlowComparisonEntity,
FlowUpdateRequestEntity,
GoToRemoteProcessGroupRequest,
@@ -270,6 +274,24 @@ export class FlowService implements
PropertyDescriptorRetriever {
return
this.httpClient.put(`${this.nifiCommon.stripProtocol(request.uri)}/run-status`,
startRequest);
}
+ enableComponent(request: EnableComponentRequest): Observable<any> {
+ const enableRequest: ComponentRunStatusRequest = {
+ revision: request.revision,
+ disconnectedNodeAcknowledged:
this.clusterConnectionService.isDisconnectionAcknowledged(),
+ state: 'STOPPED'
+ };
+ return
this.httpClient.put(`${this.nifiCommon.stripProtocol(request.uri)}/run-status`,
enableRequest);
+ }
+
+ disableComponent(request: DisableComponentRequest): Observable<any> {
+ const disableRequest: ComponentRunStatusRequest = {
+ revision: request.revision,
+ disconnectedNodeAcknowledged:
this.clusterConnectionService.isDisconnectionAcknowledged(),
+ state: 'DISABLED'
+ };
+ return
this.httpClient.put(`${this.nifiCommon.stripProtocol(request.uri)}/run-status`,
disableRequest);
+ }
+
startComponent(request: StartComponentRequest): Observable<any> {
const startRequest: ComponentRunStatusRequest = {
revision: request.revision,
@@ -292,6 +314,24 @@ export class FlowService implements
PropertyDescriptorRetriever {
return
this.httpClient.delete(`${this.nifiCommon.stripProtocol(request.uri)}/threads`);
}
+ enableProcessGroup(request: EnableProcessGroupRequest): Observable<any> {
+ const enableRequest: ProcessGroupRunStatusRequest = {
+ id: request.id,
+ disconnectedNodeAcknowledged:
this.clusterConnectionService.isDisconnectionAcknowledged(),
+ state: 'ENABLED'
+ };
+ return
this.httpClient.put(`${FlowService.API}/flow/process-groups/${request.id}`,
enableRequest);
+ }
+
+ disableProcessGroup(request: DisableProcessGroupRequest): Observable<any> {
+ const disableComponent: ProcessGroupRunStatusRequest = {
+ id: request.id,
+ disconnectedNodeAcknowledged:
this.clusterConnectionService.isDisconnectionAcknowledged(),
+ state: 'DISABLED'
+ };
+ return
this.httpClient.put(`${FlowService.API}/flow/process-groups/${request.id}`,
disableComponent);
+ }
+
startProcessGroup(request: StartProcessGroupRequest): Observable<any> {
const startRequest: ProcessGroupRunStatusRequest = {
id: request.id,
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 c00c48bc3f..0a0f8f4af8 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
@@ -34,10 +34,20 @@ import {
CreateRemoteProcessGroupRequest,
DeleteComponentRequest,
DeleteComponentResponse,
+ DisableComponentRequest,
+ DisableComponentResponse,
+ DisableComponentsRequest,
+ DisableProcessGroupRequest,
+ DisableProcessGroupResponse,
DownloadFlowRequest,
EditComponentDialogRequest,
EditConnectionDialogRequest,
EditCurrentProcessGroupRequest,
+ EnableComponentRequest,
+ EnableComponentResponse,
+ EnableComponentsRequest,
+ EnableProcessGroupRequest,
+ EnableProcessGroupResponse,
EnterProcessGroupRequest,
FlowUpdateRequestEntity,
GoToRemoteProcessGroupRequest,
@@ -588,6 +598,50 @@ export const replayLastProvenanceEvent = createAction(
props<{ request: ReplayLastProvenanceEventRequest }>()
);
+export const enableComponent = createAction(
+ `${CANVAS_PREFIX} Enable Component`,
+ props<{ request: EnableComponentRequest | EnableProcessGroupRequest }>()
+);
+
+export const enableComponents = createAction(
+ `${CANVAS_PREFIX} Enable Components`,
+ props<{ request: EnableComponentsRequest }>()
+);
+
+export const enableComponentSuccess = createAction(
+ `${CANVAS_PREFIX} Enable Component Success`,
+ props<{ response: EnableComponentResponse }>()
+);
+
+export const enableProcessGroupSuccess = createAction(
+ `${CANVAS_PREFIX} Enable Process Group Success`,
+ props<{ response: EnableProcessGroupResponse }>()
+);
+
+export const enableCurrentProcessGroup = createAction(`${CANVAS_PREFIX} Enable
Current Process Group`);
+
+export const disableComponent = createAction(
+ `${CANVAS_PREFIX} Disable Component`,
+ props<{ request: DisableComponentRequest | DisableProcessGroupRequest }>()
+);
+
+export const disableComponents = createAction(
+ `${CANVAS_PREFIX} Disable Components`,
+ props<{ request: DisableComponentsRequest }>()
+);
+
+export const disableComponentSuccess = createAction(
+ `${CANVAS_PREFIX} Disable Component Success`,
+ props<{ response: DisableComponentResponse }>()
+);
+
+export const disableProcessGroupSuccess = createAction(
+ `${CANVAS_PREFIX} Disable Process Group Success`,
+ props<{ response: DisableProcessGroupResponse }>()
+);
+
+export const disableCurrentProcessGroup = createAction(`${CANVAS_PREFIX}
Disable Current Process Group`);
+
export const runOnce = createAction(`${CANVAS_PREFIX} Run Once`, props<{
request: RunOnceRequest }>());
export const runOnceSuccess = createAction(`${CANVAS_PREFIX} Run Once
Success`, props<{ response: RunOnceResponse }>());
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 d5f9d96b55..bbfe801474 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
@@ -2436,6 +2436,240 @@ export class FlowEffects {
{ dispatch: false }
);
+ enableCurrentProcessGroup$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowActions.enableCurrentProcessGroup),
+ concatLatestFrom(() =>
this.store.select(selectCurrentProcessGroupId)),
+ switchMap(([, pgId]) => {
+ return of(
+ FlowActions.enableComponent({
+ request: {
+ id: pgId,
+ type: ComponentType.ProcessGroup
+ }
+ })
+ );
+ })
+ )
+ );
+
+ enableComponents$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowActions.enableComponents),
+ map((action) => action.request),
+ mergeMap((request) => [
+ ...request.components.map((component) => {
+ return FlowActions.enableComponent({
+ request: component
+ });
+ })
+ ])
+ )
+ );
+
+ enableComponent$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowActions.enableComponent),
+ map((action) => action.request),
+ mergeMap((request) => {
+ switch (request.type) {
+ case ComponentType.InputPort:
+ case ComponentType.OutputPort:
+ case ComponentType.Processor:
+ if ('uri' in request && 'revision' in request) {
+ return
from(this.flowService.enableComponent(request)).pipe(
+ map((response) => {
+ return FlowActions.enableComponentSuccess({
+ response: {
+ type: request.type,
+ component: response
+ }
+ });
+ }),
+ catchError((errorResponse: HttpErrorResponse)
=>
+ of(FlowActions.flowSnackbarError({ error:
errorResponse.error }))
+ )
+ );
+ }
+ return of(
+ FlowActions.flowSnackbarError({
+ error: `Enabling ${request.type} requires both
uri and revision properties`
+ })
+ );
+ case ComponentType.ProcessGroup:
+ return
from(this.flowService.enableProcessGroup(request)).pipe(
+ map((enablePgResponse) => {
+ return FlowActions.enableProcessGroupSuccess({
+ response: {
+ type: request.type,
+ component: enablePgResponse
+ }
+ });
+ }),
+ catchError((errorResponse: HttpErrorResponse) =>
+ of(FlowActions.flowSnackbarError({ error:
errorResponse.error }))
+ )
+ );
+ default:
+ return of(
+ FlowActions.flowSnackbarError({ error:
`${request.type} does not support enabling` })
+ );
+ }
+ })
+ )
+ );
+
+ /**
+ * If the component enabled was the current process group, reload the flow
+ */
+ enableCurrentProcessGroupSuccess$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowActions.enableProcessGroupSuccess),
+ map((action) => action.response),
+ concatLatestFrom(() =>
this.store.select(selectCurrentProcessGroupId)),
+ filter(([response, currentPg]) => response.component.id ===
currentPg),
+ switchMap(() => of(FlowActions.reloadFlow()))
+ )
+ );
+
+ /**
+ * If a ProcessGroup was enabled, it should be reloaded as the response
from the start operation doesn't contain all the displayed info
+ */
+ enableProcessGroupSuccess$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowActions.enableProcessGroupSuccess),
+ map((action) => action.response),
+ concatLatestFrom(() =>
this.store.select(selectCurrentProcessGroupId)),
+ filter(([response, currentPg]) => response.component.id !==
currentPg),
+ switchMap(([response]) =>
+ of(
+ FlowActions.loadChildProcessGroup({
+ request: {
+ id: response.component.id
+ }
+ })
+ )
+ )
+ )
+ );
+
+ disableCurrentProcessGroup$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowActions.disableCurrentProcessGroup),
+ concatLatestFrom(() =>
this.store.select(selectCurrentProcessGroupId)),
+ switchMap(([, pgId]) => {
+ return of(
+ FlowActions.disableComponent({
+ request: {
+ id: pgId,
+ type: ComponentType.ProcessGroup
+ }
+ })
+ );
+ })
+ )
+ );
+
+ disableComponents$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowActions.disableComponents),
+ map((action) => action.request),
+ mergeMap((request) => [
+ ...request.components.map((component) => {
+ return FlowActions.disableComponent({
+ request: component
+ });
+ })
+ ])
+ )
+ );
+
+ disableComponent$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowActions.disableComponent),
+ map((action) => action.request),
+ mergeMap((request) => {
+ switch (request.type) {
+ case ComponentType.InputPort:
+ case ComponentType.OutputPort:
+ case ComponentType.Processor:
+ if ('uri' in request && 'revision' in request) {
+ return
from(this.flowService.disableComponent(request)).pipe(
+ map((response) => {
+ return
FlowActions.disableComponentSuccess({
+ response: {
+ type: request.type,
+ component: response
+ }
+ });
+ }),
+ catchError((errorResponse: HttpErrorResponse)
=>
+ of(FlowActions.flowSnackbarError({ error:
errorResponse.error }))
+ )
+ );
+ }
+ return of(
+ FlowActions.flowSnackbarError({
+ error: `Disabling ${request.type} requires
both uri and revision properties`
+ })
+ );
+ case ComponentType.ProcessGroup:
+ return
from(this.flowService.disableProcessGroup(request)).pipe(
+ map((enablePgResponse) => {
+ return FlowActions.disableProcessGroupSuccess({
+ response: {
+ type: request.type,
+ component: enablePgResponse
+ }
+ });
+ }),
+ catchError((errorResponse: HttpErrorResponse) =>
+ of(FlowActions.flowSnackbarError({ error:
errorResponse.error }))
+ )
+ );
+ default:
+ return of(
+ FlowActions.flowSnackbarError({ error:
`${request.type} does not support disabling` })
+ );
+ }
+ })
+ )
+ );
+
+ /**
+ * If the component disabled was the current process group, reload the flow
+ */
+ disableCurrentProcessGroupSuccess$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowActions.disableProcessGroupSuccess),
+ map((action) => action.response),
+ concatLatestFrom(() =>
this.store.select(selectCurrentProcessGroupId)),
+ filter(([response, currentPg]) => response.component.id ===
currentPg),
+ switchMap(() => of(FlowActions.reloadFlow()))
+ )
+ );
+
+ /**
+ * If a ProcessGroup was disabled, it should be reloaded as the response
from the start operation doesn't contain all the displayed info
+ */
+ disableProcessGroupSuccess$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(FlowActions.disableProcessGroupSuccess),
+ map((action) => action.response),
+ concatLatestFrom(() =>
this.store.select(selectCurrentProcessGroupId)),
+ filter(([response, currentPg]) => response.component.id !==
currentPg),
+ switchMap(([response]) =>
+ of(
+ FlowActions.loadChildProcessGroup({
+ request: {
+ id: response.component.id
+ }
+ })
+ )
+ )
+ )
+ );
+
startCurrentProcessGroup$ = createEffect(() =>
this.actions$.pipe(
ofType(FlowActions.startCurrentProcessGroup),
@@ -2449,8 +2683,7 @@ export class FlowEffects {
}
})
);
- }),
- catchError((error) => of(FlowActions.flowApiError({ error:
error.error })))
+ })
)
);
@@ -2488,11 +2721,13 @@ export class FlowEffects {
}
});
}),
- catchError((error) =>
of(FlowActions.flowApiError({ error: error.error })))
+ catchError((errorResponse: HttpErrorResponse)
=>
+ of(FlowActions.flowSnackbarError({ error:
errorResponse.error }))
+ )
);
}
return of(
- FlowActions.flowApiError({
+ FlowActions.flowSnackbarError({
error: `Starting ${request.type} requires both
uri and revision properties`
})
);
@@ -2509,13 +2744,16 @@ export class FlowEffects {
}
});
}),
- catchError((error) =>
of(FlowActions.flowApiError({ error: error.error })))
+ catchError((errorResponse: HttpErrorResponse) =>
+ of(FlowActions.flowSnackbarError({ error:
errorResponse.error }))
+ )
);
default:
- return of(FlowActions.flowApiError({ error:
`${request.type} does not support starting` }));
+ return of(
+ FlowActions.flowSnackbarError({ error:
`${request.type} does not support starting` })
+ );
}
- }),
- catchError((error) => of(FlowActions.flowApiError({ error:
error.error })))
+ })
)
);
@@ -2604,11 +2842,13 @@ export class FlowEffects {
}
});
}),
- catchError((error) =>
of(FlowActions.flowApiError({ error: error.error })))
+ catchError((errorResponse: HttpErrorResponse)
=>
+ of(FlowActions.flowSnackbarError({ error:
errorResponse.error }))
+ )
);
}
return of(
- FlowActions.flowApiError({
+ FlowActions.flowSnackbarError({
error: `Stopping ${request.type} requires both
uri and revision properties`
})
);
@@ -2625,13 +2865,16 @@ export class FlowEffects {
}
});
}),
- catchError((error) =>
of(FlowActions.flowApiError({ error: error.error })))
+ catchError((errorResponse: HttpErrorResponse) =>
+ of(FlowActions.flowSnackbarError({ error:
errorResponse.error }))
+ )
);
default:
- return of(FlowActions.flowApiError({ error:
`${request.type} does not support stopping` }));
+ return of(
+ FlowActions.flowSnackbarError({ error:
`${request.type} does not support stopping` })
+ );
}
- }),
- catchError((error) => of(FlowActions.flowApiError({ error:
error.error })))
+ })
)
);
@@ -2739,6 +2982,7 @@ export class FlowEffects {
//////////////////////////////////
// Start version control effects
//////////////////////////////////
+
openSaveVersionDialogRequest$ = createEffect(() =>
this.actions$.pipe(
ofType(FlowActions.openSaveVersionDialogRequest),
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 7eb089b8ba..3ce6560e2f 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
@@ -30,6 +30,12 @@ import {
createProcessGroup,
createProcessor,
deleteComponentsSuccess,
+ disableComponent,
+ disableComponentSuccess,
+ disableProcessGroupSuccess,
+ enableComponent,
+ enableComponentSuccess,
+ enableProcessGroupSuccess,
flowApiError,
flowVersionBannerError,
groupComponents,
@@ -61,8 +67,11 @@ import {
setTransitionRequired,
startComponent,
startComponentSuccess,
+ startProcessGroupSuccess,
startRemoteProcessGroupPolling,
+ stopComponent,
stopComponentSuccess,
+ stopProcessGroupSuccess,
stopRemoteProcessGroupPolling,
stopVersionControl,
stopVersionControlSuccess,
@@ -275,10 +284,30 @@ export const flowReducer = createReducer(
dragging: false,
saving: false
})),
- on(updateComponent, updateProcessor, updateConnection, startComponent,
runOnce, (state) => ({
- ...state,
- saving: true
- })),
+ on(
+ updateComponent,
+ updateProcessor,
+ updateConnection,
+ enableComponent,
+ disableComponent,
+ startComponent,
+ stopComponent,
+ runOnce,
+ (state) => ({
+ ...state,
+ saving: true
+ })
+ ),
+ on(
+ enableProcessGroupSuccess,
+ disableProcessGroupSuccess,
+ startProcessGroupSuccess,
+ stopProcessGroupSuccess,
+ (state) => ({
+ ...state,
+ saving: false
+ })
+ ),
on(updateComponentSuccess, updateProcessorSuccess,
updateConnectionSuccess, (state, { response }) => {
return produce(state, (draftState) => {
const collection: any[] | null =
getComponentCollection(draftState, response.type);
@@ -400,20 +429,26 @@ export const flowReducer = createReducer(
...state,
operationCollapsed
})),
- on(startComponentSuccess, stopComponentSuccess, (state, { response }) => {
- return produce(state, (draftState) => {
- const collection: any[] | null =
getComponentCollection(draftState, response.type);
+ on(
+ startComponentSuccess,
+ stopComponentSuccess,
+ enableComponentSuccess,
+ disableComponentSuccess,
+ (state, { response }) => {
+ return produce(state, (draftState) => {
+ const collection: any[] | null =
getComponentCollection(draftState, response.type);
- if (collection) {
- const componentIndex: number = collection.findIndex((f: any)
=> response.component.id === f.id);
- if (componentIndex > -1) {
- collection[componentIndex] = response.component;
+ if (collection) {
+ const componentIndex: number = collection.findIndex((f:
any) => response.component.id === f.id);
+ if (componentIndex > -1) {
+ collection[componentIndex] = response.component;
+ }
}
- }
- draftState.saving = false;
- });
- }),
+ draftState.saving = false;
+ });
+ }
+ ),
on(runOnceSuccess, (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/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 09083969bb..53d89209a8 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
@@ -655,6 +655,64 @@ export interface RunOnceResponse {
component: ComponentEntity;
}
+export interface EnableProcessGroupRequest {
+ id: string;
+ type: ComponentType;
+}
+
+export interface EnableComponentRequest {
+ id: string;
+ uri: string;
+ type: ComponentType;
+ revision: Revision;
+}
+
+export interface EnableComponentsRequest {
+ components: EnableComponentRequest[];
+}
+
+export interface EnableComponentResponse {
+ type: ComponentType;
+ component: ComponentEntity;
+}
+
+export interface EnableProcessGroupResponse {
+ type: ComponentType;
+ component: {
+ id: string;
+ state: string;
+ };
+}
+
+export interface DisableProcessGroupRequest {
+ id: string;
+ type: ComponentType;
+}
+
+export interface DisableComponentRequest {
+ id: string;
+ uri: string;
+ type: ComponentType;
+ revision: Revision;
+}
+
+export interface DisableComponentsRequest {
+ components: DisableComponentRequest[];
+}
+
+export interface DisableComponentResponse {
+ type: ComponentType;
+ component: ComponentEntity;
+}
+
+export interface DisableProcessGroupResponse {
+ type: ComponentType;
+ component: {
+ id: string;
+ state: string;
+ };
+}
+
export interface StartProcessGroupRequest {
id: string;
type: ComponentType;
@@ -692,10 +750,6 @@ export interface StopProcessGroupResponse {
};
}
-export interface StartComponentsResponse {
- components: StartComponentsResponse[];
-}
-
export interface ComponentRunStatusRequest {
revision: Revision;
state: string;
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/graph-controls/operation-control/operation-control.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/graph-controls/operation-control/operation-control.component.ts
index 6eac3f66f6..42bdbdcd56 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/graph-controls/operation-control/operation-control.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/graph-controls/operation-control/operation-control.component.ts
@@ -19,6 +19,10 @@ import { Component, Input } from '@angular/core';
import {
copy,
deleteComponents,
+ disableComponents,
+ disableCurrentProcessGroup,
+ enableComponents,
+ enableCurrentProcessGroup,
getParameterContextsAndOpenGroupComponentsDialog,
navigateToEditComponent,
navigateToEditCurrentProcessGroup,
@@ -38,6 +42,8 @@ import { Storage } from
'../../../../../../service/storage.service';
import {
CopyComponentRequest,
DeleteComponentRequest,
+ DisableComponentRequest,
+ EnableComponentRequest,
MoveComponentRequest,
StartComponentRequest,
StopComponentRequest
@@ -48,6 +54,7 @@ import { ComponentType } from
'../../../../../../state/shared';
import { MatButtonModule } from '@angular/material/button';
import * as d3 from 'd3';
import { CanvasView } from '../../../../service/canvas-view.service';
+import { Client } from '../../../../../../service/client.service';
@Component({
selector: 'operation-control',
@@ -69,6 +76,7 @@ export class OperationControl {
private store: Store<CanvasState>,
public canvasUtils: CanvasUtils,
private canvasView: CanvasView,
+ private client: Client,
private storage: Storage
) {
try {
@@ -76,7 +84,7 @@ export class OperationControl {
OperationControl.CONTROL_VISIBILITY_KEY
);
if (item) {
- this.operationCollapsed = item[OperationControl.OPERATION_KEY]
=== false;
+ this.operationCollapsed =
!item[OperationControl.OPERATION_KEY];
this.store.dispatch(setOperationCollapsed({
operationCollapsed: this.operationCollapsed }));
}
} catch (e) {
@@ -98,7 +106,7 @@ export class OperationControl {
this.storage.setItem(OperationControl.CONTROL_VISIBILITY_KEY, item);
}
- getContextIcon(selection: any): string {
+ getContextIcon(selection: d3.Selection<any, any, any, any>): string {
if (selection.size() === 0) {
if (this.breadcrumbEntity.parentBreadcrumb == null) {
return 'icon-drop';
@@ -128,7 +136,7 @@ export class OperationControl {
}
}
- getContextName(selection: any): string {
+ getContextName(selection: d3.Selection<any, any, any, any>): string {
if (selection.size() === 0) {
if (this.breadcrumbEntity.permissions.canRead) {
return this.breadcrumbEntity.breadcrumb.name;
@@ -153,7 +161,7 @@ export class OperationControl {
}
}
- getContextType(selection: any): string {
+ getContextType(selection: d3.Selection<any, any, any, any>): string {
if (selection.size() === 0) {
return 'Process Group';
} else if (selection.size() > 1) {
@@ -179,7 +187,7 @@ export class OperationControl {
}
}
- getContextId(selection: any): string {
+ getContextId(selection: d3.Selection<any, any, any, any>): string {
if (selection.size() === 0) {
return this.breadcrumbEntity.id;
} else if (selection.size() > 1) {
@@ -190,11 +198,11 @@ export class OperationControl {
return selectionData.id;
}
- canConfigure(selection: any): boolean {
+ canConfigure(selection: d3.Selection<any, any, any, any>): boolean {
return this.canvasUtils.isConfigurable(selection);
}
- configure(selection: any): void {
+ configure(selection: d3.Selection<any, any, any, any>): void {
if (selection.empty()) {
this.store.dispatch(navigateToEditCurrentProcessGroup());
} else {
@@ -214,11 +222,11 @@ export class OperationControl {
return this.canvasUtils.supportsManagedAuthorizer();
}
- canManageAccess(selection: any): boolean {
+ canManageAccess(selection: d3.Selection<any, any, any, any>): boolean {
return this.canvasUtils.canManagePolicies(selection);
}
- manageAccess(selection: any): void {
+ manageAccess(selection: d3.Selection<any, any, any, any>): void {
if (selection.empty()) {
this.store.dispatch(
navigateToManageComponentPolicies({
@@ -265,29 +273,69 @@ export class OperationControl {
}
}
- canEnable(selection: any): boolean {
- // TODO - canEnable
- return false;
+ canEnable(selection: d3.Selection<any, any, any, any>): boolean {
+ return this.canvasUtils.canEnable(selection);
}
- enable(selection: any): void {
- // TODO - enable
+ enable(selection: d3.Selection<any, any, any, any>): void {
+ if (selection.empty()) {
+ // attempting to enable the current process group
+ this.store.dispatch(enableCurrentProcessGroup());
+ } else {
+ const components: EnableComponentRequest[] = [];
+ const enableable = this.canvasUtils.filterEnable(selection);
+ enableable.each((d: any) => {
+ components.push({
+ id: d.id,
+ uri: d.uri,
+ type: d.type,
+ revision: this.client.getRevision(d)
+ });
+ });
+ this.store.dispatch(
+ enableComponents({
+ request: {
+ components
+ }
+ })
+ );
+ }
}
- canDisable(selection: any): boolean {
- // TODO - canDisable
- return false;
+ canDisable(selection: d3.Selection<any, any, any, any>): boolean {
+ return this.canvasUtils.canDisable(selection);
}
- disable(selection: any): void {
- // TODO - disable
+ disable(selection: d3.Selection<any, any, any, any>): void {
+ if (selection.empty()) {
+ // attempting to disable the current process group
+ this.store.dispatch(disableCurrentProcessGroup());
+ } else {
+ const components: DisableComponentRequest[] = [];
+ const disableable = this.canvasUtils.filterDisable(selection);
+ disableable.each((d: any) => {
+ components.push({
+ id: d.id,
+ uri: d.uri,
+ type: d.type,
+ revision: this.client.getRevision(d)
+ });
+ });
+ this.store.dispatch(
+ disableComponents({
+ request: {
+ components
+ }
+ })
+ );
+ }
}
- canStart(selection: any): boolean {
+ canStart(selection: d3.Selection<any, any, any, any>): boolean {
return this.canvasUtils.areAnyRunnable(selection);
}
- start(selection: any): void {
+ start(selection: d3.Selection<any, any, any, any>): void {
if (selection.empty()) {
// attempting to start the current process group
this.store.dispatch(startCurrentProcessGroup());
@@ -299,7 +347,7 @@ export class OperationControl {
id: d.id,
uri: d.uri,
type: d.type,
- revision: d.revision
+ revision: this.client.getRevision(d)
});
});
this.store.dispatch(
@@ -312,11 +360,11 @@ export class OperationControl {
}
}
- canStop(selection: any): boolean {
+ canStop(selection: d3.Selection<any, any, any, any>): boolean {
return this.canvasUtils.areAnyStoppable(selection);
}
- stop(selection: any): void {
+ stop(selection: d3.Selection<any, any, any, any>): void {
if (selection.empty()) {
// attempting to start the current process group
this.store.dispatch(stopCurrentProcessGroup());
@@ -328,7 +376,7 @@ export class OperationControl {
id: d.id,
uri: d.uri,
type: d.type,
- revision: d.revision
+ revision: this.client.getRevision(d)
});
});
this.store.dispatch(
@@ -382,11 +430,11 @@ export class OperationControl {
);
}
- canGroup(selection: any): boolean {
+ canGroup(selection: d3.Selection<any, any, any, any>): boolean {
return this.canvasUtils.isDisconnected(selection);
}
- group(selection: any): void {
+ group(selection: d3.Selection<any, any, any, any>): void {
const moveComponents: MoveComponentRequest[] = [];
selection.each(function (d: any) {
moveComponents.push({
@@ -408,20 +456,20 @@ export class OperationControl {
);
}
- canColor(selection: any): boolean {
+ canColor(selection: d3.Selection<any, any, any, any>): boolean {
// TODO
return false;
}
- color(selection: any): void {
+ color(selection: d3.Selection<any, any, any, any>): void {
// TODO
}
- canDelete(selection: any): boolean {
+ canDelete(selection: d3.Selection<any, any, any, any>): boolean {
return this.canvasUtils.areDeletable(selection);
}
- delete(selection: any): void {
+ delete(selection: d3.Selection<any, any, any, any>): void {
if (selection.size() === 1) {
const selectionData = selection.datum();
this.store.dispatch(