diff options
| author | Georgios Andreadis <g.andreadis@student.tudelft.nl> | 2017-09-14 14:07:21 +0200 |
|---|---|---|
| committer | Georgios Andreadis <g.andreadis@student.tudelft.nl> | 2017-09-23 10:06:02 +0200 |
| commit | f604406453f95c82c3e5e4294a51245661868bbe (patch) | |
| tree | 6282cc3eb3164ddd94052175f872c8fc2ee2f623 | |
| parent | 7151ae60cf587a502a7e09d19ebd0fd33e761bf2 (diff) | |
First attempt at experiment list UI
| -rw-r--r-- | src/actions/experiments.js | 24 | ||||
| -rw-r--r-- | src/actions/modals/experiments.js | 14 | ||||
| -rw-r--r-- | src/actions/modals/simulations.js | 2 | ||||
| -rw-r--r-- | src/api/routes/jobs.js | 9 | ||||
| -rw-r--r-- | src/api/routes/tasks.js | 5 | ||||
| -rw-r--r-- | src/components/experiments/ExperimentListComponent.js | 19 | ||||
| -rw-r--r-- | src/components/experiments/ExperimentRowComponent.js | 17 | ||||
| -rw-r--r-- | src/components/modals/custom-components/NewExperimentModalComponent.js | 87 | ||||
| -rw-r--r-- | src/containers/experiments/ExperimentListContainer.js | 27 | ||||
| -rw-r--r-- | src/containers/experiments/ExperimentRowContainer.js | 19 | ||||
| -rw-r--r-- | src/containers/modals/NewExperimentModal.js | 36 | ||||
| -rw-r--r-- | src/pages/Experiments.js | 9 | ||||
| -rw-r--r-- | src/reducers/modals.js | 2 | ||||
| -rw-r--r-- | src/sagas/experiments.js | 73 | ||||
| -rw-r--r-- | src/sagas/index.js | 9 | ||||
| -rw-r--r-- | src/sagas/objects.js | 16 | ||||
| -rw-r--r-- | src/shapes/index.js | 36 |
17 files changed, 392 insertions, 12 deletions
diff --git a/src/actions/experiments.js b/src/actions/experiments.js new file mode 100644 index 00000000..b067f5d9 --- /dev/null +++ b/src/actions/experiments.js @@ -0,0 +1,24 @@ +export const FETCH_EXPERIMENTS_OF_SIMULATION = "FETCH_EXPERIMENTS_OF_SIMULATION"; +export const ADD_EXPERIMENT = "ADD_EXPERIMENT"; +export const DELETE_EXPERIMENT = "DELETE_EXPERIMENT"; + +export function fetchExperimentsOfSimulation(simulationId) { + return { + type: FETCH_EXPERIMENTS_OF_SIMULATION, + simulationId + }; +} + +export function addExperiment(experiment) { + return { + type: ADD_EXPERIMENT, + experiment + }; +} + +export function deleteExperiment(id) { + return { + type: DELETE_EXPERIMENT, + id + }; +} diff --git a/src/actions/modals/experiments.js b/src/actions/modals/experiments.js new file mode 100644 index 00000000..43a53a3a --- /dev/null +++ b/src/actions/modals/experiments.js @@ -0,0 +1,14 @@ +export const OPEN_NEW_EXPERIMENT_MODAL = "OPEN_NEW_EXPERIMENT_MODAL"; +export const CLOSE_NEW_EXPERIMENT_MODAL = "CLOSE_EXPERIMENT_MODAL"; + +export function openNewExperimentModal() { + return { + type: OPEN_NEW_EXPERIMENT_MODAL + }; +} + +export function closeNewExperimentModal() { + return { + type: CLOSE_NEW_EXPERIMENT_MODAL + }; +} diff --git a/src/actions/modals/simulations.js b/src/actions/modals/simulations.js index 53d2c565..8281238d 100644 --- a/src/actions/modals/simulations.js +++ b/src/actions/modals/simulations.js @@ -1,5 +1,5 @@ export const OPEN_NEW_SIMULATION_MODAL = "OPEN_NEW_SIMULATION_MODAL"; -export const CLOSE_NEW_SIMULATION_MODAL = "CLOSE_SIMULATION_POPUP"; +export const CLOSE_NEW_SIMULATION_MODAL = "CLOSE_SIMULATION_MODAL"; export function openNewSimulationModal() { return { diff --git a/src/api/routes/jobs.js b/src/api/routes/jobs.js new file mode 100644 index 00000000..90947a51 --- /dev/null +++ b/src/api/routes/jobs.js @@ -0,0 +1,9 @@ +import {getAll, getById} from "./util"; + +export function getAllJobs() { + return getAll("/jobs"); +} + +export function getTasksOfJob(jobId) { + return getById("/jobs/{jobId}/tasks", {jobId}); +} diff --git a/src/api/routes/tasks.js b/src/api/routes/tasks.js new file mode 100644 index 00000000..69736af8 --- /dev/null +++ b/src/api/routes/tasks.js @@ -0,0 +1,5 @@ +import {getAll} from "./util"; + +export function getAllTasks() { + return getAll("/tasks"); +} diff --git a/src/components/experiments/ExperimentListComponent.js b/src/components/experiments/ExperimentListComponent.js index c3e3db8d..001b0e32 100644 --- a/src/components/experiments/ExperimentListComponent.js +++ b/src/components/experiments/ExperimentListComponent.js @@ -1,6 +1,8 @@ +import PropTypes from "prop-types"; import React from "react"; +import ExperimentRowContainer from "../../containers/experiments/ExperimentRowContainer"; -const ExperimentListContainer = ({experiments}) => ( +const ExperimentListComponent = ({experimentIds}) => ( <table className="table"> <thead> <tr> @@ -11,16 +13,15 @@ const ExperimentListContainer = ({experiments}) => ( </tr> </thead> <tbody> - {experiments.map(experiment => ( - <tr> - <td>{experiment.name}</td> - <td>{experiment.path.name}</td> - <td>{experiment.trace.name}</td> - <td>{experiment.scheduler.name}</td> - </tr> + {experimentIds.map(experimentId => ( + <ExperimentRowContainer experimentId={experimentId}/> ))} </tbody> </table> ); -export default ExperimentListContainer; +ExperimentListComponent.propTypes = { + experimentIds: PropTypes.arrayOf(PropTypes.number).isRequired, +}; + +export default ExperimentListComponent; diff --git a/src/components/experiments/ExperimentRowComponent.js b/src/components/experiments/ExperimentRowComponent.js new file mode 100644 index 00000000..79ce3eea --- /dev/null +++ b/src/components/experiments/ExperimentRowComponent.js @@ -0,0 +1,17 @@ +import React from "react"; +import Shapes from "../../shapes/index"; + +const ExperimentRowComponent = ({experiment}) => ( + <tr> + <td>{experiment.name}</td> + <td>{experiment.path.name}</td> + <td>{experiment.trace.name}</td> + <td>{experiment.scheduler.name}</td> + </tr> +); + +ExperimentRowComponent.propTypes = { + experiment: Shapes.Experiment +}; + +export default ExperimentRowComponent; diff --git a/src/components/modals/custom-components/NewExperimentModalComponent.js b/src/components/modals/custom-components/NewExperimentModalComponent.js new file mode 100644 index 00000000..9efccc8a --- /dev/null +++ b/src/components/modals/custom-components/NewExperimentModalComponent.js @@ -0,0 +1,87 @@ +import PropTypes from "prop-types"; +import React from "react"; +import Shapes from "../../../shapes"; +import Modal from "../Modal"; + +class NewExperimentModalComponent extends React.Component { + static propTypes = { + show: PropTypes.bool.isRequired, + paths: PropTypes.arrayOf(Shapes.Path), + schedulers: PropTypes.arrayOf(Shapes.Scheduler), + traces: PropTypes.arrayOf(Shapes.Trace), + callback: PropTypes.func.isRequired, + }; + + reset() { + this.textInput.value = ""; + this.pathSelect.selectedIndex = 0; + this.traceSelect.selectedIndex = 0; + this.schedulerSelect.selectedIndex = 0; + } + + onSubmit() { + this.props.callback(this.textInput.value, this.pathSelect.value, this.traceSelect.value, + this.schedulerSelect.value); + this.reset(); + } + + onCancel() { + this.props.callback(undefined); + this.reset(); + } + + render() { + return ( + <Modal title="New Experiment" + show={this.props.show} + onSubmit={this.onSubmit.bind(this)} + onCancel={this.onCancel.bind(this)}> + <form onSubmit={e => { + e.preventDefault(); + this.onSubmit(); + }}> + <div className="form-group"> + <label className="form-control-label">Name:</label> + <input type="text" className="form-control" + ref={textInput => this.textInput = textInput}/> + </div> + <div className="form-group"> + <label className="form-control-label">Path:</label> + <select className="form-control" + ref={pathSelect => this.pathSelect = pathSelect}> + {this.props.paths.map(path => ( + <option value={path.id} key={path.id}> + {path.name} + </option> + ))} + </select> + </div> + <div className="form-group"> + <label className="form-control-label">Trace:</label> + <select className="form-control" + ref={traceSelect => this.traceSelect = traceSelect}> + {this.props.traces.map(trace => ( + <option value={trace.id} key={trace.id}> + {trace.name} + </option> + ))} + </select> + </div> + <div className="form-group"> + <label className="form-control-label">Scheduler:</label> + <select className="form-control" + ref={schedulerSelect => this.schedulerSelect = schedulerSelect}> + {this.props.schedulers.map(scheduler => ( + <option value={scheduler.name} key={scheduler.name}> + {scheduler.name} + </option> + ))} + </select> + </div> + </form> + </Modal> + ); + } +} + +export default NewExperimentModalComponent; diff --git a/src/containers/experiments/ExperimentListContainer.js b/src/containers/experiments/ExperimentListContainer.js new file mode 100644 index 00000000..38e5a9f6 --- /dev/null +++ b/src/containers/experiments/ExperimentListContainer.js @@ -0,0 +1,27 @@ +import {connect} from "react-redux"; +import ExperimentListComponent from "../../components/experiments/ExperimentListComponent"; + +const mapStateToProps = state => { + if (!state.currentSimulationId) { + return { + experimentIds: [], + }; + } + + const experimentIds = state.objects.simulation[state.currentSimulationId].experimentIds; + if (experimentIds) { + return { + experimentIds, + }; + } + + return { + experimentIds: [], + }; +}; + +const ExperimentListContainer = connect( + mapStateToProps +)(ExperimentListComponent); + +export default ExperimentListContainer; diff --git a/src/containers/experiments/ExperimentRowContainer.js b/src/containers/experiments/ExperimentRowContainer.js new file mode 100644 index 00000000..99b35779 --- /dev/null +++ b/src/containers/experiments/ExperimentRowContainer.js @@ -0,0 +1,19 @@ +import {connect} from "react-redux"; +import ExperimentRowComponent from "../../components/experiments/ExperimentRowComponent"; + +const mapStateToProps = (state, ownProps) => { + const experiment = Object.assign({}, state.objects.experiment[ownProps.experimentId]); + experiment.trace = state.objects.trace[experiment.traceId]; + experiment.scheduler = state.objects.scheduler[experiment.schedulerName]; + experiment.path = state.objects.path[experiment.pathId]; + + return { + experiment, + }; +}; + +const ExperimentRowContainer = connect( + mapStateToProps +)(ExperimentRowComponent); + +export default ExperimentRowContainer; diff --git a/src/containers/modals/NewExperimentModal.js b/src/containers/modals/NewExperimentModal.js new file mode 100644 index 00000000..13aed74d --- /dev/null +++ b/src/containers/modals/NewExperimentModal.js @@ -0,0 +1,36 @@ +import {connect} from "react-redux"; +import {addExperiment} from "../../actions/experiments"; +import {closeNewExperimentModal} from "../../actions/modals/experiments"; +import NewExperimentModalComponent from "../../components/modals/custom-components/NewExperimentModalComponent"; + +const mapStateToProps = state => { + return { + show: state.modals.newExperimentModalVisible, + paths: Object.values(state.objects.path).filter(path => path.simulationId === state.currentSimulationId), + traces: Object.values(state.objects.trace), + schedulers: Object.values(state.objects.scheduler), + }; +}; + +const mapDispatchToProps = dispatch => { + return { + callback: (name, pathId, traceId, schedulerName) => { + if (name) { + dispatch(addExperiment({ + name, + pathId, + traceId, + schedulerName + })); + } + dispatch(closeNewExperimentModal()); + } + }; +}; + +const NewExperimentModal = connect( + mapStateToProps, + mapDispatchToProps +)(NewExperimentModalComponent); + +export default NewExperimentModal; diff --git a/src/pages/Experiments.js b/src/pages/Experiments.js index 9dbe0dc0..d1d19d9f 100644 --- a/src/pages/Experiments.js +++ b/src/pages/Experiments.js @@ -1,8 +1,11 @@ import PropTypes from "prop-types"; import React from "react"; import {connect} from "react-redux"; +import {fetchExperimentsOfSimulation} from "../actions/experiments"; import {openSimulationSucceeded} from "../actions/simulations"; import AppNavbar from "../components/navigation/AppNavbar"; +import ExperimentListContainer from "../containers/experiments/ExperimentListContainer"; +import NewExperimentModal from "../containers/modals/NewExperimentModal"; class ExperimentsComponent extends React.Component { static propTypes = { @@ -11,7 +14,7 @@ class ExperimentsComponent extends React.Component { componentDidMount() { this.props.storeSimulationId(this.props.simulationId); - // TODO fetch experiments + this.props.fetchExperimentsOfSimulation(this.props.simulationId); } render() { @@ -19,8 +22,9 @@ class ExperimentsComponent extends React.Component { <div className="full-height"> <AppNavbar simulationId={this.props.simulationId} inSimulation={true}/> <div className="container text-page-container full-height"> - Test + <ExperimentListContainer/> </div> + <NewExperimentModal/> </div> ); } @@ -29,6 +33,7 @@ class ExperimentsComponent extends React.Component { const mapDispatchToProps = dispatch => { return { storeSimulationId: id => dispatch(openSimulationSucceeded(id)), + fetchExperimentsOfSimulation: id => dispatch(fetchExperimentsOfSimulation(id)), }; }; diff --git a/src/reducers/modals.js b/src/reducers/modals.js index 0dafcde4..9f73c6ec 100644 --- a/src/reducers/modals.js +++ b/src/reducers/modals.js @@ -1,4 +1,5 @@ import {combineReducers} from "redux"; +import {CLOSE_NEW_EXPERIMENT_MODAL, OPEN_NEW_EXPERIMENT_MODAL} from "../actions/modals/experiments"; import {CLOSE_DELETE_PROFILE_MODAL, OPEN_DELETE_PROFILE_MODAL} from "../actions/modals/profile"; import {CLOSE_NEW_SIMULATION_MODAL, OPEN_NEW_SIMULATION_MODAL} from "../actions/modals/simulations"; import { @@ -35,4 +36,5 @@ export const modals = combineReducers({ editRackNameModalVisible: modal(OPEN_EDIT_RACK_NAME_MODAL, CLOSE_EDIT_RACK_NAME_MODAL), deleteRackModalVisible: modal(OPEN_DELETE_RACK_MODAL, CLOSE_DELETE_RACK_MODAL), deleteMachineModalVisible: modal(OPEN_DELETE_MACHINE_MODAL, CLOSE_DELETE_MACHINE_MODAL), + newExperimentModalVisible: modal(OPEN_NEW_EXPERIMENT_MODAL, CLOSE_NEW_EXPERIMENT_MODAL), }); diff --git a/src/sagas/experiments.js b/src/sagas/experiments.js new file mode 100644 index 00000000..0ac919f4 --- /dev/null +++ b/src/sagas/experiments.js @@ -0,0 +1,73 @@ +import {call, put, select} from "redux-saga/effects"; +import {addPropToStoreObject, addToStore} from "../actions/objects"; +import {deleteExperiment} from "../api/routes/experiments"; +import {addExperiment, getExperimentsOfSimulation} from "../api/routes/simulations"; +import { + fetchAndStoreAllJobs, + fetchAndStoreAllSchedulers, + fetchAndStoreAllTasks, + fetchAndStoreAllTraces, + fetchAndStorePathsOfSimulation +} from "./objects"; + +export function* onFetchExperimentsOfSimulation() { + try { + const currentSimulationId = yield select(state => state.currentSimulationId); + + yield fetchExperimentSpecifications(); + const experiments = yield call(getExperimentsOfSimulation, currentSimulationId); + for (let i in experiments) { + yield put(addToStore("experiment", experiments[i])); + } + yield put(addPropToStoreObject("simulation", currentSimulationId, + {experimentIds: experiments.map(experiment => experiment.id)})); + } catch (error) { + console.log(error); + } +} + +function* fetchExperimentSpecifications() { + try { + const currentSimulationId = yield select(state => state.currentSimulationId); + + yield fetchAndStorePathsOfSimulation(currentSimulationId); + yield fetchAndStoreAllTasks(); + yield fetchAndStoreAllJobs(); + yield fetchAndStoreAllTraces(); + yield fetchAndStoreAllSchedulers(); + } catch (error) { + console.log(error); + } +} + +export function* onAddExperiment(action) { + try { + const currentSimulationId = yield select(state => state.currentSimulationId); + + const experiment = yield call(addExperiment, currentSimulationId, Object.assign({}, action.experiment, { + id: -1, + simulationId: currentSimulationId + })); + yield put(addToStore("experiment", experiment)); + + const experimentIds = yield select(state => state.objects.simulation[currentSimulationId]); + yield put(addPropToStoreObject("simulation", currentSimulationId, + {experimentIds: experimentIds.concat([experiment.id])})); + } catch (error) { + console.log(error); + } +} + +export function* onDeleteExperiment(action) { + try { + yield call(deleteExperiment, action.id); + + const currentSimulationId = yield select(state => state.currentSimulationId); + const experimentIds = yield select(state => state.objects.simulation[currentSimulationId]); + + yield put(addPropToStoreObject("simulation", currentSimulationId, + {experimentIds: experimentIds.filter(id => id !== action.id)})); + } catch (error) { + console.log(error); + } +} diff --git a/src/sagas/index.js b/src/sagas/index.js index 5cb25202..e86ef8a1 100644 --- a/src/sagas/index.js +++ b/src/sagas/index.js @@ -1,5 +1,6 @@ import {takeEvery} from "redux-saga/effects"; import {LOG_IN} from "../actions/auth"; +import {ADD_EXPERIMENT, DELETE_EXPERIMENT, FETCH_EXPERIMENTS_OF_SIMULATION} from "../actions/experiments"; import {ADD_SIMULATION, DELETE_SIMULATION} from "../actions/simulations"; import { ADD_TILE, @@ -12,6 +13,7 @@ import {ADD_UNIT, DELETE_MACHINE, DELETE_UNIT} from "../actions/topology/machine import {ADD_MACHINE, DELETE_RACK, EDIT_RACK_NAME} from "../actions/topology/rack"; import {ADD_RACK_TO_TILE, DELETE_ROOM, EDIT_ROOM_NAME} from "../actions/topology/room"; import {DELETE_CURRENT_USER, FETCH_AUTHORIZATIONS_OF_CURRENT_USER} from "../actions/users"; +import {onAddExperiment, onDeleteExperiment, onFetchExperimentsOfSimulation} from "./experiments"; import {onDeleteCurrentUser} from "./profile"; import {onSimulationAdd, onSimulationDelete} from "./simulations"; import { @@ -34,10 +36,13 @@ import {onFetchAuthorizationsOfCurrentUser, onFetchLoggedInUser} from "./users"; export default function* rootSaga() { yield takeEvery(LOG_IN, onFetchLoggedInUser); + yield takeEvery(FETCH_AUTHORIZATIONS_OF_CURRENT_USER, onFetchAuthorizationsOfCurrentUser); yield takeEvery(ADD_SIMULATION, onSimulationAdd); yield takeEvery(DELETE_SIMULATION, onSimulationDelete); + yield takeEvery(DELETE_CURRENT_USER, onDeleteCurrentUser); + yield takeEvery(FETCH_LATEST_DATACENTER, onFetchLatestDatacenter); yield takeEvery(START_NEW_ROOM_CONSTRUCTION, onStartNewRoomConstruction); yield takeEvery(CANCEL_NEW_ROOM_CONSTRUCTION, onCancelNewRoomConstruction); @@ -52,4 +57,8 @@ export default function* rootSaga() { yield takeEvery(DELETE_MACHINE, onDeleteMachine); yield takeEvery(ADD_UNIT, onAddUnit); yield takeEvery(DELETE_UNIT, onDeleteUnit); + + yield takeEvery(FETCH_EXPERIMENTS_OF_SIMULATION, onFetchExperimentsOfSimulation); + yield takeEvery(ADD_EXPERIMENT, onAddExperiment); + yield takeEvery(DELETE_EXPERIMENT, onDeleteExperiment); } diff --git a/src/sagas/objects.js b/src/sagas/objects.js index 508f1cf5..375781be 100644 --- a/src/sagas/objects.js +++ b/src/sagas/objects.js @@ -1,8 +1,10 @@ import {call, put, select} from "redux-saga/effects"; import {addToStore} from "../actions/objects"; import {getDatacenter, getRoomsOfDatacenter} from "../api/routes/datacenters"; +import {getAllJobs} from "../api/routes/jobs"; import {getPath, getSectionsOfPath} from "../api/routes/paths"; import {getTilesOfRoom} from "../api/routes/rooms"; +import {getAllSchedulers} from "../api/routes/schedulers"; import {getSection} from "../api/routes/sections"; import {getPathsOfSimulation, getSimulation} from "../api/routes/simulations"; import { @@ -18,7 +20,9 @@ import { getPSU, getStorage } from "../api/routes/specifications"; +import {getAllTasks} from "../api/routes/tasks"; import {getMachinesOfRackByTile, getRackByTile} from "../api/routes/tiles"; +import {getAllTraces} from "../api/routes/traces"; import {getUser} from "../api/routes/users"; export const OBJECT_SELECTORS = { @@ -124,3 +128,15 @@ export const fetchAndStorePath = (id) => export const fetchAndStorePathsOfSimulation = (simulationId) => fetchAndStoreObjects("path", call(getPathsOfSimulation, simulationId)); + +export const fetchAndStoreAllTraces = () => + fetchAndStoreObjects("trace", call(getAllTraces)); + +export const fetchAndStoreAllJobs = () => + fetchAndStoreObjects("job", call(getAllJobs)); + +export const fetchAndStoreAllTasks = () => + fetchAndStoreObjects("task", call(getAllTasks)); + +export const fetchAndStoreAllSchedulers = () => + fetchAndStoreObjects("scheduler", call(getAllSchedulers)); diff --git a/src/shapes/index.js b/src/shapes/index.js index 07fa512d..cfe0ff54 100644 --- a/src/shapes/index.js +++ b/src/shapes/index.js @@ -136,6 +136,42 @@ Shapes.Path = PropTypes.shape({ sections: PropTypes.arrayOf(Shapes.Section), }); +Shapes.Scheduler = PropTypes.shape({ + name: PropTypes.string.isRequired, +}); + +Shapes.Task = PropTypes.shape({ + id: PropTypes.number.isRequired, + jobId: PropTypes.number.isRequired, + startTick: PropTypes.number.isRequired, + totalFlopCount: PropTypes.number.isRequired, +}); + +Shapes.Job = PropTypes.shape({ + id: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + traceId: PropTypes.number.isRequired, + taskIds: PropTypes.arrayOf(PropTypes.number), +}); + +Shapes.Trace = PropTypes.shape({ + id: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + jobIds: PropTypes.arrayOf(PropTypes.number), +}); + +Shapes.Experiment = PropTypes.shape({ + id: PropTypes.number.isRequired, + simulationId: PropTypes.number.isRequired, + traceId: PropTypes.number.isRequired, + trace: Shapes.Trace, + pathId: PropTypes.number.isRequired, + path: Shapes.Path, + schedulerName: PropTypes.string.isRequired, + scheduler: Shapes.Scheduler, + name: PropTypes.string.isRequired, +}); + Shapes.WallSegment = PropTypes.shape({ startPosX: PropTypes.number.isRequired, startPosY: PropTypes.number.isRequired, |
