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
This commit is contained in:
Matt Gilman 2024-04-23 16:47:25 -04:00 committed by GitHub
parent cde820673c
commit 562eece6e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 683 additions and 77 deletions

View File

@ -58,13 +58,19 @@ import {
copy, copy,
paste, paste,
terminateThreads, terminateThreads,
navigateToParameterContext navigateToParameterContext,
enableCurrentProcessGroup,
enableComponents,
disableCurrentProcessGroup,
disableComponents
} from '../state/flow/flow.actions'; } from '../state/flow/flow.actions';
import { ComponentType } from '../../../state/shared'; import { ComponentType } from '../../../state/shared';
import { import {
ConfirmStopVersionControlRequest, ConfirmStopVersionControlRequest,
CopyComponentRequest, CopyComponentRequest,
DeleteComponentRequest, DeleteComponentRequest,
DisableComponentRequest,
EnableComponentRequest,
MoveComponentRequest, MoveComponentRequest,
OpenChangeVersionDialogRequest, OpenChangeVersionDialogRequest,
OpenLocalChangesDialogRequest, 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 // 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. // to the Enable Transmission menu option and not show the start option.
const allRpgs = const allRpgs =
!startable.empty() &&
startable.filter((d: any) => d.type === ComponentType.RemoteProcessGroup).size() === startable.filter((d: any) => d.type === ComponentType.RemoteProcessGroup).size() ===
startable.size(); startable.size();
return this.canvasUtils.areAnyRunnable(selection) && !allRpgs; 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 // 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. // to the Disable Transmission menu option and not show the start option.
const allRpgs = const allRpgs =
!stoppable.empty() &&
stoppable.filter((d: any) => d.type === ComponentType.RemoteProcessGroup).size() === stoppable.filter((d: any) => d.type === ComponentType.RemoteProcessGroup).size() ===
stoppable.size(); stoppable.size();
return this.canvasUtils.areAnyStoppable(selection) && !allRpgs; return this.canvasUtils.areAnyStoppable(selection) && !allRpgs;
}, },
@ -685,25 +693,65 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
} }
}, },
{ {
condition: (selection: any) => { condition: (selection: d3.Selection<any, any, any, any>) => {
// TODO - canEnable return this.canvasUtils.canEnable(selection);
return false;
}, },
clazz: 'fa fa-flash', clazz: 'fa fa-flash',
text: 'Enable', text: 'Enable',
action: () => { action: (selection: d3.Selection<any, any, any, any>) => {
// TODO - enable 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) => { condition: (selection: d3.Selection<any, any, any, any>) => {
// TODO - canDisable return this.canvasUtils.canDisable(selection);
return false;
}, },
clazz: 'icon icon-enable-false', clazz: 'icon icon-enable-false',
text: 'Disable', text: 'Disable',
action: () => { action: (selection: d3.Selection<any, any, any, any>) => {
// TODO - disable 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
}
})
);
}
} }
}, },
{ {

View File

@ -1440,6 +1440,89 @@ export class CanvasUtils {
return '#ffffff'; 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. * Determines if the components in the specified selection are runnable.
* *

View File

@ -27,7 +27,11 @@ import {
CreateProcessorRequest, CreateProcessorRequest,
CreateRemoteProcessGroupRequest, CreateRemoteProcessGroupRequest,
DeleteComponentRequest, DeleteComponentRequest,
DisableComponentRequest,
DisableProcessGroupRequest,
DownloadFlowRequest, DownloadFlowRequest,
EnableComponentRequest,
EnableProcessGroupRequest,
FlowComparisonEntity, FlowComparisonEntity,
FlowUpdateRequestEntity, FlowUpdateRequestEntity,
GoToRemoteProcessGroupRequest, GoToRemoteProcessGroupRequest,
@ -270,6 +274,24 @@ export class FlowService implements PropertyDescriptorRetriever {
return this.httpClient.put(`${this.nifiCommon.stripProtocol(request.uri)}/run-status`, startRequest); 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> { startComponent(request: StartComponentRequest): Observable<any> {
const startRequest: ComponentRunStatusRequest = { const startRequest: ComponentRunStatusRequest = {
revision: request.revision, revision: request.revision,
@ -292,6 +314,24 @@ export class FlowService implements PropertyDescriptorRetriever {
return this.httpClient.delete(`${this.nifiCommon.stripProtocol(request.uri)}/threads`); 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> { startProcessGroup(request: StartProcessGroupRequest): Observable<any> {
const startRequest: ProcessGroupRunStatusRequest = { const startRequest: ProcessGroupRunStatusRequest = {
id: request.id, id: request.id,

View File

@ -34,10 +34,20 @@ import {
CreateRemoteProcessGroupRequest, CreateRemoteProcessGroupRequest,
DeleteComponentRequest, DeleteComponentRequest,
DeleteComponentResponse, DeleteComponentResponse,
DisableComponentRequest,
DisableComponentResponse,
DisableComponentsRequest,
DisableProcessGroupRequest,
DisableProcessGroupResponse,
DownloadFlowRequest, DownloadFlowRequest,
EditComponentDialogRequest, EditComponentDialogRequest,
EditConnectionDialogRequest, EditConnectionDialogRequest,
EditCurrentProcessGroupRequest, EditCurrentProcessGroupRequest,
EnableComponentRequest,
EnableComponentResponse,
EnableComponentsRequest,
EnableProcessGroupRequest,
EnableProcessGroupResponse,
EnterProcessGroupRequest, EnterProcessGroupRequest,
FlowUpdateRequestEntity, FlowUpdateRequestEntity,
GoToRemoteProcessGroupRequest, GoToRemoteProcessGroupRequest,
@ -588,6 +598,50 @@ export const replayLastProvenanceEvent = createAction(
props<{ request: ReplayLastProvenanceEventRequest }>() 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 runOnce = createAction(`${CANVAS_PREFIX} Run Once`, props<{ request: RunOnceRequest }>());
export const runOnceSuccess = createAction(`${CANVAS_PREFIX} Run Once Success`, props<{ response: RunOnceResponse }>()); export const runOnceSuccess = createAction(`${CANVAS_PREFIX} Run Once Success`, props<{ response: RunOnceResponse }>());

View File

@ -2436,6 +2436,240 @@ export class FlowEffects {
{ dispatch: false } { 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(() => startCurrentProcessGroup$ = createEffect(() =>
this.actions$.pipe( this.actions$.pipe(
ofType(FlowActions.startCurrentProcessGroup), 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( return of(
FlowActions.flowApiError({ FlowActions.flowSnackbarError({
error: `Starting ${request.type} requires both uri and revision properties` 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: 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( return of(
FlowActions.flowApiError({ FlowActions.flowSnackbarError({
error: `Stopping ${request.type} requires both uri and revision properties` 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: 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 // Start version control effects
////////////////////////////////// //////////////////////////////////
openSaveVersionDialogRequest$ = createEffect(() => openSaveVersionDialogRequest$ = createEffect(() =>
this.actions$.pipe( this.actions$.pipe(
ofType(FlowActions.openSaveVersionDialogRequest), ofType(FlowActions.openSaveVersionDialogRequest),

View File

@ -30,6 +30,12 @@ import {
createProcessGroup, createProcessGroup,
createProcessor, createProcessor,
deleteComponentsSuccess, deleteComponentsSuccess,
disableComponent,
disableComponentSuccess,
disableProcessGroupSuccess,
enableComponent,
enableComponentSuccess,
enableProcessGroupSuccess,
flowApiError, flowApiError,
flowVersionBannerError, flowVersionBannerError,
groupComponents, groupComponents,
@ -61,8 +67,11 @@ import {
setTransitionRequired, setTransitionRequired,
startComponent, startComponent,
startComponentSuccess, startComponentSuccess,
startProcessGroupSuccess,
startRemoteProcessGroupPolling, startRemoteProcessGroupPolling,
stopComponent,
stopComponentSuccess, stopComponentSuccess,
stopProcessGroupSuccess,
stopRemoteProcessGroupPolling, stopRemoteProcessGroupPolling,
stopVersionControl, stopVersionControl,
stopVersionControlSuccess, stopVersionControlSuccess,
@ -275,10 +284,30 @@ export const flowReducer = createReducer(
dragging: false, dragging: false,
saving: false saving: false
})), })),
on(updateComponent, updateProcessor, updateConnection, startComponent, runOnce, (state) => ({ on(
...state, updateComponent,
saving: true 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 }) => { on(updateComponentSuccess, updateProcessorSuccess, updateConnectionSuccess, (state, { response }) => {
return produce(state, (draftState) => { return produce(state, (draftState) => {
const collection: any[] | null = getComponentCollection(draftState, response.type); const collection: any[] | null = getComponentCollection(draftState, response.type);
@ -400,20 +429,26 @@ export const flowReducer = createReducer(
...state, ...state,
operationCollapsed operationCollapsed
})), })),
on(startComponentSuccess, stopComponentSuccess, (state, { response }) => { on(
return produce(state, (draftState) => { startComponentSuccess,
const collection: any[] | null = getComponentCollection(draftState, response.type); stopComponentSuccess,
enableComponentSuccess,
disableComponentSuccess,
(state, { response }) => {
return produce(state, (draftState) => {
const collection: any[] | null = getComponentCollection(draftState, response.type);
if (collection) { if (collection) {
const componentIndex: number = collection.findIndex((f: any) => response.component.id === f.id); const componentIndex: number = collection.findIndex((f: any) => response.component.id === f.id);
if (componentIndex > -1) { if (componentIndex > -1) {
collection[componentIndex] = response.component; collection[componentIndex] = response.component;
}
} }
}
draftState.saving = false; draftState.saving = false;
}); });
}), }
),
on(runOnceSuccess, (state, { response }) => { on(runOnceSuccess, (state, { response }) => {
return produce(state, (draftState) => { return produce(state, (draftState) => {

View File

@ -655,6 +655,64 @@ export interface RunOnceResponse {
component: ComponentEntity; 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 { export interface StartProcessGroupRequest {
id: string; id: string;
type: ComponentType; type: ComponentType;
@ -692,10 +750,6 @@ export interface StopProcessGroupResponse {
}; };
} }
export interface StartComponentsResponse {
components: StartComponentsResponse[];
}
export interface ComponentRunStatusRequest { export interface ComponentRunStatusRequest {
revision: Revision; revision: Revision;
state: string; state: string;

View File

@ -19,6 +19,10 @@ import { Component, Input } from '@angular/core';
import { import {
copy, copy,
deleteComponents, deleteComponents,
disableComponents,
disableCurrentProcessGroup,
enableComponents,
enableCurrentProcessGroup,
getParameterContextsAndOpenGroupComponentsDialog, getParameterContextsAndOpenGroupComponentsDialog,
navigateToEditComponent, navigateToEditComponent,
navigateToEditCurrentProcessGroup, navigateToEditCurrentProcessGroup,
@ -38,6 +42,8 @@ import { Storage } from '../../../../../../service/storage.service';
import { import {
CopyComponentRequest, CopyComponentRequest,
DeleteComponentRequest, DeleteComponentRequest,
DisableComponentRequest,
EnableComponentRequest,
MoveComponentRequest, MoveComponentRequest,
StartComponentRequest, StartComponentRequest,
StopComponentRequest StopComponentRequest
@ -48,6 +54,7 @@ import { ComponentType } from '../../../../../../state/shared';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import * as d3 from 'd3'; import * as d3 from 'd3';
import { CanvasView } from '../../../../service/canvas-view.service'; import { CanvasView } from '../../../../service/canvas-view.service';
import { Client } from '../../../../../../service/client.service';
@Component({ @Component({
selector: 'operation-control', selector: 'operation-control',
@ -69,6 +76,7 @@ export class OperationControl {
private store: Store<CanvasState>, private store: Store<CanvasState>,
public canvasUtils: CanvasUtils, public canvasUtils: CanvasUtils,
private canvasView: CanvasView, private canvasView: CanvasView,
private client: Client,
private storage: Storage private storage: Storage
) { ) {
try { try {
@ -76,7 +84,7 @@ export class OperationControl {
OperationControl.CONTROL_VISIBILITY_KEY OperationControl.CONTROL_VISIBILITY_KEY
); );
if (item) { if (item) {
this.operationCollapsed = item[OperationControl.OPERATION_KEY] === false; this.operationCollapsed = !item[OperationControl.OPERATION_KEY];
this.store.dispatch(setOperationCollapsed({ operationCollapsed: this.operationCollapsed })); this.store.dispatch(setOperationCollapsed({ operationCollapsed: this.operationCollapsed }));
} }
} catch (e) { } catch (e) {
@ -98,7 +106,7 @@ export class OperationControl {
this.storage.setItem(OperationControl.CONTROL_VISIBILITY_KEY, item); 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 (selection.size() === 0) {
if (this.breadcrumbEntity.parentBreadcrumb == null) { if (this.breadcrumbEntity.parentBreadcrumb == null) {
return 'icon-drop'; 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 (selection.size() === 0) {
if (this.breadcrumbEntity.permissions.canRead) { if (this.breadcrumbEntity.permissions.canRead) {
return this.breadcrumbEntity.breadcrumb.name; 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) { if (selection.size() === 0) {
return 'Process Group'; return 'Process Group';
} else if (selection.size() > 1) { } 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) { if (selection.size() === 0) {
return this.breadcrumbEntity.id; return this.breadcrumbEntity.id;
} else if (selection.size() > 1) { } else if (selection.size() > 1) {
@ -190,11 +198,11 @@ export class OperationControl {
return selectionData.id; return selectionData.id;
} }
canConfigure(selection: any): boolean { canConfigure(selection: d3.Selection<any, any, any, any>): boolean {
return this.canvasUtils.isConfigurable(selection); return this.canvasUtils.isConfigurable(selection);
} }
configure(selection: any): void { configure(selection: d3.Selection<any, any, any, any>): void {
if (selection.empty()) { if (selection.empty()) {
this.store.dispatch(navigateToEditCurrentProcessGroup()); this.store.dispatch(navigateToEditCurrentProcessGroup());
} else { } else {
@ -214,11 +222,11 @@ export class OperationControl {
return this.canvasUtils.supportsManagedAuthorizer(); return this.canvasUtils.supportsManagedAuthorizer();
} }
canManageAccess(selection: any): boolean { canManageAccess(selection: d3.Selection<any, any, any, any>): boolean {
return this.canvasUtils.canManagePolicies(selection); return this.canvasUtils.canManagePolicies(selection);
} }
manageAccess(selection: any): void { manageAccess(selection: d3.Selection<any, any, any, any>): void {
if (selection.empty()) { if (selection.empty()) {
this.store.dispatch( this.store.dispatch(
navigateToManageComponentPolicies({ navigateToManageComponentPolicies({
@ -265,29 +273,69 @@ export class OperationControl {
} }
} }
canEnable(selection: any): boolean { canEnable(selection: d3.Selection<any, any, any, any>): boolean {
// TODO - canEnable return this.canvasUtils.canEnable(selection);
return false;
} }
enable(selection: any): void { enable(selection: d3.Selection<any, any, any, any>): void {
// TODO - enable 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 { canDisable(selection: d3.Selection<any, any, any, any>): boolean {
// TODO - canDisable return this.canvasUtils.canDisable(selection);
return false;
} }
disable(selection: any): void { disable(selection: d3.Selection<any, any, any, any>): void {
// TODO - disable 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); return this.canvasUtils.areAnyRunnable(selection);
} }
start(selection: any): void { start(selection: d3.Selection<any, any, any, any>): void {
if (selection.empty()) { if (selection.empty()) {
// attempting to start the current process group // attempting to start the current process group
this.store.dispatch(startCurrentProcessGroup()); this.store.dispatch(startCurrentProcessGroup());
@ -299,7 +347,7 @@ export class OperationControl {
id: d.id, id: d.id,
uri: d.uri, uri: d.uri,
type: d.type, type: d.type,
revision: d.revision revision: this.client.getRevision(d)
}); });
}); });
this.store.dispatch( 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); return this.canvasUtils.areAnyStoppable(selection);
} }
stop(selection: any): void { stop(selection: d3.Selection<any, any, any, any>): void {
if (selection.empty()) { if (selection.empty()) {
// attempting to start the current process group // attempting to start the current process group
this.store.dispatch(stopCurrentProcessGroup()); this.store.dispatch(stopCurrentProcessGroup());
@ -328,7 +376,7 @@ export class OperationControl {
id: d.id, id: d.id,
uri: d.uri, uri: d.uri,
type: d.type, type: d.type,
revision: d.revision revision: this.client.getRevision(d)
}); });
}); });
this.store.dispatch( 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); return this.canvasUtils.isDisconnected(selection);
} }
group(selection: any): void { group(selection: d3.Selection<any, any, any, any>): void {
const moveComponents: MoveComponentRequest[] = []; const moveComponents: MoveComponentRequest[] = [];
selection.each(function (d: any) { selection.each(function (d: any) {
moveComponents.push({ moveComponents.push({
@ -408,20 +456,20 @@ export class OperationControl {
); );
} }
canColor(selection: any): boolean { canColor(selection: d3.Selection<any, any, any, any>): boolean {
// TODO // TODO
return false; return false;
} }
color(selection: any): void { color(selection: d3.Selection<any, any, any, any>): void {
// TODO // TODO
} }
canDelete(selection: any): boolean { canDelete(selection: d3.Selection<any, any, any, any>): boolean {
return this.canvasUtils.areDeletable(selection); return this.canvasUtils.areDeletable(selection);
} }
delete(selection: any): void { delete(selection: d3.Selection<any, any, any, any>): void {
if (selection.size() === 1) { if (selection.size() === 1) {
const selectionData = selection.datum(); const selectionData = selection.datum();
this.store.dispatch( this.store.dispatch(