Skip to content

Commit

Permalink
Allows uploading a pipeline via a URL
Browse files Browse the repository at this point in the history
Still needs verification on real cluster
  • Loading branch information
rileyjbauer committed Dec 17, 2018
1 parent 3306934 commit 69cfdec
Show file tree
Hide file tree
Showing 6 changed files with 735 additions and 359 deletions.
29 changes: 20 additions & 9 deletions frontend/mock-backend/mock-api-middleware.ts
Expand Up @@ -25,6 +25,7 @@ import { ApiListJobsResponse, ApiJob } from '../src/apis/job';
import { ApiRun, ApiListRunsResponse, ApiResourceType } from '../src/apis/run';
import { ApiListExperimentsResponse, ApiExperiment } from '../src/apis/experiment';
import RunUtils from '../src/lib/RunUtils';
import { Response } from 'express-serve-static-core';

const rocMetadataJsonPath = './eval-output/metadata.json';
const rocMetadataJsonPath2 = './eval-output/metadata2.json';
Expand Down Expand Up @@ -497,20 +498,16 @@ export default (app: express.Application) => {
res.send(JSON.stringify({ template: fs.readFileSync(filePath, 'utf-8') }));
});

app.options(v1beta1Prefix + '/pipelines/upload', (req, res) => {
res.send();
});
app.post(v1beta1Prefix + '/pipelines/upload', (req, res) => {
function mockCreatePipeline(res: Response, name: string, body?: any): void {
res.header('Content-Type', 'application/json');
// Don't allow uploading multiple pipelines with the same name
const pipelineName = decodeURIComponent(req.query.name);
if (fixedData.pipelines.find((p) => p.name === pipelineName)) {
if (fixedData.pipelines.find((p) => p.name === name)) {
res.status(502).send(
`A Pipeline named: "${pipelineName}" already exists. Please choose a different name.`);
`A Pipeline named: "${name}" already exists. Please choose a different name.`);
} else {
const pipeline = req.body;
const pipeline = body || {};
pipeline.id = 'new-pipeline-' + (fixedData.pipelines.length + 1);
pipeline.name = pipelineName;
pipeline.name = name;
pipeline.created_at = new Date();
pipeline.description =
'TODO: the mock middleware does not actually use the uploaded pipeline';
Expand All @@ -530,6 +527,20 @@ export default (app: express.Application) => {
res.send(fixedData.pipelines[fixedData.pipelines.length - 1]);
}, 1000);
}
}

app.options(v1beta1Prefix + '/pipelines', (req, res) => {
res.send();
});
app.post(v1beta1Prefix + '/pipelines', (req, res) => {
mockCreatePipeline(res, req.body.name);
});

app.options(v1beta1Prefix + '/pipelines/upload', (req, res) => {
res.send();
});
app.post(v1beta1Prefix + '/pipelines/upload', (req, res) => {
mockCreatePipeline(res, decodeURIComponent(req.query.name), req.body);
});

app.get('/artifacts/get', (req, res) => {
Expand Down
116 changes: 100 additions & 16 deletions frontend/src/components/UploadPipelineDialog.test.tsx
Expand Up @@ -15,69 +15,153 @@
*/

import * as React from 'react';
import { shallow } from 'enzyme';
import UploadPipelineDialog from './UploadPipelineDialog';
import { shallow, ReactWrapper, ShallowWrapper } from 'enzyme';
import UploadPipelineDialog, { ImportMethod } from './UploadPipelineDialog';
import TestUtils from '../TestUtils';

describe('UploadPipelineDialog', () => {
let tree: ReactWrapper | ShallowWrapper;

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

it('renders closed', () => {
const tree = shallow(<UploadPipelineDialog open={false} onClose={jest.fn()} />);
tree = shallow(<UploadPipelineDialog open={false} onClose={jest.fn()} />);
expect(tree).toMatchSnapshot();
});

it('renders open', () => {
const tree = shallow(<UploadPipelineDialog open={false} onClose={jest.fn()} />);
tree = shallow(<UploadPipelineDialog open={false} onClose={jest.fn()} />);
expect(tree).toMatchSnapshot();
});

it('renders an active dropzone', () => {
const tree = shallow(<UploadPipelineDialog open={false} onClose={jest.fn()} />);
tree = shallow(<UploadPipelineDialog open={false} onClose={jest.fn()} />);
tree.setState({ dropzoneActive: true });
expect(tree).toMatchSnapshot();
});

it('renders with a selected file to upload', () => {
const tree = shallow(<UploadPipelineDialog open={false} onClose={jest.fn()} />);
tree = shallow(<UploadPipelineDialog open={false} onClose={jest.fn()} />);
tree.setState({ fileToUpload: true });
expect(tree).toMatchSnapshot();
});

it('renders alternate UI for uploading via URL', () => {
tree = shallow(<UploadPipelineDialog open={false} onClose={jest.fn()} />);
tree.setState({ importMethod: ImportMethod.URL });
expect(tree).toMatchSnapshot();
});

it('calls close callback with null and empty string when canceled', () => {
const spy = jest.fn();
const tree = shallow(<UploadPipelineDialog open={false} onClose={spy} />);
tree = shallow(<UploadPipelineDialog open={false} onClose={spy} />);
tree.find('#cancelUploadBtn').simulate('click');
expect(spy).toHaveBeenCalledWith('', null, '');
expect(spy).toHaveBeenCalledWith(false, '', null, '', ImportMethod.LOCAL, '');
});

it('calls close callback with null and empty string when dialog is closed', () => {
const spy = jest.fn();
const tree = shallow(<UploadPipelineDialog open={false} onClose={spy} />);
tree = shallow(<UploadPipelineDialog open={false} onClose={spy} />);
tree.find('WithStyles(Dialog)').simulate('close');
expect(spy).toHaveBeenCalledWith('', null, '');
expect(spy).toHaveBeenCalledWith(false, '', null, '', ImportMethod.LOCAL, '');
});

it('calls close callback with file name, file object, and descriptio when confirmed', () => {
it('calls close callback with file name, file object, and description when confirmed', () => {
const spy = jest.fn();
const tree = shallow(<UploadPipelineDialog open={false} onClose={spy} />);
tree = shallow(<UploadPipelineDialog open={false} onClose={spy} />);
(tree.instance() as any)._dropzoneRef = { current: { open: () => null } };
(tree.instance() as UploadPipelineDialog).handleChange('uploadPipelineName')({ target: { value: 'test name' } });
tree.find('#confirmUploadBtn').simulate('click');
expect(spy).toHaveBeenLastCalledWith('test name', null, '');
expect(spy).toHaveBeenLastCalledWith(true, 'test name', null, '', ImportMethod.LOCAL, '');
});

it('sets the import method based on which radio button is toggled', () => {
tree = shallow(<UploadPipelineDialog open={false} onClose={jest.fn()} />);
// Import method is LOCAL by default
expect(tree.state('importMethod')).toBe(ImportMethod.LOCAL);

// Click 'Import by URL'
tree.find('#uploadFromUrlBtn').simulate('change');
expect(tree.state('importMethod')).toBe(ImportMethod.URL);

// Click back to default, 'Upload a file'
tree.find('#uploadLocalFileBtn').simulate('change');
expect(tree.state('importMethod')).toBe(ImportMethod.LOCAL);
});

it('resets all state if the dialog is closed and the callback returns true', async () => {
const spy = jest.fn(() => true);

tree = shallow(<UploadPipelineDialog open={false} onClose={spy} />);
tree.setState({
busy: true,
dropzoneActive: true,
file: {},
fileName: 'test file name',
fileUrl: 'https://some.url.com',
importMethod: ImportMethod.URL,
uploadPipelineDescription: 'test description',
uploadPipelineName: 'test pipeline name',
});

tree.find('#confirmUploadBtn').simulate('click');
await TestUtils.flushPromises();

expect(tree.state('busy')).toBe(false);
expect(tree.state('dropzoneActive')).toBe(false);
expect(tree.state('file')).toBeNull();
expect(tree.state('fileName')).toBe('');
expect(tree.state('fileUrl')).toBe('');
expect(tree.state('importMethod')).toBe(ImportMethod.LOCAL);
expect(tree.state('uploadPipelineDescription')).toBe('');
expect(tree.state('uploadPipelineName')).toBe('');
});

it('does not reset the state if the dialog is closed and the callback returns false', async () => {
const spy = jest.fn(() => false);

tree = shallow(<UploadPipelineDialog open={false} onClose={spy} />);
tree.setState({
busy: true,
dropzoneActive: true,
file: {},
fileName: 'test file name',
fileUrl: 'https://some.url.com',
importMethod: ImportMethod.URL,
uploadPipelineDescription: 'test description',
uploadPipelineName: 'test pipeline name',
});

tree.find('#confirmUploadBtn').simulate('click');
await TestUtils.flushPromises();

expect(tree.state('dropzoneActive')).toBe(true);
expect(tree.state('file')).toEqual({});
expect(tree.state('fileName')).toBe('test file name');
expect(tree.state('fileUrl')).toBe('https://some.url.com');
expect(tree.state('importMethod')).toBe(ImportMethod.URL);
expect(tree.state('uploadPipelineDescription')).toBe('test description');
expect(tree.state('uploadPipelineName')).toBe('test pipeline name');
// 'busy' is set to false regardless upon the callback returning
expect(tree.state('busy')).toBe(false);
});

it('sets an active dropzone on drag', () => {
const tree = shallow(<UploadPipelineDialog open={false} onClose={jest.fn()} />);
tree = shallow(<UploadPipelineDialog open={false} onClose={jest.fn()} />);
tree.find('#dropZone').simulate('dragEnter');
expect(tree.state()).toHaveProperty('dropzoneActive', true);
});

it('sets an inactive dropzone on drag leave', () => {
const tree = shallow(<UploadPipelineDialog open={false} onClose={jest.fn()} />);
tree = shallow(<UploadPipelineDialog open={false} onClose={jest.fn()} />);
tree.find('#dropZone').simulate('dragLeave');
expect(tree.state()).toHaveProperty('dropzoneActive', false);
});

it('sets a file object on drop', () => {
const tree = shallow(<UploadPipelineDialog open={false} onClose={jest.fn()} />);
tree = shallow(<UploadPipelineDialog open={false} onClose={jest.fn()} />);
const file = { name: 'test upload file' };
tree.find('#dropZone').simulate('drop', [file]);
expect(tree.state()).toHaveProperty('dropzoneActive', false);
Expand Down

0 comments on commit 69cfdec

Please sign in to comment.