Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
navigateToViewStatusHistoryForComponent,
openChangeProcessorVersionDialog,
openChangeVersionDialogRequest,
openCreateBranchDialogRequest,
openCommitLocalChangesDialogRequest,
openForceCommitLocalChangesDialogRequest,
openRevertLocalChangesDialogRequest,
Expand Down Expand Up @@ -215,6 +216,28 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
this.store.dispatch(openChangeVersionDialogRequest({ request }));
}
},
{
condition: (selection: d3.Selection<any, any, any, any>) => {
return this.canvasUtils.supportsCreateFlowBranch(selection);
},
clazz: 'fa fa-code-fork',
text: 'Create Branch',
action: (selection: d3.Selection<any, any, any, any>) => {
let pgId;
if (selection.empty()) {
pgId = this.canvasUtils.getProcessGroupId();
} else {
pgId = selection.datum().id;
}
this.store.dispatch(
openCreateBranchDialogRequest({
request: {
processGroupId: pgId
}
})
);
}
},
{
isSeparator: true
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ import * as fromFlow from '../state/flow/flow.reducer';
import { transformFeatureKey } from '../state/transform';
import * as fromTransform from '../state/transform/transform.reducer';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { selectConnections, selectCurrentProcessGroupId, selectFlowState } from '../state/flow/flow.selectors';
import {
selectConnections,
selectCurrentProcessGroupId,
selectFlowState,
selectRegistryClients
} from '../state/flow/flow.selectors';
import { controllerServicesFeatureKey } from '../state/controller-services';
import * as fromControllerServices from '../state/controller-services/controller-services.reducer';
import { selectCurrentUser } from '../../../state/current-user/current-user.selectors';
Expand Down Expand Up @@ -131,6 +136,91 @@ describe('CanvasUtils', () => {
});
});

describe('supportsCreateFlowBranch', () => {
const registryId = '324e0ab1-0197-1000-ffff-ffffb3123c5c';

function createProcessGroupSelection(versionControlInformation: any): d3.Selection<any, any, any, any> {
const pgDatum = {
id: '1',
type: ComponentType.ProcessGroup,
permissions: { canRead: true, canWrite: true },
component: {
id: '1',
name: 'Test Process Group',
versionControlInformation
}
};
return d3.select(document.createElement('div')).classed('process-group', true).datum(pgDatum);
}

function configure(canVersionFlows: boolean, supportsBranching: boolean): void {
const store = TestBed.inject(MockStore);
store.overrideSelector(selectCurrentUser, { ...fromUser.initialState.user, canVersionFlows });
store.overrideSelector(selectRegistryClients, [
{ id: registryId, component: { supportsBranching } }
] as any);
store.refreshState();
}

it('should return false when the user cannot version flows', () => {
configure(false, true);
const selection = createProcessGroupSelection({ groupId: '1', registryId, state: 'UP_TO_DATE' });
expect(service.supportsCreateFlowBranch(selection)).toBe(false);
});

it('should return false when there is no version control information', () => {
configure(true, true);
const selection = createProcessGroupSelection(null);
expect(service.supportsCreateFlowBranch(selection)).toBe(false);
});

it('should return false when the registry id is missing', () => {
configure(true, true);
const selection = createProcessGroupSelection({ groupId: '1', state: 'UP_TO_DATE' });
expect(service.supportsCreateFlowBranch(selection)).toBe(false);
});

it('should return false when the flow is in a sync failure state', () => {
configure(true, true);
const selection = createProcessGroupSelection({ groupId: '1', registryId, state: 'SYNC_FAILURE' });
expect(service.supportsCreateFlowBranch(selection)).toBe(false);
});

it('should return false when the registry client does not support branching', () => {
configure(true, false);
const selection = createProcessGroupSelection({ groupId: '1', registryId, state: 'UP_TO_DATE' });
expect(service.supportsCreateFlowBranch(selection)).toBe(false);
});

it('should return true when version controlled and the registry client supports branching', () => {
configure(true, true);
const selection = createProcessGroupSelection({ groupId: '1', registryId, state: 'UP_TO_DATE' });
expect(service.supportsCreateFlowBranch(selection)).toBe(true);
});

it('should return true when the flow is locally modified', () => {
configure(true, true);
const selection = createProcessGroupSelection({ groupId: '1', registryId, state: 'LOCALLY_MODIFIED' });
expect(service.supportsCreateFlowBranch(selection)).toBe(true);
});

it('should return true when the flow is locally modified and stale', () => {
configure(true, true);
const selection = createProcessGroupSelection({
groupId: '1',
registryId,
state: 'LOCALLY_MODIFIED_AND_STALE'
});
expect(service.supportsCreateFlowBranch(selection)).toBe(true);
});

it('should return true when the flow is stale', () => {
configure(true, true);
const selection = createProcessGroupSelection({ groupId: '1', registryId, state: 'STALE' });
expect(service.supportsCreateFlowBranch(selection)).toBe(true);
});
});

describe('isStoppable', () => {
it('should return false for empty selection', () => {
const emptySelection = d3.select(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ import {
selectConnections,
selectCurrentParameterContext,
selectCurrentProcessGroupId,
selectParentProcessGroupId
selectParentProcessGroupId,
selectRegistryClients
} from '../state/flow/flow.selectors';
import { initialState as initialFlowState } from '../state/flow/flow.reducer';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { BulletinsTip } from '../../../ui/common/tooltips/bulletins-tip/bulletins-tip.component';
import { Position } from '../state/shared';
import { BreadcrumbEntity } from '../../../state/shared';
import { BreadcrumbEntity, RegistryClientEntity } from '../../../state/shared';
import { BulletinEntity, ComponentType, NiFiCommon, ParameterContextReferenceEntity, Permissions } from '@nifi/shared';
import { CurrentUser } from '../../../state/current-user';
import { initialState as initialUserState } from '../../../state/current-user/current-user.reducer';
Expand Down Expand Up @@ -89,6 +90,7 @@ export class CanvasUtils {
private connections: any[] = [];
private breadcrumbs: BreadcrumbEntity | null = null;
private copiedSnippet: CopiedSnippet | null = null;
private registryClients: RegistryClientEntity[] = initialFlowState.registryClients;

private readonly humanizeDuration: Humanizer;

Expand Down Expand Up @@ -158,6 +160,13 @@ export class CanvasUtils {
.subscribe((scale) => {
this.scale = scale;
});

this.store
.select(selectRegistryClients)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((registryClients) => {
this.registryClients = registryClients;
});
}

public hasDownstream(selection: any): boolean {
Expand Down Expand Up @@ -2172,6 +2181,32 @@ export class CanvasUtils {
);
}

/**
* Returns whether the process group supports creating a new branch. This requires that the
* process group is under version control with a registry client that supports branching.
*
* @argument {d3.Selection} selection The selection
* @return {boolean} Whether the selection supports creating a branch.
*/
public supportsCreateFlowBranch(selection: d3.Selection<any, any, any, any>): boolean {
if (!this.canVersionFlows()) {
return false;
}

const versionControlInformation = this.getFlowVersionControlInformation(selection);

if (!versionControlInformation || !versionControlInformation.registryId) {
return false;
}

if (versionControlInformation.state === 'SYNC_FAILURE') {
return false;
}

const registryClient = this.registryClients.find((client) => client.id === versionControlInformation.registryId);
return !!registryClient?.component.supportsBranching;
}

/**
* Determines whether the current selection supports stopping flow versioning.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
CreateComponentRequest,
CreateComponentResponse,
CreateConnection,
CreateFlowBranchRequest,
CreateLabelRequest,
CreatePortRequest,
CreateProcessGroupRequest,
Expand All @@ -50,6 +51,7 @@ import {
} from '../state/flow';
import { Client } from '../../../service/client.service';
import { ComponentType, NiFiCommon } from '@nifi/shared';
import { Revision } from '@nifi/shared';
import { ClusterConnectionService } from '../../../service/cluster-connection.service';
import {
ClearBulletinsRequest,
Expand Down Expand Up @@ -415,6 +417,33 @@ export class FlowService implements PropertyDescriptorRetriever {
) as Observable<VersionControlInformationEntity>;
}

createFlowBranch(request: CreateFlowBranchRequest): Observable<VersionControlInformationEntity> {
const payload: {
processGroupRevision: Revision;
branch: string;
disconnectedNodeAcknowledged: boolean;
sourceBranch?: string;
sourceVersion?: string;
} = {
processGroupRevision: request.revision,
branch: request.branch,
disconnectedNodeAcknowledged: this.clusterConnectionService.isDisconnectionAcknowledged()
};

if (request.sourceBranch) {
payload.sourceBranch = request.sourceBranch;
}

if (request.sourceVersion) {
payload.sourceVersion = request.sourceVersion;
}

return this.httpClient.post(
`${FlowService.API}/versions/process-groups/${request.processGroupId}/branches`,
payload
) as Observable<VersionControlInformationEntity>;
}

stopVersionControl(request: StopVersionControlRequest): Observable<VersionControlInformationEntity> {
const params: any = {
version: request.revision.version,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/

import { createAction, props } from '@ngrx/store';
import { HttpErrorResponse } from '@angular/common/http';
import {
CenterComponentRequest,
ChangeColorRequest,
Expand All @@ -28,6 +29,8 @@ import {
CreateComponentResponse,
CreateConnection,
CreateConnectionRequest,
CreateBranchDialogRequest,
CreateFlowBranchRequest,
CreatePortRequest,
CreateProcessGroupDialogRequest,
CreateProcessGroupRequest,
Expand Down Expand Up @@ -71,6 +74,7 @@ import {
NavigateToQueueListing,
OpenChangeVersionDialogRequest,
OpenComponentDialogRequest,
OpenCreateBranchDialogRequest,
OpenGroupComponentsDialogRequest,
OpenLocalChangesDialogRequest,
OpenSaveVersionDialogRequest,
Expand Down Expand Up @@ -847,6 +851,31 @@ export const saveToFlowRegistrySuccess = createAction(
props<{ response: VersionControlInformationEntity }>()
);

export const openCreateBranchDialogRequest = createAction(
`${CANVAS_PREFIX} Open Create Branch Dialog Request`,
props<{ request: OpenCreateBranchDialogRequest }>()
);

export const openCreateBranchDialog = createAction(
`${CANVAS_PREFIX} Open Create Branch Dialog`,
props<{ request: CreateBranchDialogRequest }>()
);

export const createFlowBranch = createAction(
`${CANVAS_PREFIX} Create Flow Branch`,
props<{ request: CreateFlowBranchRequest }>()
);

export const createFlowBranchSuccess = createAction(
`${CANVAS_PREFIX} Create Flow Branch Success`,
props<{ response: VersionControlInformationEntity }>()
);

export const createFlowBranchFailure = createAction(
`${CANVAS_PREFIX} Create Flow Branch Failure`,
props<{ errorResponse: HttpErrorResponse }>()
);

export const stopVersionControlRequest = createAction(
`${CANVAS_PREFIX} Stop Version Control Request`,
props<{ request: ConfirmStopVersionControlRequest }>()
Expand Down
Loading
Loading