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,
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
}
})
);
}
}
},
{

View File

@ -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.
*

View File

@ -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,

View File

@ -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 }>());

View File

@ -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),

View File

@ -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) => {

View File

@ -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;

View File

@ -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(