Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new run button to pipeline details #1

Closed
wants to merge 3 commits into from
Closed
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
1 change: 1 addition & 0 deletions frontend/src/components/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const css = stylesheet({

export enum QUERY_PARAMS {
cloneFromRun = 'cloneFromRun',
pipelineFromRun = 'pipelineFromRun',
experimentId = 'experimentId',
isRecurring = 'recurring',
firstRunInExperiment = 'firstRunInExperiment',
Expand Down
160 changes: 119 additions & 41 deletions frontend/src/pages/NewRun.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ describe('NewRun', () => {
const MOCK_EXPERIMENT = newMockExperiment();
const MOCK_PIPELINE = newMockPipeline();
const MOCK_RUN_DETAIL = newMockRunDetail();
const MOCK_RUN_WITH_EMBEDDED_PIPELINE = newMockRunWithEmbeddedPipeline();

function newMockExperiment(): ApiExperiment {
return {
Expand Down Expand Up @@ -86,6 +87,13 @@ describe('NewRun', () => {
};
}

function newMockRunWithEmbeddedPipeline(): ApiRunDetail {
const runDetail = newMockRunDetail();
delete runDetail.run!.pipeline_spec!.pipeline_id;
runDetail.run!.pipeline_spec!.workflow_manifest = '{"parameters": []}';
return runDetail;
}

function generateProps(): PageProps {
return {
history: { push: historyPushSpy, replace: historyReplaceSpy } as any,
Expand All @@ -104,19 +112,7 @@ describe('NewRun', () => {
}

beforeEach(() => {
// Reset mocks
consoleErrorSpy.mockReset();
createJobSpy.mockReset();
createRunSpy.mockReset();
getExperimentSpy.mockReset();
getPipelineSpy.mockReset();
getRunSpy.mockReset();
historyPushSpy.mockReset();
historyReplaceSpy.mockReset();
updateBannerSpy.mockReset();
updateDialogSpy.mockReset();
updateSnackbarSpy.mockReset();
updateToolbarSpy.mockReset();
jest.clearAllMocks();

consoleErrorSpy.mockImplementation(() => null);
createRunSpy.mockImplementation(() => ({ id: 'new-run-id' }));
Expand All @@ -126,6 +122,7 @@ describe('NewRun', () => {
});

afterEach(() => {
jest.resetAllMocks();
tree.unmount();
});

Expand Down Expand Up @@ -390,6 +387,7 @@ describe('NewRun', () => {
expect(tree.state('pipelineSelectorOpen')).toBe(false);
await TestUtils.flushPromises();
});
});

describe('choosing an experiment', () => {
it('opens up the experiment selector modal when users clicks \'Choose\'', async () => {
Expand Down Expand Up @@ -645,45 +643,36 @@ describe('NewRun', () => {
});

it('loads and selects embedded pipeline from run', async () => {
const runDetail = newMockRunDetail();
delete runDetail.run!.pipeline_spec!.pipeline_id;
runDetail.run!.pipeline_spec!.workflow_manifest = '{"parameters": []}';
const props = generateProps();
props.location.search = `?${QUERY_PARAMS.cloneFromRun}=${runDetail.run!.id}`;
props.location.search = `?${QUERY_PARAMS.cloneFromRun}=${MOCK_RUN_WITH_EMBEDDED_PIPELINE.run!.id}`;

getRunSpy.mockImplementation(() => runDetail);
getRunSpy.mockImplementation(() => MOCK_RUN_WITH_EMBEDDED_PIPELINE);

tree = shallow(<TestNewRun {...props} />);
await TestUtils.flushPromises();

expect(updateBannerSpy).toHaveBeenCalledTimes(1);
expect(tree.state('clonedRunPipeline')).toEqual({ parameters: [] });
expect(tree.state('usePipelineFromClonedRun')).toBe(true);
expect(tree.state('pipelineFromRun')).toEqual({ parameters: [] });
expect(tree.state('usePipelineFromRun')).toBe(true);
});

it('shows switching controls when run has embedded pipeline, selects that pipeline by default,' +
' and hides pipeline selector', async () => {
const runDetail = newMockRunDetail();
delete runDetail.run!.pipeline_spec!.pipeline_id;
runDetail.run!.pipeline_spec!.workflow_manifest = '{"parameters": []}';
const props = generateProps();
props.location.search = `?${QUERY_PARAMS.cloneFromRun}=${runDetail.run!.id}`;
props.location.search = `?${QUERY_PARAMS.cloneFromRun}=${MOCK_RUN_WITH_EMBEDDED_PIPELINE.run!.id}`;

getRunSpy.mockImplementation(() => runDetail);
getRunSpy.mockImplementation(() => MOCK_RUN_WITH_EMBEDDED_PIPELINE);

tree = shallow(<TestNewRun {...props} />);
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});

it('shows pipeline selector when switching from embedded pipeline to select pipeline', async () => {
const runDetail = newMockRunDetail();
delete runDetail.run!.pipeline_spec!.pipeline_id;
runDetail.run!.pipeline_spec!.workflow_manifest = '{"parameters": []}';
const props = generateProps();
props.location.search = `?${QUERY_PARAMS.cloneFromRun}=${runDetail.run!.id}`;
props.location.search = `?${QUERY_PARAMS.cloneFromRun}=${MOCK_RUN_WITH_EMBEDDED_PIPELINE.run!.id}`;

getRunSpy.mockImplementation(() => runDetail);
getRunSpy.mockImplementation(() => MOCK_RUN_WITH_EMBEDDED_PIPELINE);

tree = shallow(<TestNewRun {...props} />);
await TestUtils.flushPromises();
Expand All @@ -692,13 +681,10 @@ describe('NewRun', () => {
});

it('resets selected pipeline from embedded when switching to select from pipeline list, and back', async () => {
const runDetail = newMockRunDetail();
delete runDetail.run!.pipeline_spec!.pipeline_id;
runDetail.run!.pipeline_spec!.workflow_manifest = '{"parameters": []}';
const props = generateProps();
props.location.search = `?${QUERY_PARAMS.cloneFromRun}=${runDetail.run!.id}`;
props.location.search = `?${QUERY_PARAMS.cloneFromRun}=${MOCK_RUN_WITH_EMBEDDED_PIPELINE.run!.id}`;

getRunSpy.mockImplementation(() => runDetail);
getRunSpy.mockImplementation(() => MOCK_RUN_WITH_EMBEDDED_PIPELINE);

tree = shallow(<TestNewRun {...props} />);
await TestUtils.flushPromises();
Expand Down Expand Up @@ -783,7 +769,103 @@ describe('NewRun', () => {
mode: 'error',
}));
});
});

describe('arriving from pipeline details page', () => {

it('indicates that a pipeline is preselected and provides a means of selecting a different pipeline', async () => {
const props = generateProps();
props.location.search = `?${QUERY_PARAMS.pipelineFromRun}=${MOCK_RUN_WITH_EMBEDDED_PIPELINE.run!.id}`;

getRunSpy.mockImplementationOnce(() => MOCK_RUN_WITH_EMBEDDED_PIPELINE);

tree = shallow(<TestNewRun {...props as any} />);
await TestUtils.flushPromises();

expect(tree.state('usePipelineFromRun')).toBe(true);
expect(tree.state('usePipelineFromRunLabel')).toBe('Use pipeline from previous step');
expect(tree).toMatchSnapshot();
});

it('retrieves the run with the embedded pipeline', async () => {
const props = generateProps();
props.location.search = `?${QUERY_PARAMS.pipelineFromRun}=${MOCK_RUN_WITH_EMBEDDED_PIPELINE.run!.id}`;

getRunSpy.mockImplementationOnce(() => MOCK_RUN_WITH_EMBEDDED_PIPELINE);

tree = shallow(<TestNewRun {...props as any} />);
await TestUtils.flushPromises();

expect(getRunSpy).toHaveBeenLastCalledWith(MOCK_RUN_WITH_EMBEDDED_PIPELINE.run!.id);
});

it('parses the embedded pipeline and stores it in state', async () => {
const runDetail = newMockRunWithEmbeddedPipeline();
runDetail.run!.pipeline_spec!.workflow_manifest = JSON.stringify(MOCK_PIPELINE);
const props = generateProps();
props.location.search = `?${QUERY_PARAMS.pipelineFromRun}=${runDetail.run!.id}`;

getRunSpy.mockImplementationOnce(() => runDetail);

tree = shallow(<TestNewRun {...props as any} />);
await TestUtils.flushPromises();

expect(tree.state('pipeline')).toEqual(MOCK_PIPELINE);
expect(tree.state('pipelineFromRun')).toEqual(MOCK_PIPELINE);
expect(tree.state('pipelineName')).toEqual(MOCK_PIPELINE.name);
});

it('displays a page error if it fails to parse the embedded pipeline', async () => {
const runDetail = newMockRunWithEmbeddedPipeline();
runDetail.run!.pipeline_spec!.workflow_manifest = 'not JSON';
const props = generateProps();
props.location.search = `?${QUERY_PARAMS.pipelineFromRun}=${runDetail.run!.id}`;

getRunSpy.mockImplementationOnce(() => runDetail);

tree = shallow(<TestNewRun {...props as any} />);
await TestUtils.flushPromises();

expect(updateBannerSpy).toHaveBeenLastCalledWith(expect.objectContaining({
additionalInfo: 'Unexpected token o in JSON at position 1',
message: 'Error: failed to parse the embedded pipeline\'s spec: not JSON. Click Details for more information.',
mode: 'error',
}));
});

it('displays a page error if ', async () => {
const runDetail = newMockRunWithEmbeddedPipeline();
// Remove workflow_manifest entirely
delete runDetail.run!.pipeline_spec!.workflow_manifest;
const props = generateProps();
props.location.search = `?${QUERY_PARAMS.pipelineFromRun}=${runDetail.run!.id}`;

getRunSpy.mockImplementationOnce(() => runDetail);

tree = shallow(<TestNewRun {...props as any} />);
await TestUtils.flushPromises();

expect(updateBannerSpy).toHaveBeenLastCalledWith(expect.objectContaining({
message: `Error: somehow the run provided in the query params: ${runDetail.run!.id} had no embedded pipeline.`,
mode: 'error',
}));
});

it('displays a page error if it fails to retrieve the run containing the embedded pipeline', async () => {
const props = generateProps();
props.location.search = `?${QUERY_PARAMS.pipelineFromRun}=${MOCK_RUN_WITH_EMBEDDED_PIPELINE.run!.id}`;

TestUtils.makeErrorResponseOnce(getRunSpy, 'test - error!');

tree = shallow(<TestNewRun {...props as any} />);
await TestUtils.flushPromises();

expect(updateBannerSpy).toHaveBeenLastCalledWith(expect.objectContaining({
additionalInfo: 'test - error!',
message: `Error: failed to retrieve the specified run: ${MOCK_RUN_WITH_EMBEDDED_PIPELINE.run!.id}. Click Details for more information.`,
mode: 'error',
}));
});
});

describe('creating a new run', () => {
Expand Down Expand Up @@ -888,13 +970,10 @@ describe('NewRun', () => {
});

it('copies pipeline from run in the create API call when cloning a run with embedded pipeline', async () => {
const runDetail = newMockRunDetail();
delete runDetail.run!.pipeline_spec!.pipeline_id;
runDetail.run!.pipeline_spec!.workflow_manifest = '{"parameters": []}';
const props = generateProps();
props.location.search = `?${QUERY_PARAMS.cloneFromRun}=${runDetail.run!.id}`;
props.location.search = `?${QUERY_PARAMS.cloneFromRun}=${MOCK_RUN_WITH_EMBEDDED_PIPELINE.run!.id}`;

getRunSpy.mockImplementation(() => runDetail);
getRunSpy.mockImplementation(() => MOCK_RUN_WITH_EMBEDDED_PIPELINE);

tree = shallow(<TestNewRun {...props} />);
await TestUtils.flushPromises();
Expand Down Expand Up @@ -1077,7 +1156,6 @@ describe('NewRun', () => {
open: true,
});
});

});

describe('creating a new recurring run', () => {
Expand Down
Loading