From 53099fe3a00430912349446b5c4771cfce679d7f Mon Sep 17 00:00:00 2001 From: Hamid Zare Date: Tue, 28 Jul 2020 12:07:37 -0700 Subject: [PATCH 1/6] sep: refactor basics --- .../CreateExperimentModal.module.scss} | 0 .../src/components/CreateExperimentModal.tsx | 84 +++++++++++++++++++ webui/react/src/pages/ExperimentDetails.tsx | 73 ++++------------ 3 files changed, 100 insertions(+), 57 deletions(-) rename webui/react/src/{pages/ExperimentDetails.module.scss => components/CreateExperimentModal.module.scss} (100%) create mode 100644 webui/react/src/components/CreateExperimentModal.tsx diff --git a/webui/react/src/pages/ExperimentDetails.module.scss b/webui/react/src/components/CreateExperimentModal.module.scss similarity index 100% rename from webui/react/src/pages/ExperimentDetails.module.scss rename to webui/react/src/components/CreateExperimentModal.module.scss diff --git a/webui/react/src/components/CreateExperimentModal.tsx b/webui/react/src/components/CreateExperimentModal.tsx new file mode 100644 index 00000000000..f1d6fd38bf7 --- /dev/null +++ b/webui/react/src/components/CreateExperimentModal.tsx @@ -0,0 +1,84 @@ +import { Alert, Modal } from 'antd'; +import yaml from 'js-yaml'; +import React, { useCallback, useState } from 'react'; +import MonacoEditor from 'react-monaco-editor'; + +import { routeAll } from 'routes'; +import { forkExperiment } from 'services/api'; + +import css from './CreateExperimentModal.module.scss'; + +interface Props { + visible: boolean; + setVisible: (arg0: boolean) => void; // or on finish + title: string; + configValue: string; + setConfigValue: (arg0: string) => void; + parentId: number; +} + +const CreateExperimentModal: React.FC = ( + { visible, configValue, setConfigValue, parentId, setVisible }: Props, +) => { + const [ configError, setConfigError ] = useState(); + + const editorOnChange = useCallback((newValue) => { + setConfigValue(newValue); + setConfigError(undefined); + }, [ setConfigError, setConfigValue ]); + + const monacoOpts = { + minimap: { enabled: false }, + selectOnLineNumbers: true, + }; + + const handleOk = async (): Promise => { + try { + // Validate the yaml syntax by attempting to load it. + yaml.safeLoad(configValue); + const configId = await forkExperiment({ experimentConfig: configValue, parentId }); + setVisible(false); + routeAll(`/det/experiments/${configId}`); + } catch (e) { + let errorMessage = 'Failed to config using the provided config.'; + if (e.name === 'YAMLException') { + errorMessage = e.message; + } else if (e.response?.data?.message) { + errorMessage = e.response.data.message; + } + setConfigError(errorMessage); + } + }; + + const handleCancel = (): void => { + setVisible(false); + }; + return + + {configError && + + } + ; + +}; +export default CreateExperimentModal; diff --git a/webui/react/src/pages/ExperimentDetails.tsx b/webui/react/src/pages/ExperimentDetails.tsx index 994349e9140..e67114635b4 100644 --- a/webui/react/src/pages/ExperimentDetails.tsx +++ b/webui/react/src/pages/ExperimentDetails.tsx @@ -1,11 +1,11 @@ -import { Alert, Breadcrumb, Button, Modal, Space, Table, Tooltip } from 'antd'; +import { Breadcrumb, Button, Space, Table, Tooltip } from 'antd'; import { ColumnType } from 'antd/lib/table'; import yaml from 'js-yaml'; import React, { useCallback, useEffect, useState } from 'react'; -import MonacoEditor from 'react-monaco-editor'; import { useParams } from 'react-router'; import CheckpointModal from 'components/CheckpointModal'; +import CreateExperimentModal from 'components/CreateExperimentModal'; import ExperimentActions from 'components/ExperimentActions'; import ExperimentChart from 'components/ExperimentChart'; import ExperimentInfoBox from 'components/ExperimentInfoBox'; @@ -20,8 +20,7 @@ import { durationRenderer, relativeTimeRenderer, stateRenderer } from 'component import handleError, { ErrorType } from 'ErrorHandler'; import usePolling from 'hooks/usePolling'; import useRestApi from 'hooks/useRestApi'; -import { routeAll } from 'routes'; -import { forkExperiment, getExperimentDetails, isNotFound } from 'services/api'; +import { getExperimentDetails, isNotFound } from 'services/api'; import { ExperimentDetailsParams } from 'services/types'; import { CheckpointDetail, ExperimentDetails, TrialItem } from 'types'; import { clone } from 'utils/data'; @@ -29,8 +28,6 @@ import { alphanumericSorter, numericSorter, runStateSorter, stringTimeSorter } f import { humanReadableFloat } from 'utils/string'; import { getDuration } from 'utils/time'; -import css from './ExperimentDetails.module.scss'; - interface Params { experimentId: string; } @@ -40,9 +37,6 @@ const ExperimentDetailsComp: React.FC = () => { const id = parseInt(experimentId); const [ activeCheckpoint, setActiveCheckpoint ] = useState(); const [ showCheckpoint, setShowCheckpoint ] = useState(false); - const [ forkValue, setForkValue ] = useState('Loading'); - const [ forkModalState, setForkModalState ] = useState({ visible: false }); - const [ forkError, setForkError ] = useState(); const [ experimentResponse, triggerExperimentRequest ] = useRestApi(getExperimentDetails, { id }); const experiment = experimentResponse.data; @@ -54,6 +48,8 @@ const ExperimentDetailsComp: React.FC = () => { }, [ id, triggerExperimentRequest ]); usePolling(pollExperimentDetails); + const [ forkValue, setForkValue ] = useState('Loading'); + const [ forkModalState, setForkModalState ] = useState({ config: '', visible: false }); useEffect(() => { if (experiment && experiment.config) { @@ -77,9 +73,8 @@ const ExperimentDetailsComp: React.FC = () => { setForkModalState(state => ({ ...state, visible: true })); }, [ setForkModalState ]); - const editorOnChange = useCallback((newValue) => { - setForkValue(newValue); - setForkError(undefined); + const setVisible = useCallback((isVisible: boolean) => { + setForkModalState(state => ({ ...state, visible: isVisible })); }, []); const handleTableRow = useCallback((record: TrialItem) => ({ @@ -103,28 +98,6 @@ const ExperimentDetailsComp: React.FC = () => { return ; } - const handleOk = async (): Promise => { - try { - // Validate the yaml syntax by attempting to load it. - yaml.safeLoad(forkValue); - const forkId = await forkExperiment({ experimentConfig: forkValue, parentId: id }); - setForkModalState(state => ({ ...state, visible: false })); - routeAll(`/det/experiments/${forkId}`); - } catch (e) { - let errorMessage = 'Failed to fork using the provided config.'; - if (e.name === 'YAMLException') { - errorMessage = e.message; - } else if (e.response?.data?.message) { - errorMessage = e.response.data.message; - } - setForkError(errorMessage); - } - }; - - const handleCancel = (): void => { - setForkModalState(state => ({ ...state, visible: false })); - }; - const handleCheckpointShow = (event: React.MouseEvent, checkpoint: CheckpointDetail) => { event.stopPropagation(); setActiveCheckpoint(checkpoint); @@ -227,30 +200,8 @@ const ExperimentDetailsComp: React.FC = () => { experiment={experiment} onClick={{ Fork: showForkModal }} onSettled={pollExperimentDetails} /> -
+
- - - {forkError && } - { show={showCheckpoint} title={`Best Checkpoint for Trial ${activeCheckpoint.trialId}`} onHide={handleCheckpointDismiss} />} + ); }; From 1d6ff323b6d22cf7e34504f5fb0dce35c86c57c9 Mon Sep 17 00:00:00 2001 From: Hamid Zare Date: Tue, 28 Jul 2020 12:29:18 -0700 Subject: [PATCH 2/6] refactor: use a state object --- .../src/components/CreateExperimentModal.tsx | 33 ++++++++++--------- webui/react/src/pages/ExperimentDetails.tsx | 17 +++------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/webui/react/src/components/CreateExperimentModal.tsx b/webui/react/src/components/CreateExperimentModal.tsx index f1d6fd38bf7..c62fa8dd72b 100644 --- a/webui/react/src/components/CreateExperimentModal.tsx +++ b/webui/react/src/components/CreateExperimentModal.tsx @@ -1,6 +1,6 @@ import { Alert, Modal } from 'antd'; import yaml from 'js-yaml'; -import React, { useCallback, useState } from 'react'; +import React, { SetStateAction, useCallback, useState } from 'react'; import MonacoEditor from 'react-monaco-editor'; import { routeAll } from 'routes'; @@ -8,24 +8,27 @@ import { forkExperiment } from 'services/api'; import css from './CreateExperimentModal.module.scss'; -interface Props { +interface RWState { visible: boolean; - setVisible: (arg0: boolean) => void; // or on finish + config: string; +} + +interface Props { title: string; - configValue: string; - setConfigValue: (arg0: string) => void; parentId: number; + state: RWState; + setState: (arg0: SetStateAction) => void; } const CreateExperimentModal: React.FC = ( - { visible, configValue, setConfigValue, parentId, setVisible }: Props, + { state, setState, parentId }: Props, ) => { const [ configError, setConfigError ] = useState(); - const editorOnChange = useCallback((newValue) => { - setConfigValue(newValue); + const editorOnChange = useCallback((newValue: string) => { + setState((existingState: RWState) => ({ ...existingState, config: newValue })); setConfigError(undefined); - }, [ setConfigError, setConfigValue ]); + }, [ setState, setConfigError ]); const monacoOpts = { minimap: { enabled: false }, @@ -35,9 +38,9 @@ const CreateExperimentModal: React.FC = ( const handleOk = async (): Promise => { try { // Validate the yaml syntax by attempting to load it. - yaml.safeLoad(configValue); - const configId = await forkExperiment({ experimentConfig: configValue, parentId }); - setVisible(false); + yaml.safeLoad(state.config); + const configId = await forkExperiment({ experimentConfig: state.config, parentId }); + setState(existingState => ({ ...existingState, visible: false })); routeAll(`/det/experiments/${configId}`); } catch (e) { let errorMessage = 'Failed to config using the provided config.'; @@ -51,7 +54,7 @@ const CreateExperimentModal: React.FC = ( }; const handleCancel = (): void => { - setVisible(false); + setState(existingState => ({ ...existingState, visible: false })); }; return = ( minWidth: '60rem', }} title={`Config Experiment ${parentId}`} - visible={visible} + visible={state.visible} onCancel={handleCancel} onOk={handleOk} > @@ -72,7 +75,7 @@ const CreateExperimentModal: React.FC = ( language="yaml" options={monacoOpts} theme="vs-light" - value={configValue} + value={state.config} onChange={editorOnChange} /> {configError && diff --git a/webui/react/src/pages/ExperimentDetails.tsx b/webui/react/src/pages/ExperimentDetails.tsx index e67114635b4..ef04b2b1b56 100644 --- a/webui/react/src/pages/ExperimentDetails.tsx +++ b/webui/react/src/pages/ExperimentDetails.tsx @@ -48,8 +48,7 @@ const ExperimentDetailsComp: React.FC = () => { }, [ id, triggerExperimentRequest ]); usePolling(pollExperimentDetails); - const [ forkValue, setForkValue ] = useState('Loading'); - const [ forkModalState, setForkModalState ] = useState({ config: '', visible: false }); + const [ forkModalState, setForkModalState ] = useState({ config: 'Loading', visible: false }); useEffect(() => { if (experiment && experiment.config) { @@ -57,14 +56,14 @@ const ExperimentDetailsComp: React.FC = () => { const prefix = 'Fork of '; const rawConfig = clone(experiment.configRaw); rawConfig.description = prefix + rawConfig.description; - setForkValue(yaml.safeDump(rawConfig)); + setForkModalState(state => ({ ...state, config: yaml.safeDump(rawConfig) })); } catch (e) { handleError({ error: e, message: 'failed to load experiment config', type: ErrorType.ApiBadResponse, }); - setForkValue('failed to load experiment config'); + setForkModalState(state => ({ ...state, config: 'failed to load experiment config' })); } } }, [ experiment ]); @@ -73,10 +72,6 @@ const ExperimentDetailsComp: React.FC = () => { setForkModalState(state => ({ ...state, visible: true })); }, [ setForkModalState ]); - const setVisible = useCallback((isVisible: boolean) => { - setForkModalState(state => ({ ...state, visible: isVisible })); - }, []); - const handleTableRow = useCallback((record: TrialItem) => ({ onClick: makeClickHandler(record.url as string), }), []); @@ -223,12 +218,10 @@ const ExperimentDetailsComp: React.FC = () => { title={`Best Checkpoint for Trial ${activeCheckpoint.trialId}`} onHide={handleCheckpointDismiss} />} ); From 0a9eabf843b49b585e4bff3f29a14ac5fecae417 Mon Sep 17 00:00:00 2001 From: Hamid Zare Date: Tue, 28 Jul 2020 12:31:33 -0700 Subject: [PATCH 3/6] refactor and add trial continue modal --- .../CreateExperimentModal.module.scss | 7 -- .../src/components/CreateExperimentModal.tsx | 35 ++++----- webui/react/src/components/TrialActions.tsx | 18 +++-- webui/react/src/ioTypes.ts | 6 +- .../src/pages/ExperimentDetails.module.scss | 7 ++ webui/react/src/pages/ExperimentDetails.tsx | 6 +- webui/react/src/pages/TrialDetails.tsx | 76 +++++++++++++++++-- webui/react/src/services/decoder.ts | 1 + webui/react/src/types.ts | 15 +++- webui/react/src/utils/types.ts | 16 +++- 10 files changed, 139 insertions(+), 48 deletions(-) create mode 100644 webui/react/src/pages/ExperimentDetails.module.scss diff --git a/webui/react/src/components/CreateExperimentModal.module.scss b/webui/react/src/components/CreateExperimentModal.module.scss index bb59a289ca3..01f11fbc7ee 100644 --- a/webui/react/src/components/CreateExperimentModal.module.scss +++ b/webui/react/src/components/CreateExperimentModal.module.scss @@ -1,10 +1,3 @@ -.topRow { - display: flex; - - section { - flex-grow: 1; - } -} .error { margin: var(--theme-sizes-layout-medium) var(--theme-sizes-layout-large); } diff --git a/webui/react/src/components/CreateExperimentModal.tsx b/webui/react/src/components/CreateExperimentModal.tsx index c62fa8dd72b..d74d066de39 100644 --- a/webui/react/src/components/CreateExperimentModal.tsx +++ b/webui/react/src/components/CreateExperimentModal.tsx @@ -8,38 +8,35 @@ import { forkExperiment } from 'services/api'; import css from './CreateExperimentModal.module.scss'; -interface RWState { +export interface CreateExpModalState { visible: boolean; config: string; } interface Props { title: string; + okText: string; parentId: number; - state: RWState; - setState: (arg0: SetStateAction) => void; + visible: boolean; + config: string; + setState: (arg0: SetStateAction) => void; } const CreateExperimentModal: React.FC = ( - { state, setState, parentId }: Props, + { visible, config, setState, parentId, ...props }: Props, ) => { const [ configError, setConfigError ] = useState(); const editorOnChange = useCallback((newValue: string) => { - setState((existingState: RWState) => ({ ...existingState, config: newValue })); + setState((existingState: CreateExpModalState) => ({ ...existingState, config: newValue })); setConfigError(undefined); }, [ setState, setConfigError ]); - const monacoOpts = { - minimap: { enabled: false }, - selectOnLineNumbers: true, - }; - const handleOk = async (): Promise => { try { // Validate the yaml syntax by attempting to load it. - yaml.safeLoad(state.config); - const configId = await forkExperiment({ experimentConfig: state.config, parentId }); + yaml.safeLoad(config); + const configId = await forkExperiment({ experimentConfig: config, parentId }); setState(existingState => ({ ...existingState, visible: false })); routeAll(`/det/experiments/${configId}`); } catch (e) { @@ -61,21 +58,25 @@ const CreateExperimentModal: React.FC = ( padding: 0, }} className={css.configModal} - okText="Fork" + okText={props.okText} style={{ minWidth: '60rem', }} - title={`Config Experiment ${parentId}`} - visible={state.visible} + title={props.title} + visible={visible} onCancel={handleCancel} onOk={handleOk} > {configError && diff --git a/webui/react/src/components/TrialActions.tsx b/webui/react/src/components/TrialActions.tsx index b9790684b46..6b4b957d851 100644 --- a/webui/react/src/components/TrialActions.tsx +++ b/webui/react/src/components/TrialActions.tsx @@ -10,20 +10,21 @@ import { terminalRunStates } from 'utils/types'; import css from './TrialActions.module.scss'; -interface Props { - trial: TrialDetails; - onSettled: () => void; // A callback to trigger after an action is done. -} - -enum Action { +export enum Action { Continue = 'Continue', Logs = 'Logs', Tensorboard = 'Tensorboard', } +interface Props { + trial: TrialDetails; + onSettled: () => void; // A callback to trigger after an action is done. + onClick: (action: Action) => (() => void); +} + type ButtonLoadingStates = Record; -const TrialActions: React.FC = ({ trial, onSettled: updateFn }: Props) => { +const TrialActions: React.FC = ({ trial, onClick, onSettled: updateFn }: Props) => { const [ buttonStates, setButtonStates ] = useState({ Continue: false, @@ -48,7 +49,8 @@ const TrialActions: React.FC = ({ trial, onSettled: updateFn }: Props) => }; const actionButtons: ConditionalButton[] = [ - { button: }, + { button: }, { button: }, diff --git a/webui/react/src/ioTypes.ts b/webui/react/src/ioTypes.ts index dada9842b59..2f559762056 100644 --- a/webui/react/src/ioTypes.ts +++ b/webui/react/src/ioTypes.ts @@ -164,12 +164,10 @@ export const ioStep = io.type({ export const ioTrialDetails = io.type({ end_time: io.union([ io.string, io.null ]), experiment_id: io.number, - + hparams: io.record(io.string, io.any), id: io.number, - seed: io.number, start_time: io.string, - state: runStatesIoType, steps: io.array(ioStep), warm_start_checkpoint_id: io.union([ io.number, io.null ]), @@ -187,7 +185,7 @@ export const ioTrial = io.type({ best_validation_metric: io.union([ io.number, io.null ]), end_time: io.union([ io.string, io.null ]), experiment_id: io.number, - hparams: io.any, + hparams: io.record(io.string, io.any), id: io.number, latest_validation_metrics: io.union([ ioLatestValidatonMetrics, io.null ]), num_batches: io.union([ io.number, io.null ]), diff --git a/webui/react/src/pages/ExperimentDetails.module.scss b/webui/react/src/pages/ExperimentDetails.module.scss new file mode 100644 index 00000000000..3b592ef2c33 --- /dev/null +++ b/webui/react/src/pages/ExperimentDetails.module.scss @@ -0,0 +1,7 @@ +.topRow { + display: flex; + + section { + flex-grow: 1; + } +} diff --git a/webui/react/src/pages/ExperimentDetails.tsx b/webui/react/src/pages/ExperimentDetails.tsx index ef04b2b1b56..58c51fc2eb8 100644 --- a/webui/react/src/pages/ExperimentDetails.tsx +++ b/webui/react/src/pages/ExperimentDetails.tsx @@ -218,10 +218,12 @@ const ExperimentDetailsComp: React.FC = () => { title={`Best Checkpoint for Trial ${activeCheckpoint.trialId}`} onHide={handleCheckpointDismiss} />} ); diff --git a/webui/react/src/pages/TrialDetails.tsx b/webui/react/src/pages/TrialDetails.tsx index 5ad6715a968..c2590236506 100644 --- a/webui/react/src/pages/TrialDetails.tsx +++ b/webui/react/src/pages/TrialDetails.tsx @@ -1,19 +1,24 @@ import { Breadcrumb, Space } from 'antd'; -import React, { useCallback } from 'react'; +import yaml from 'js-yaml'; +import React, { useCallback, useEffect, useState } from 'react'; import { useParams } from 'react-router'; +import CreateExperimentModal, { CreateExpModalState } from 'components/CreateExperimentModal'; import Icon from 'components/Icon'; import Link from 'components/Link'; import Message from 'components/Message'; import Page from 'components/Page'; import Section from 'components/Section'; import Spinner from 'components/Spinner'; -import TrialActions from 'components/TrialActions'; +import TrialActions, { Action as TrialAction } from 'components/TrialActions'; +import handleError, { ErrorType } from 'ErrorHandler'; import usePolling from 'hooks/usePolling'; import useRestApi from 'hooks/useRestApi'; -import { getTrialDetails, isNotFound } from 'services/api'; +import { getExperimentDetails, getTrialDetails, isNotFound } from 'services/api'; import { TrialDetailsParams } from 'services/types'; import { TrialDetails } from 'types'; +import { clone } from 'utils/data'; +import { trialHParamsToExperimentHParams } from 'utils/types'; interface Params { trialId: string; @@ -24,11 +29,62 @@ const TrialDetailsComp: React.FC = () => { const trialId = parseInt(trialIdParam); const [ trial, triggerTrialRequest ] = useRestApi(getTrialDetails, { id: trialId }); + const [ ContModalState, setContModalState ] = useState( + { config: 'Loading', visible: false }, + ); + const pollTrialDetails = useCallback( () => triggerTrialRequest({ id: trialId }), [ triggerTrialRequest, trialId ], ); usePolling(pollTrialDetails); + const experimentId = trial.data?.experimentId; + const hparams = trial.data?.hparams; + + useEffect(() => { + if (!experimentId || !hparams) return; + getExperimentDetails({ id: experimentId }) + .then(experiment => { + + try { + const rawConfig = clone(experiment.configRaw); + const newDescription = `Continuation of trial ${trialId}, experiment` + + ` ${experiment.id} (${rawConfig.description})`; + const newSearcher = { + max_steps: 100, // TODO add form + metric: rawConfig.searcher.metric, + name: rawConfig.searcher.name, + smaller_is_better: rawConfig.searcher.smaller_is_better, + source_trial_id: trialId, + }; + const newHyperparameters = trialHParamsToExperimentHParams(hparams); + + setContModalState(state => ({ ...state, + config: yaml.safeDump({ + ...rawConfig, + description: newDescription, + hyperparameters: newHyperparameters, + searcher: newSearcher, + }) })); + } catch (e) { + setContModalState(state => ({ ...state, config: 'failed to load experiment config' })); + handleError({ + error: e, + message: 'failed to load experiment config', + type: ErrorType.ApiBadResponse, + }); + } + }); + + } , [ experimentId, trialId, hparams ]); + + const handleActionClick = useCallback((action: TrialAction) => (): void => { + switch (action) { + case TrialAction.Continue: + setContModalState(state => ({ ...state, visible: true })); + break; + } + }, [ ]); if (isNaN(trialId)) { return ( @@ -48,7 +104,7 @@ const TrialDetailsComp: React.FC = () => { ); } - if (!trial.data) { + if (!trial.data || !experimentId) { return ; } @@ -65,10 +121,20 @@ const TrialDetailsComp: React.FC = () => { {trialId} - +
+ ); }; diff --git a/webui/react/src/services/decoder.ts b/webui/react/src/services/decoder.ts index 30ced1be583..1db30c88752 100644 --- a/webui/react/src/services/decoder.ts +++ b/webui/react/src/services/decoder.ts @@ -217,6 +217,7 @@ export const jsonToTrialDetails = (data: unknown): TrialDetails => { return { endTime: io.end_time || undefined, experimentId: io.experiment_id, + hparams: io.hparams, id: io.id, seed: io.seed, startTime: io.start_time, diff --git a/webui/react/src/types.ts b/webui/react/src/types.ts index 153937e8c93..33a80ca105b 100644 --- a/webui/react/src/types.ts +++ b/webui/react/src/types.ts @@ -170,6 +170,13 @@ interface DataLayer { type: string; } +interface ExperimentHyperParam { + type: string; + val: unknown; +} + +export type ExperimentHyperParams = Record; + export interface ExperimentConfig { checkpointPolicy: string; checkpointStorage?: CheckpointStorage; @@ -258,13 +265,15 @@ export interface Step { state: RunState; } +// FIXME this vs TrialItem export interface TrialDetails { - id: number; - state: RunState; - experimentId: number; endTime?: string; + experimentId: number; + hparams: Record; + id: number; seed: number; startTime: string; + state: RunState; steps: Step[]; warmStartCheckpointId?: number; } diff --git a/webui/react/src/utils/types.ts b/webui/react/src/utils/types.ts index f0421d53f53..40a119d387d 100644 --- a/webui/react/src/utils/types.ts +++ b/webui/react/src/utils/types.ts @@ -1,6 +1,6 @@ import { - AnyTask, Command, CommandState, CommandType, Experiment, ExperimentItem, - RecentCommandTask, RecentExperimentTask, RecentTask, RunState, + AnyTask, Command, CommandState, CommandType, Experiment, ExperimentHyperParams, + ExperimentItem, RecentCommandTask, RecentExperimentTask, RecentTask, RunState, } from 'types'; /* Conversions to Tasks */ @@ -157,3 +157,15 @@ export const oneOfProperties = (obj: any, props: string[]): T => { } throw new Error('no matching property'); }; + +export const trialHParamsToExperimentHParams = (hParams: Record) +: ExperimentHyperParams => { + const experimentHParams: ExperimentHyperParams = {}; + Object.entries(hParams).forEach(([ param, value ]) => { + experimentHParams[param] = { + type: 'const', + val: value, + }; + }); + return experimentHParams; +}; From e8b5a425b1c6b3b970a25def48db84d7f9a3bd92 Mon Sep 17 00:00:00 2001 From: Hamid Zare Date: Mon, 3 Aug 2020 10:36:57 -0700 Subject: [PATCH 4/6] clean up trialX types --- webui/react/src/types.ts | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/webui/react/src/types.ts b/webui/react/src/types.ts index 33a80ca105b..3791edd7317 100644 --- a/webui/react/src/types.ts +++ b/webui/react/src/types.ts @@ -241,23 +241,6 @@ export interface LatestValidationMetrics { validationMetrics: Record; } -export interface TrialItem { - bestAvailableCheckpoint?: Checkpoint; - bestValidationMetric?: number; - endTime?: string; - experimentId: number; - hparams: Record; - id: number; - latestValidationMetrics?: LatestValidationMetrics; - numBatches: number; - numCompletedCheckpoints: number; - numSteps: number; - seed: number; - startTime: string; - state: RunState; - url: string; -} - export interface Step { endTime?: string; id: number; @@ -265,15 +248,25 @@ export interface Step { state: RunState; } -// FIXME this vs TrialItem -export interface TrialDetails { - endTime?: string; +interface TrialBase extends StartEndTimes { experimentId: number; - hparams: Record; id: number; - seed: number; - startTime: string; state: RunState; + seed: number; + hparams: Record; +} + +export interface TrialItem extends TrialBase { + bestAvailableCheckpoint?: Checkpoint; + bestValidationMetric?: number; + latestValidationMetrics?: LatestValidationMetrics; + numBatches: number; + numCompletedCheckpoints: number; + numSteps: number; + url: string; +} + +export interface TrialDetails extends TrialBase { steps: Step[]; warmStartCheckpointId?: number; } From c659dd9dc3ee417f215a019e65091d5a54acddf9 Mon Sep 17 00:00:00 2001 From: Hamid Zare Date: Tue, 4 Aug 2020 13:44:14 -0700 Subject: [PATCH 5/6] refactor: break setState to separate handlers to be more consistent with common patterns? --- .../src/components/CreateExperimentModal.tsx | 20 +++++------- webui/react/src/pages/ExperimentDetails.tsx | 18 ++++++----- webui/react/src/pages/TrialDetails.tsx | 31 +++++++++---------- 3 files changed, 33 insertions(+), 36 deletions(-) diff --git a/webui/react/src/components/CreateExperimentModal.tsx b/webui/react/src/components/CreateExperimentModal.tsx index d74d066de39..e93fe2b825c 100644 --- a/webui/react/src/components/CreateExperimentModal.tsx +++ b/webui/react/src/components/CreateExperimentModal.tsx @@ -1,6 +1,6 @@ import { Alert, Modal } from 'antd'; import yaml from 'js-yaml'; -import React, { SetStateAction, useCallback, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import MonacoEditor from 'react-monaco-editor'; import { routeAll } from 'routes'; @@ -8,36 +8,32 @@ import { forkExperiment } from 'services/api'; import css from './CreateExperimentModal.module.scss'; -export interface CreateExpModalState { - visible: boolean; - config: string; -} - interface Props { title: string; okText: string; parentId: number; visible: boolean; config: string; - setState: (arg0: SetStateAction) => void; + onVisibleChange: (arg0: boolean) => void; + onConfigChange: (arg0: string) => void; } const CreateExperimentModal: React.FC = ( - { visible, config, setState, parentId, ...props }: Props, + { visible, config, onVisibleChange, onConfigChange, parentId, ...props }: Props, ) => { const [ configError, setConfigError ] = useState(); const editorOnChange = useCallback((newValue: string) => { - setState((existingState: CreateExpModalState) => ({ ...existingState, config: newValue })); + onConfigChange(newValue); setConfigError(undefined); - }, [ setState, setConfigError ]); + }, [ onConfigChange, setConfigError ]); const handleOk = async (): Promise => { try { // Validate the yaml syntax by attempting to load it. yaml.safeLoad(config); const configId = await forkExperiment({ experimentConfig: config, parentId }); - setState(existingState => ({ ...existingState, visible: false })); + onVisibleChange(false); routeAll(`/det/experiments/${configId}`); } catch (e) { let errorMessage = 'Failed to config using the provided config.'; @@ -51,7 +47,7 @@ const CreateExperimentModal: React.FC = ( }; const handleCancel = (): void => { - setState(existingState => ({ ...existingState, visible: false })); + onVisibleChange(false); }; return { }, [ id, triggerExperimentRequest ]); usePolling(pollExperimentDetails); - const [ forkModalState, setForkModalState ] = useState({ config: 'Loading', visible: false }); + const [ forkModalVisible, setForkModalVisible ] = useState(false); + const [ forkModalConfig, setForkModalConfig ] = useState('Loading'); useEffect(() => { if (experiment && experiment.config) { @@ -56,21 +57,21 @@ const ExperimentDetailsComp: React.FC = () => { const prefix = 'Fork of '; const rawConfig = clone(experiment.configRaw); rawConfig.description = prefix + rawConfig.description; - setForkModalState(state => ({ ...state, config: yaml.safeDump(rawConfig) })); + setForkModalConfig(yaml.safeDump(rawConfig)); } catch (e) { handleError({ error: e, message: 'failed to load experiment config', type: ErrorType.ApiBadResponse, }); - setForkModalState(state => ({ ...state, config: 'failed to load experiment config' })); + setForkModalConfig('failed to load experiment config'); } } }, [ experiment ]); const showForkModal = useCallback((): void => { - setForkModalState(state => ({ ...state, visible: true })); - }, [ setForkModalState ]); + setForkModalVisible(true); + }, [ setForkModalVisible ]); const handleTableRow = useCallback((record: TrialItem) => ({ onClick: makeClickHandler(record.url as string), @@ -218,12 +219,13 @@ const ExperimentDetailsComp: React.FC = () => { title={`Best Checkpoint for Trial ${activeCheckpoint.trialId}`} onHide={handleCheckpointDismiss} />} ); diff --git a/webui/react/src/pages/TrialDetails.tsx b/webui/react/src/pages/TrialDetails.tsx index c2590236506..69f129fc38e 100644 --- a/webui/react/src/pages/TrialDetails.tsx +++ b/webui/react/src/pages/TrialDetails.tsx @@ -3,7 +3,7 @@ import yaml from 'js-yaml'; import React, { useCallback, useEffect, useState } from 'react'; import { useParams } from 'react-router'; -import CreateExperimentModal, { CreateExpModalState } from 'components/CreateExperimentModal'; +import CreateExperimentModal from 'components/CreateExperimentModal'; import Icon from 'components/Icon'; import Link from 'components/Link'; import Message from 'components/Message'; @@ -29,9 +29,8 @@ const TrialDetailsComp: React.FC = () => { const trialId = parseInt(trialIdParam); const [ trial, triggerTrialRequest ] = useRestApi(getTrialDetails, { id: trialId }); - const [ ContModalState, setContModalState ] = useState( - { config: 'Loading', visible: false }, - ); + const [ contModalVisible, setContModalVisible ] = useState(false); + const [ contModalConfig, setContModalConfig ] = useState('Loading'); const pollTrialDetails = useCallback( () => triggerTrialRequest({ id: trialId }), @@ -59,15 +58,14 @@ const TrialDetailsComp: React.FC = () => { }; const newHyperparameters = trialHParamsToExperimentHParams(hparams); - setContModalState(state => ({ ...state, - config: yaml.safeDump({ - ...rawConfig, - description: newDescription, - hyperparameters: newHyperparameters, - searcher: newSearcher, - }) })); + setContModalConfig(yaml.safeDump({ + ...rawConfig, + description: newDescription, + hyperparameters: newHyperparameters, + searcher: newSearcher, + })); } catch (e) { - setContModalState(state => ({ ...state, config: 'failed to load experiment config' })); + setContModalConfig('failed to load experiment config'); handleError({ error: e, message: 'failed to load experiment config', @@ -81,7 +79,7 @@ const TrialDetailsComp: React.FC = () => { const handleActionClick = useCallback((action: TrialAction) => (): void => { switch (action) { case TrialAction.Continue: - setContModalState(state => ({ ...state, visible: true })); + setContModalVisible(true); break; } }, [ ]); @@ -128,12 +126,13 @@ const TrialDetailsComp: React.FC = () => {
); From e3476cc55a7be705d8d5cddd5dbadfc66127de63 Mon Sep 17 00:00:00 2001 From: Hamid Zare Date: Wed, 5 Aug 2020 12:07:35 -0700 Subject: [PATCH 6/6] rename type definition args --- webui/react/src/components/CreateExperimentModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webui/react/src/components/CreateExperimentModal.tsx b/webui/react/src/components/CreateExperimentModal.tsx index e93fe2b825c..3cd7efc42eb 100644 --- a/webui/react/src/components/CreateExperimentModal.tsx +++ b/webui/react/src/components/CreateExperimentModal.tsx @@ -14,8 +14,8 @@ interface Props { parentId: number; visible: boolean; config: string; - onVisibleChange: (arg0: boolean) => void; - onConfigChange: (arg0: string) => void; + onVisibleChange: (visible: boolean) => void; + onConfigChange: (config: string) => void; } const CreateExperimentModal: React.FC = (