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

Support choosing tensorboard version from UI #2690

Merged
merged 37 commits into from
Dec 24, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
556ace1
Support select tensorflow image for tensorboard
dldaisy Nov 29, 2019
f9259bf
modify test for tensorflow version select
dldaisy Nov 29, 2019
8d541fb
delete not available image entry
dldaisy Nov 29, 2019
e0d2f4b
Support tensorflow image selection to run tensorboard
dldaisy Dec 3, 2019
c89a22b
format code with prettier
dldaisy Dec 4, 2019
1bf624a
use HasPrefix instead of regexp
dldaisy Dec 4, 2019
301ec41
delete
dldaisy Dec 6, 2019
07ba4fc
modified tensorboard test
dldaisy Dec 11, 2019
68ae3bf
delete tensorboard
dldaisy Dec 11, 2019
45af753
modify typo
dldaisy Dec 11, 2019
324bc83
test tensorboard
dldaisy Dec 11, 2019
81dab3b
Merge remote-tracking branch 'upstream/master'
dldaisy Dec 11, 2019
9578720
Merge remote-tracking branch 'upstream/master'
dldaisy Dec 11, 2019
48bffbc
tensorboard test
dldaisy Dec 11, 2019
65ff6bb
fuck
dldaisy Dec 11, 2019
df9c9fa
fuck2
dldaisy Dec 11, 2019
9a22416
modify test
dldaisy Dec 17, 2019
d896c4c
merge master
dldaisy Dec 17, 2019
13b5cf6
modify typo in tensorboard hint
dldaisy Dec 17, 2019
c05c1ee
npm run format
dldaisy Dec 18, 2019
6bfb09a
modify tensorboard snapshot
dldaisy Dec 18, 2019
65b1d4a
compatible with previous kfp version. Allow vacant tensorflowImage fi…
dldaisy Dec 19, 2019
e8f6644
add 2 tests for dialog
dldaisy Dec 19, 2019
5bed8d8
modify default tensorflow image to 1.13.2
dldaisy Dec 19, 2019
2900699
merge get version and get tensorboard; let --bind_all support tensorb…
dldaisy Dec 20, 2019
a8308c7
modify reconciler.go
dldaisy Dec 23, 2019
d5a2e15
reconciler rollback
dldaisy Dec 23, 2019
5c5687a
modify corresponding test for --bind_all
dldaisy Dec 23, 2019
0042e1f
modify requested chances 12/23
dldaisy Dec 23, 2019
a6374ae
formControl sorted alphabetically
dldaisy Dec 23, 2019
e3ee9f8
select sorted alphabetically
dldaisy Dec 23, 2019
8801bb2
modify details from PR request 12/24
dldaisy Dec 24, 2019
4bf953a
moidfy format
dldaisy Dec 24, 2019
34e26ed
modify details 12/23
dldaisy Dec 24, 2019
fec3711
modify snapshot
dldaisy Dec 24, 2019
04048ac
retest
dldaisy Dec 24, 2019
f0e1790
retest
dldaisy Dec 24, 2019
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
Prev Previous commit
Next Next commit
modify requested chances 12/23
  • Loading branch information
dldaisy committed Dec 23, 2019
commit 0042e1ff39d8e3e2f57a0aa987752335513e0315
333 changes: 333 additions & 0 deletions frontend/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"prettier": "1.18.2",
"react-router-test-context": "^0.1.0",
"react-test-renderer": "^16.5.2",
"snapshot-diff": "^0.6.1",
"swagger-ts-client": "^0.9.6",
"ts-node": "^7.0.1",
"ts-node-dev": "^1.0.0-pre.30",
Expand Down
2 changes: 1 addition & 1 deletion frontend/server/k8s-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export async function getTensorboardInstance(
* and returns the deleted podAddress
*/

export async function deleteTensorboardInstance(logdir: string, tfversion: string): Promise<void> {
export async function deleteTensorboardInstance(logdir: string): Promise<void> {
if (!k8sV1CustomObjectClient) {
throw new Error('Cannot access kubernetes Custom Object API');
}
Expand Down
24 changes: 16 additions & 8 deletions frontend/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,12 +294,14 @@ const getTensorboardHandler = async (req, res) => {
res.status(500).send('Cannot talk to Kubernetes master');
return;
}
const logdir = decodeURIComponent(req.query.logdir);
if (!logdir) {

if (!req.query.logdir) {
res.status(404).send('logdir argument is required');
return;
}

const logdir = decodeURIComponent(req.query.logdir);

try {
res.send(await k8sHelper.getTensorboardInstance(logdir));
} catch (err) {
Expand All @@ -312,19 +314,20 @@ const createTensorboardHandler = async (req, res) => {
res.status(500).send('Cannot talk to Kubernetes master');
return;
}
const logdir = decodeURIComponent(req.query.logdir);
const tfversion = decodeURIComponent(req.query.tfversion);

if (!logdir) {
if (!req.query.logdir) {
res.status(404).send('logdir argument is required');
return;
}

if (!tfversion) {
if (!req.query.tfversion) {
res.status(404).send('tensorflow version argument is required');
return;
}

const logdir = decodeURIComponent(req.query.logdir);
const tfversion = decodeURIComponent(req.query.tfversion);

try {
await k8sHelper.newTensorboardInstance(logdir, tfversion, podTemplateSpec);
const tensorboardAddress = await k8sHelper.waitForTensorboardInstance(logdir, 60 * 1000);
Expand All @@ -339,11 +342,16 @@ const deleteTensorboardHandler = async (req, res) => {
res.status(500).send('Cannot talk to Kubernetes master');
return;
}

if (!req.query.logdir) {
res.status(404).send('logdir argument is required');
return;
}

const logdir = decodeURIComponent(req.query.logdir);
const tfversion = decodeURIComponent(req.query.tfversion);

try {
await k8sHelper.deleteTensorboardInstance(logdir, tfversion);
await k8sHelper.deleteTensorboardInstance(logdir);
res.send('Tensorboard deleted.');
} catch (err) {
res.status(500).send('Failed to delete Tensorboard app: ' + JSON.stringify(err));
Expand Down
30 changes: 21 additions & 9 deletions frontend/src/components/viewers/Tensorboard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { PlotType } from './Viewer';
import { ReactWrapper, ShallowWrapper, shallow, mount } from 'enzyme';

describe('Tensorboard', () => {
const snapshotDiff = require('snapshot-diff');

let tree: ReactWrapper | ShallowWrapper;
beforeEach(() => {
jest.clearAllMocks();
Expand All @@ -37,19 +39,30 @@ describe('Tensorboard', () => {
jest.restoreAllMocks();
});

it('does not break on no config', async () => {
it('base component snapshot', async () => {
Apis.getTensorboardApp = jest.fn(() => Promise.resolve({ podAddress: '', tfVersion: '' }));
tree = shallow(<TensorboardViewer configs={[]} />);
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});

it('does not break on no config', async () => {
Apis.getTensorboardApp = jest.fn(() => Promise.resolve({ podAddress: '', tfVersion: '' }));
tree = shallow(<TensorboardViewer configs={[]} />);
const initialHtml = tree.debug();
dldaisy marked this conversation as resolved.
Show resolved Hide resolved

await TestUtils.flushPromises();
expect(snapshotDiff(tree.debug(), initialHtml)).toMatchSnapshot();
});

it('does not break on empty data', async () => {
Apis.getTensorboardApp = jest.fn(() => Promise.resolve({ podAddress: '', tfVersion: '' }));
const config = { type: PlotType.TENSORBOARD, url: '' };
tree = shallow(<TensorboardViewer configs={[config]} />);
const initialHtml = tree.debug();

await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
expect(snapshotDiff(tree.debug(), initialHtml)).toMatchSnapshot();
});

it('shows a link to the tensorboard instance if exists', async () => {
Expand All @@ -58,6 +71,7 @@ describe('Tensorboard', () => {
Promise.resolve({ podAddress: 'test/address', tfVersion: '1.14.0' }),
);
tree = shallow(<TensorboardViewer configs={[config]} />);

await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});
Expand All @@ -67,9 +81,10 @@ describe('Tensorboard', () => {
const getAppMock = () => Promise.resolve({ podAddress: '', tfVersion: '' });
const spy = jest.spyOn(Apis, 'getTensorboardApp').mockImplementation(getAppMock);
tree = shallow(<TensorboardViewer configs={[config]} />);
const initialHtml = tree.debug();

await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
expect(snapshotDiff(tree.debug(), initialHtml)).toMatchSnapshot();
expect(spy).toHaveBeenCalledWith(config.url);
});

Expand Down Expand Up @@ -131,7 +146,7 @@ describe('Tensorboard', () => {
expect(Apis.startTensorboardApp).toHaveBeenCalledWith('http%3A%2F%2Ftest%2Furl', '2.0.0');
});

it('delete the tensorboard instance, confirm in the dialog, \
it('delete the tensorboard instance, confirm in the dialog,\
then return back to previous page', async () => {
Apis.getTensorboardApp = jest.fn(() =>
Promise.resolve({ podAddress: 'podaddress', tfVersion: '1.14.0' }),
Expand All @@ -149,10 +164,7 @@ describe('Tensorboard', () => {
.find('Button')
.simulate('click');
tree.find('BusyButton').simulate('click');
expect(Apis.deleteTensorboardApp).toHaveBeenCalledWith(
encodeURIComponent('http://test/url'),
tree.state('tensorflowVersion'),
);
expect(Apis.deleteTensorboardApp).toHaveBeenCalledWith(encodeURIComponent('http://test/url'));
await TestUtils.flushPromises();
tree.update();
// the tree has returned to 'start tensorboard' page
Expand Down Expand Up @@ -192,7 +204,7 @@ describe('Tensorboard', () => {
.find('#cancel')
.find('Button')
.simulate('click');
tree.update();

expect(tree.findWhere(el => el.text() === 'Open Tensorboard').exists()).toBeTruthy();
expect(tree.findWhere(el => el.text() === 'Delete Tensorboard').exists()).toBeTruthy();
});
Expand Down
63 changes: 38 additions & 25 deletions frontend/src/components/viewers/Tensorboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import { classes, stylesheet } from 'typestyle';

export const css = stylesheet({
button: {
marginBottom: 20,
width: 150,
},
shortButton: {
width: 50,
},
formControl: {
minWidth: 120,
},
select: {
minHeight: 50,
},
});

export interface TensorboardViewerConfig extends ViewerConfig {
url: string;
Expand Down Expand Up @@ -71,7 +88,7 @@ class TensorboardViewer extends Viewer<TensorboardViewerProps, TensorboardViewer
this._checkTensorboardApp();
}

public onChangeFunc = (e: React.ChangeEvent<{ name?: string; value: unknown }>): void => {
public handleVersionSelect = (e: React.ChangeEvent<{ name?: string; value: unknown }>): void => {
this.setState({ tensorflowVersion: e.target.value as string });
dldaisy marked this conversation as resolved.
Show resolved Hide resolved
};

Expand All @@ -89,63 +106,63 @@ class TensorboardViewer extends Viewer<TensorboardViewerProps, TensorboardViewer
<div>
{this.state.podAddress && (
<div>
<div className={padding(20, 'b')}>{`Tensorboard is running for this output.`}</div>
<div
className={padding(20, 'b')}
>{`Tensorboard ${this.state.tensorflowVersion} is running for this output.`}</div>
<a
href={'apis/v1beta1/_proxy/' + podAddress}
target='_blank'
className={commonCss.unstyled}
>
<Button
className={commonCss.buttonAction}
className={classes(commonCss.buttonAction, css.button)}
disabled={this.state.busy}
style={{ marginBottom: 20, width: 150 }}
color={'primary'}
>
Open Tensorboard
</Button>
</a>

<div>
<Button
className={commonCss.buttonAction}
className={css.button}
disabled={this.state.busy}
id={'delete'}
title={`stop tensorboard and delete its instance`}
onClick={this._handleDeleteOpen.bind(this)}
style={{ marginBottom: 20, width: 150 }}
color={'default'}
>
Delete Tensorboard
</Button>
<Dialog
open={this.state.deleteDialogOpen}
onClose={this._handleDeleteClose.bind(this)}
onClose={this._handleDeleteClose}
aria-labelledby='dialog-title'
>
<DialogTitle style={{ cursor: 'move' }} id='dialog-title'>
<DialogTitle id='dialog-title'>
{`Stop Tensorboard ${this.state.tensorflowVersion}?`}
</DialogTitle>
<DialogContent>
<DialogContentText>
You can stop the current running tensorboard. The tensorboard viewer will also
be deleted from your GKE workloads.
be deleted from your workloads.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
className={css.shortButton}
id={'cancel'}
autoFocus={true}
onClick={this._handleDeleteClose.bind(this)}
style={{ width: 50 }}
onClick={this._handleDeleteClose}
color='primary'
>
Cancel
</Button>
<BusyButton
id={'confirm'}
className={commonCss.buttonAction}
className={classes(commonCss.buttonAction, css.shortButton)}
onClick={this._deleteTensorboard.bind(this)}
dldaisy marked this conversation as resolved.
Show resolved Hide resolved
busy={this.state.busy}
color='primary'
style={{ width: 50 }}
title={`Stop`}
/>
</DialogActions>
Expand All @@ -157,15 +174,14 @@ class TensorboardViewer extends Viewer<TensorboardViewerProps, TensorboardViewer
{!this.state.podAddress && (
<div>
<div className={padding(30, 'b')}>
<FormControl className='Launch Tensorboard' style={{ minWidth: 120 }}>
class
<FormControl className={css.formControl}>
<InputLabel htmlFor='grouped-select'>TF Version</InputLabel>
<Select
className={css.select}
value={this.state.tensorflowVersion}
input={<Input id='grouped-select' />}
onChange={this.onChangeFunc}
style={{
minHeight: 40,
}}
onChange={this.handleVersionSelect}
>
<ListSubheader>Tensoflow 1.x</ListSubheader>
<MenuItem value={'1.4.0'}>TensorFlow 1.4.0</MenuItem>
Expand Down Expand Up @@ -234,22 +250,19 @@ class TensorboardViewer extends Viewer<TensorboardViewerProps, TensorboardViewer
});
}

private async _deleteTensorboard(): Promise<void> {
private _deleteTensorboard = async () => {
// delete the already opened Tensorboard, clear the podAddress recorded in frontend,
// and return to the select & start tensorboard page
this.setState({ busy: true }, async () => {
await Apis.deleteTensorboardApp(
encodeURIComponent(this._buildUrl()),
encodeURIComponent(this.state.tensorflowVersion),
);
await Apis.deleteTensorboardApp(encodeURIComponent(this._buildUrl()));
dldaisy marked this conversation as resolved.
Show resolved Hide resolved
this.setState({
busy: false,
deleteDialogOpen: false,
podAddress: '',
tensorflowVersion: '',
});
});
}
};
}

export default TensorboardViewer;
Loading