From 5f66de118a60281e98a6364ac0b3808eec6fc9c5 Mon Sep 17 00:00:00 2001 From: Matei Zaharia Date: Fri, 10 Aug 2018 22:27:34 -0700 Subject: [PATCH] Small tracking UI improvements (#263) This PR makes a few small fixes: 1) Got rid of NaNs shown on the Compare Runs page for run IDs that didn't have a specific metric. 2) Changed the collapse/expand button for opening the Experiments sidebar to have `cursor: pointer` so it looks like a clickable object. 3) Added a breadcrumb link back to each experiment on the Run, Compare Runs and Metrics pages (the "Default > ..." in the headings below): ![screen shot 2018-08-07 at 3 52 05 pm](https://user-images.githubusercontent.com/228859/43806814-63bbf14c-9a5a-11e8-960e-4a24eee1aefc.png) ![screen shot 2018-08-07 at 3 51 47 pm](https://user-images.githubusercontent.com/228859/43806820-66ddad70-9a5a-11e8-997a-4d39ffdb4503.png) ![screen shot 2018-08-07 at 6 52 16 pm](https://user-images.githubusercontent.com/228859/43811765-173593b4-9a73-11e8-9398-32a19dfee5ba.png) --- mlflow/server/js/src/Routes.js | 15 ++- mlflow/server/js/src/components/App.css | 10 ++ .../js/src/components/BreadcrumbTitle.js | 48 ++++++++ .../js/src/components/CompareRunPage.js | 11 +- .../js/src/components/CompareRunScatter.css | 17 ++- .../js/src/components/CompareRunScatter.js | 25 +++- .../js/src/components/CompareRunView.css | 5 +- .../js/src/components/CompareRunView.js | 49 +++++--- .../js/src/components/ExperimentListView.css | 1 + .../js/src/components/ExperimentListView.js | 4 +- .../js/src/components/ExperimentView.js | 9 +- mlflow/server/js/src/components/HomePage.css | 1 + mlflow/server/js/src/components/HomePage.js | 4 +- mlflow/server/js/src/components/MetricPage.js | 16 ++- .../server/js/src/components/MetricView.css | 4 + mlflow/server/js/src/components/MetricView.js | 114 ++++++++++-------- mlflow/server/js/src/components/RunPage.js | 11 +- mlflow/server/js/src/components/RunView.css | 4 +- mlflow/server/js/src/components/RunView.js | 23 ++-- 19 files changed, 258 insertions(+), 113 deletions(-) create mode 100644 mlflow/server/js/src/components/BreadcrumbTitle.js diff --git a/mlflow/server/js/src/Routes.js b/mlflow/server/js/src/Routes.js index f40cdc3858396..0d73c28fae61e 100644 --- a/mlflow/server/js/src/Routes.js +++ b/mlflow/server/js/src/Routes.js @@ -1,21 +1,28 @@ class Routes { static rootRoute = "/"; + static getExperimentPageRoute(experimentId) { return `/experiments/${experimentId}`; } + static experimentPageRoute = "/experiments/:experimentId"; + static getRunPageRoute(experimentId, runUuid) { return `/experiments/${experimentId}/runs/${runUuid}`; } + static runPageRoute = "/experiments/:experimentId/runs/:runUuid"; - static getMetricPageRoute(runUuids, metricKey) { - return `/metric/${metricKey}?runs=${JSON.stringify(runUuids)}`; + + static getMetricPageRoute(runUuids, metricKey, experimentId) { + return `/metric/${metricKey}?runs=${JSON.stringify(runUuids)}&experiment=${experimentId}`; } + static metricPageRoute = "/metric/:metricKey"; - static getCompareRunPageRoute(runUuids) { - return `/compare-runs?runs=${JSON.stringify(runUuids)}` + static getCompareRunPageRoute(runUuids, experimentId) { + return `/compare-runs?runs=${JSON.stringify(runUuids)}&experiment=${experimentId}`; } + static compareRunPageRoute = "/compare-runs" } diff --git a/mlflow/server/js/src/components/App.css b/mlflow/server/js/src/components/App.css index 9c19d5de1b834..b9e9cb56c6078 100644 --- a/mlflow/server/js/src/components/App.css +++ b/mlflow/server/js/src/components/App.css @@ -107,6 +107,10 @@ h1 { color: #333; } +h1 a, h1 a:hover, h1 a:active, h1 a:visited { + color: #333; +} + h2 { font-size: 18px; font-weight: normal; @@ -149,3 +153,9 @@ table th { color: #888888; font-weight: 500; } + +.breadcrumb-chevron { + font-size: 75%; + margin-left: 10px; + margin-right: 8px; +} diff --git a/mlflow/server/js/src/components/BreadcrumbTitle.js b/mlflow/server/js/src/components/BreadcrumbTitle.js new file mode 100644 index 0000000000000..3a90a39f351e8 --- /dev/null +++ b/mlflow/server/js/src/components/BreadcrumbTitle.js @@ -0,0 +1,48 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { Experiment } from "../sdk/MlflowMessages"; +import { Link } from 'react-router-dom'; +import Routes from "../Routes"; + +/** + * A title component that creates a

with breadcrumbs pointing to an experiment and optionally + * a run or a run comparison page. + */ +export default class BreadcrumbTitle extends Component { + static propTypes = { + experiment: PropTypes.instanceOf(Experiment).isRequired, + runUuids: PropTypes.arrayOf(String), // Optional because not all pages are nested under runs + title: PropTypes.any.isRequired, + }; + + render() { + const {experiment, runUuids, title} = this.props; + const experimentId = experiment.getExperimentId(); + const experimentLink = ( + + {experiment.getName()} + + ); + let runsLink = null; + if (runUuids) { + runsLink = (runUuids.length === 1 ? + + Run {runUuids[0]} + + : + + Comparing {runUuids.length} Runs + + ); + } + let chevron = ; + return ( +

+ {experimentLink} + {chevron} + { runsLink ? [runsLink, chevron] : [] } + {title} +

+ ); + } +} diff --git a/mlflow/server/js/src/components/CompareRunPage.js b/mlflow/server/js/src/components/CompareRunPage.js index aee6d75ea2fa9..015d5463cd368 100644 --- a/mlflow/server/js/src/components/CompareRunPage.js +++ b/mlflow/server/js/src/components/CompareRunPage.js @@ -2,17 +2,21 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import qs from 'qs'; import { connect } from 'react-redux'; -import { getRunApi, getUUID } from '../Actions'; +import { getExperimentApi, getRunApi, getUUID } from '../Actions'; import RequestStateWrapper from './RequestStateWrapper'; import CompareRunView from './CompareRunView'; class CompareRunPage extends Component { static propTypes = { + experimentId: PropTypes.number.isRequired, runUuids: PropTypes.arrayOf(String).isRequired, }; componentWillMount() { this.requestIds = []; + const experimentRequestId = getUUID(); + this.props.dispatch(getExperimentApi(this.props.experimentId, experimentRequestId)); + this.requestIds.push(experimentRequestId); this.props.runUuids.forEach((runUuid) => { const requestId = getUUID(); this.requestIds.push(requestId); @@ -23,7 +27,7 @@ class CompareRunPage extends Component { render() { return ( - + ); } @@ -33,7 +37,8 @@ const mapStateToProps = (state, ownProps) => { const { location } = ownProps; const searchValues = qs.parse(location.search); const runUuids = JSON.parse(searchValues["?runs"]); - return { runUuids }; + const experimentId = parseInt(searchValues["experiment"], 10); + return { experimentId, runUuids }; }; export default connect(mapStateToProps)(CompareRunPage); diff --git a/mlflow/server/js/src/components/CompareRunScatter.css b/mlflow/server/js/src/components/CompareRunScatter.css index 82b0c1d8b30c2..913636316ef6a 100644 --- a/mlflow/server/js/src/components/CompareRunScatter.css +++ b/mlflow/server/js/src/components/CompareRunScatter.css @@ -1,11 +1,16 @@ .scatter-tooltip { width: 300px; - font-size: 90%; +} + +.scatter-tooltip h3 { + font-size: 105%; + color: #888; } .scatter-tooltip h4 { - font-size: 110%; - margin: 4px 0 0 0; + font-size: 105%; + color: #888; + margin: 0; } .scatter-tooltip ul { @@ -16,8 +21,8 @@ .scatter-tooltip ul li { display: block; margin: 0; - padding: 0 0 0 16px; - text-indent: -13px; + padding: 0 0 0 0; + text-indent: 0; } .scatter-tooltip ul li .value { @@ -28,4 +33,4 @@ overflow: hidden; text-indent: 0; vertical-align: top; -} \ No newline at end of file +} diff --git a/mlflow/server/js/src/components/CompareRunScatter.js b/mlflow/server/js/src/components/CompareRunScatter.js index b69c895fd46e8..c943e9877fc31 100644 --- a/mlflow/server/js/src/components/CompareRunScatter.js +++ b/mlflow/server/js/src/components/CompareRunScatter.js @@ -6,7 +6,16 @@ import './CompareRunView.css'; import { RunInfo } from '../sdk/MlflowMessages'; import Utils from '../utils/Utils'; import { getLatestMetrics } from '../reducers/MetricReducer'; -import {ScatterChart, Scatter, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Label} from 'recharts'; +import { + ScatterChart, + Scatter, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, + Label, +} from 'recharts'; import './CompareRunScatter.css'; class CompareRunScatter extends Component { @@ -19,6 +28,8 @@ class CompareRunScatter extends Component { constructor(props) { super(props); + this.renderTooltip = this.renderTooltip.bind(this); + this.metricKeys = CompareRunScatter.getKeys(this.props.metricLists); this.paramKeys = CompareRunScatter.getKeys(this.props.paramLists); @@ -126,15 +137,17 @@ class CompareRunScatter extends Component { {this.renderAxisLabel('y')} - - + + content={this.renderTooltip} + /> + fill='#AE76A6' + isAnimationActive={false} + /> diff --git a/mlflow/server/js/src/components/CompareRunView.css b/mlflow/server/js/src/components/CompareRunView.css index 0b91b8f7329e9..9c95d0bc8d437 100644 --- a/mlflow/server/js/src/components/CompareRunView.css +++ b/mlflow/server/js/src/components/CompareRunView.css @@ -12,7 +12,7 @@ .run-metadata-label { display: inline-block; - width: 500px; + width: 250px; font-size: 16px; color: #888; } @@ -26,4 +26,5 @@ flex: 1; padding-left: 8px; font-size: 16px; -} \ No newline at end of file +} + diff --git a/mlflow/server/js/src/components/CompareRunView.js b/mlflow/server/js/src/components/CompareRunView.js index 112a42f7185d2..01f6d2641f923 100644 --- a/mlflow/server/js/src/components/CompareRunView.js +++ b/mlflow/server/js/src/components/CompareRunView.js @@ -1,18 +1,20 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { getParams, getRunInfo } from '../reducers/Reducers'; +import { getExperiment, getParams, getRunInfo } from '../reducers/Reducers'; import { connect } from 'react-redux'; import './CompareRunView.css'; -import { RunInfo } from '../sdk/MlflowMessages'; +import { Experiment, RunInfo } from '../sdk/MlflowMessages'; import HtmlTableView from './HtmlTableView'; import CompareRunScatter from './CompareRunScatter'; import Routes from '../Routes'; import { Link } from 'react-router-dom'; import Utils from '../utils/Utils'; import { getLatestMetrics } from '../reducers/MetricReducer'; +import BreadcrumbTitle from "./BreadcrumbTitle"; class CompareRunView extends Component { static propTypes = { + experiment: PropTypes.instanceOf(Experiment).isRequired, runInfos: PropTypes.arrayOf(RunInfo).isRequired, metricLists: PropTypes.arrayOf(Array).isRequired, paramLists: PropTypes.arrayOf(Array).isRequired, @@ -31,22 +33,29 @@ class CompareRunView extends Component { flex: '1', }, 'td-first': { - width: '500px', + width: '250px', }, 'th-first': { - width: '500px', + width: '250px', }, }; + const experiment = this.props.experiment; + const experimentId = experiment.getExperimentId(); return (
-

Comparing {this.props.runInfos.length} Runs

+
-
Run UUID:
+
Run ID:
- {this.props.runInfos.map((r, idx) => + {this.props.runInfos.map(r =>
- {r.getRunUuid()} + + {r.getRunUuid()} +
)}
@@ -70,7 +79,7 @@ class CompareRunView extends Component {

Metrics

@@ -83,13 +92,14 @@ const mapStateToProps = (state, ownProps) => { const runInfos = []; const metricLists = []; const paramLists = []; - const { runUuids } = ownProps; + const { experimentId, runUuids } = ownProps; + const experiment = getExperiment(experimentId, state); runUuids.forEach((runUuid) => { runInfos.push(getRunInfo(runUuid, state)); metricLists.push(Object.values(getLatestMetrics(runUuid, state))); paramLists.push(Object.values(getParams(runUuid, state))); }); - return { runInfos, metricLists, paramLists }; + return { experiment, runInfos, metricLists, paramLists }; }; export default connect(mapStateToProps)(CompareRunView); @@ -121,14 +131,14 @@ class Private { return rows; } - static getLatestMetricRows(runInfos, metricLists) { + static getLatestMetricRows(runInfos, metricLists, experimentId) { const rows = []; // Map of parameter key to a map of (runUuid -> value) const metricKeyValueList = []; metricLists.forEach((metricList) => { const curKeyValueObj = {}; metricList.forEach((metric) => { - curKeyValueObj[metric.key] = Utils.formatMetric(metric.value); + curKeyValueObj[metric.key] = metric.value; }); metricKeyValueList.push(curKeyValueObj); }); @@ -138,10 +148,19 @@ class Private { // Figure out which runUuids actually have this metric. const runUuidsWithMetric = Object.keys(mergedMetrics[metricKey]); const curRow = []; - curRow.push({metricKey}); + curRow.push( + + {metricKey} + + + ); runInfos.forEach((r) => { const curUuid = r.run_uuid; - curRow.push(Math.round(mergedMetrics[metricKey][curUuid] * 1e4)/1e4); + if (mergedMetrics[metricKey].hasOwnProperty(curUuid)) { + curRow.push(Utils.formatMetric(mergedMetrics[metricKey][curUuid])); + } else { + curRow.push(""); + } }); rows.push(curRow) }); diff --git a/mlflow/server/js/src/components/ExperimentListView.css b/mlflow/server/js/src/components/ExperimentListView.css index a6f0c159e97da..226fdfc6f2684 100644 --- a/mlflow/server/js/src/components/ExperimentListView.css +++ b/mlflow/server/js/src/components/ExperimentListView.css @@ -45,4 +45,5 @@ height: 24px; text-align: center; margin-left: 68px; + cursor: pointer; } diff --git a/mlflow/server/js/src/components/ExperimentListView.js b/mlflow/server/js/src/components/ExperimentListView.js index afeb955e998a1..40091e29f4348 100644 --- a/mlflow/server/js/src/components/ExperimentListView.js +++ b/mlflow/server/js/src/components/ExperimentListView.js @@ -38,7 +38,9 @@ class ExperimentListView extends Component {

Experiments

- +
{this.props.experiments.map((e) => { diff --git a/mlflow/server/js/src/components/ExperimentView.js b/mlflow/server/js/src/components/ExperimentView.js index 5dba97cc992cd..5ade62c758d64 100644 --- a/mlflow/server/js/src/components/ExperimentView.js +++ b/mlflow/server/js/src/components/ExperimentView.js @@ -330,7 +330,8 @@ class ExperimentView extends Component { onCompare() { const runsSelectedList = Object.keys(this.state.runsSelected); - this.props.history.push(Routes.getCompareRunPageRoute(runsSelectedList)); + this.props.history.push(Routes.getCompareRunPageRoute( + runsSelectedList, this.props.experiment.getExperimentId())); } onDownloadCsv() { @@ -428,13 +429,13 @@ class ExperimentView extends Component { if (sortState.isMetric !== isMetric || sortState.isParam !== isParam || sortState.key !== key) - return "sortable" + return "sortable"; return "sortable sorted " + (sortState.ascending?"asc":"desc"); - } + }; const getHeaderCell = (key, text) => { return onSortBy(false, false, key)}>{text} - } + }; const numParams = paramKeyList.length; const numMetrics = metricKeyList.length; diff --git a/mlflow/server/js/src/components/HomePage.css b/mlflow/server/js/src/components/HomePage.css index 624dfa79921fa..2a8585c70b259 100644 --- a/mlflow/server/js/src/components/HomePage.css +++ b/mlflow/server/js/src/components/HomePage.css @@ -32,5 +32,6 @@ height: 24px; text-align: center; vertical-align: bottom; + cursor: pointer; } /* END css for when experiment list collapsed */ diff --git a/mlflow/server/js/src/components/HomePage.js b/mlflow/server/js/src/components/HomePage.js index e3481aea45098..9470f569b660e 100644 --- a/mlflow/server/js/src/components/HomePage.js +++ b/mlflow/server/js/src/components/HomePage.js @@ -56,7 +56,9 @@ class HomePage extends Component { return (
- +
diff --git a/mlflow/server/js/src/components/MetricPage.js b/mlflow/server/js/src/components/MetricPage.js index 5f867d0eed170..1e58c836af2ad 100644 --- a/mlflow/server/js/src/components/MetricPage.js +++ b/mlflow/server/js/src/components/MetricPage.js @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import qs from 'qs'; -import { getMetricHistoryApi, getUUID } from '../Actions'; +import { getExperimentApi, getMetricHistoryApi, getUUID } from '../Actions'; import RequestStateWrapper from './RequestStateWrapper'; import NotFoundPage from './NotFoundPage'; import MetricView from './MetricView'; @@ -15,6 +15,11 @@ class MetricPage extends Component { componentWillMount() { this.requestIds = []; + if (this.props.experimentId !== null) { + const experimentRequestId = getUUID(); + this.props.dispatch(getExperimentApi(this.props.experimentId, experimentRequestId)); + this.requestIds.push(experimentRequestId); + } this.props.runUuids.forEach((runUuid) => { const requestId = getUUID(); this.requestIds.push(requestId); @@ -25,7 +30,9 @@ class MetricPage extends Component { render() { let view; if (this.props.runUuids.length >= 1) { - view = + view = } else { view = } @@ -41,10 +48,15 @@ const mapStateToProps = (state, ownProps) => { const { match, location } = ownProps; const searchValues = qs.parse(location.search); const runUuids = JSON.parse(searchValues["?runs"]); + let experimentId = null; + if (searchValues.hasOwnProperty("experiment")) { + experimentId = parseInt(searchValues["experiment"], 10); + } const { metricKey } = match.params; return { runUuids, metricKey, + experimentId, } }; diff --git a/mlflow/server/js/src/components/MetricView.css b/mlflow/server/js/src/components/MetricView.css index b316e5533b135..4e13afe48b140 100644 --- a/mlflow/server/js/src/components/MetricView.css +++ b/mlflow/server/js/src/components/MetricView.css @@ -3,3 +3,7 @@ div.MetricView { max-width: 1200px; max-height: 10%; } + +div.MetricView h1 { + margin-bottom: 24px; +} diff --git a/mlflow/server/js/src/components/MetricView.js b/mlflow/server/js/src/components/MetricView.js index 6c0782d949e70..3e6027896cf1d 100644 --- a/mlflow/server/js/src/components/MetricView.js +++ b/mlflow/server/js/src/components/MetricView.js @@ -4,6 +4,10 @@ import { LineChart, BarChart, Bar, XAxis, Tooltip, CartesianGrid, Line, YAxis, R import { connect } from 'react-redux'; import Utils from '../utils/Utils'; import { getMetricsByKey } from '../reducers/MetricReducer'; +import './MetricView.css'; +import { Experiment } from "../sdk/MlflowMessages"; +import { getExperiment } from "../reducers/Reducers"; +import BreadcrumbTitle from "./BreadcrumbTitle"; const COLORS = [ "#993955", @@ -16,69 +20,72 @@ const COLORS = [ class MetricView extends Component { static propTypes = { + experiment: PropTypes.instanceOf(Experiment).isRequired, title: PropTypes.element.isRequired, // Object with keys from Metric json and also metrics: PropTypes.arrayOf(Object).isRequired, runUuids: PropTypes.arrayOf(String).isRequired, }; - render() { - if (this.props.metrics.length === 1) { - return ( -
-

{this.props.title}

- - - - - - - - {this.props.runUuids.map((uuid, idx) => ( - - ))} - - -
- ) - } else { - return ( -
-

{this.props.title}

- - - - - - - - {this.props.runUuids.map((uuid, idx) => ( - - ))} - - -
- ) - } + render() { + const { experiment, runUuids, title, metrics } = this.props; + if (metrics.length === 1) { + return ( +
+ + + + + + + + + {runUuids.map((uuid, idx) => ( + + ))} + + +
+ ); + } else { + return ( +
+ + + + + + + + + {runUuids.map((uuid, idx) => ( + + ))} + + +
+ ); } + } } const mapStateToProps = (state, ownProps) => { - const { metricKey, runUuids } = ownProps; + const { metricKey, runUuids, experimentId } = ownProps; + const experiment = experimentId !== null ? getExperiment(experimentId, state) : null; let maxLength = 0; runUuids.forEach(runUuid => { maxLength = Math.max(maxLength, getMetricsByKey(runUuid, metricKey, state).length) @@ -94,6 +101,7 @@ const mapStateToProps = (state, ownProps) => { } }); return { + experiment, metrics, title: {metricKey}, runUuids: runUuids, diff --git a/mlflow/server/js/src/components/RunPage.js b/mlflow/server/js/src/components/RunPage.js index 6416b9c124beb..c1228a6dfecb9 100644 --- a/mlflow/server/js/src/components/RunPage.js +++ b/mlflow/server/js/src/components/RunPage.js @@ -10,6 +10,7 @@ class RunPage extends Component { static propTypes = { match: PropTypes.object.isRequired, runUuid: PropTypes.string.isRequired, + experimentId: PropTypes.number.isRequired, }; state = { @@ -34,7 +35,9 @@ class RunPage extends Component { > Routes.getMetricPageRoute([this.props.runUuid], key)} + getMetricPagePath={ + (key) => Routes.getMetricPageRoute([this.props.runUuid], key, this.props.experimentId) + } experimentId={this.props.experimentId} /> @@ -45,7 +48,11 @@ class RunPage extends Component { const mapStateToProps = (state, ownProps) => { const { match } = ownProps; - return { runUuid: match.params.runUuid, match, experimentId: match.params.experimentId }; + return { + runUuid: match.params.runUuid, + match, + experimentId: parseInt(match.params.experimentId, 10) + }; }; export default connect(mapStateToProps)(RunPage); diff --git a/mlflow/server/js/src/components/RunView.css b/mlflow/server/js/src/components/RunView.css index 99de8cd049a57..0bc8ca31bf690 100644 --- a/mlflow/server/js/src/components/RunView.css +++ b/mlflow/server/js/src/components/RunView.css @@ -22,7 +22,7 @@ display: flex; flex-wrap: wrap; align-items: center; - border-bottom: solid 1px #e2e2e2; + padding-top: 16px; } .run-info { @@ -74,4 +74,4 @@ i.expander { textarea.run-command { width: 100%; font-family: Menlo, Consolas, monospace; -} \ No newline at end of file +} diff --git a/mlflow/server/js/src/components/RunView.js b/mlflow/server/js/src/components/RunView.js index 75794260e9230..0be953a1eeed0 100644 --- a/mlflow/server/js/src/components/RunView.js +++ b/mlflow/server/js/src/components/RunView.js @@ -8,8 +8,8 @@ import { Link } from 'react-router-dom'; import ArtifactPage from './ArtifactPage'; import { getLatestMetrics } from '../reducers/MetricReducer'; import { Experiment } from '../sdk/MlflowMessages'; -import Routes from '../Routes'; import Utils from '../utils/Utils'; +import BreadcrumbTitle from "./BreadcrumbTitle"; const PARAMATERS_KEY = 'parameters'; const METRICS_KEY = 'metrics'; @@ -85,7 +85,6 @@ class RunView extends Component { const { run, experiment, params, tags, latestMetrics, getMetricPagePath } = this.props; const startTime = run.getStartTime() ? Utils.formatTimestamp(run.getStartTime()) : '(unknown)'; const duration = run.getStartTime() && run.getEndTime() ? run.getEndTime() - run.getStartTime() : null; - const experimentId = experiment.getExperimentId(); const tableStyles = { table: { width: 'auto', @@ -115,17 +114,11 @@ class RunView extends Component { return (
-

Run {run.getRunUuid()}

+
- Experiment Name: - - {experiment.getName()} - -
-
- Start Time: + Date: {startTime}
@@ -161,7 +154,7 @@ class RunView extends Component { {runCommand ?

Run Command

- +