From f8f617c97fcb2df3dbefc9527d974151e367cb60 Mon Sep 17 00:00:00 2001 From: Georgios Andreadis Date: Mon, 18 Sep 2017 16:52:11 +0200 Subject: Implement basic experiment mode with timeline The timeline doesn't trigger anything yet, but the visual element is in place and connected. --- src/actions/experiments.js | 5 +- src/actions/topology/building.js | 17 +--- src/api/routes/experiments.js | 4 + src/components/map/groups/RackGroup.js | 2 +- src/components/map/groups/TileGroup.js | 2 +- src/components/navigation/AppNavbar.js | 8 +- .../sidebars/topology/rack/MachineComponent.js | 2 +- src/components/timeline/PlayButtonComponent.js | 12 +++ src/components/timeline/Timeline.js | 15 +++ src/components/timeline/Timeline.sass | 108 +++++++++++++++++++++ .../timeline/TimelineControlsComponent.js | 33 +++++++ src/components/timeline/TimelineLabelsComponent.js | 11 +++ src/containers/timeline/PlayButtonContainer.js | 23 +++++ .../timeline/TimelineControlsContainer.js | 22 +++++ src/containers/timeline/TimelineLabelsContainer.js | 15 +++ src/pages/App.js | 22 ++++- src/reducers/current-ids.js | 7 +- src/reducers/simulation-mode.js | 2 +- src/routes/index.js | 20 +++- src/sagas/experiments.js | 14 ++- src/sagas/index.js | 18 +++- src/sagas/simulations.js | 3 + src/sagas/topology.js | 25 ++++- src/util/date-time.js | 24 ++++- 24 files changed, 368 insertions(+), 46 deletions(-) create mode 100644 src/components/timeline/PlayButtonComponent.js create mode 100644 src/components/timeline/Timeline.js create mode 100644 src/components/timeline/Timeline.sass create mode 100644 src/components/timeline/TimelineControlsComponent.js create mode 100644 src/components/timeline/TimelineLabelsComponent.js create mode 100644 src/containers/timeline/PlayButtonContainer.js create mode 100644 src/containers/timeline/TimelineControlsContainer.js create mode 100644 src/containers/timeline/TimelineLabelsContainer.js (limited to 'src') diff --git a/src/actions/experiments.js b/src/actions/experiments.js index c7cc7e24..c64df019 100644 --- a/src/actions/experiments.js +++ b/src/actions/experiments.js @@ -24,9 +24,10 @@ export function deleteExperiment(id) { }; } -export function openExperimentSucceeded(id) { +export function openExperimentSucceeded(simulationId, experimentId) { return { type: OPEN_EXPERIMENT_SUCCEEDED, - id + simulationId, + experimentId } } diff --git a/src/actions/topology/building.js b/src/actions/topology/building.js index 4138d2b5..019a473c 100644 --- a/src/actions/topology/building.js +++ b/src/actions/topology/building.js @@ -1,5 +1,4 @@ -export const FETCH_LATEST_DATACENTER = "FETCH_LATEST_DATACENTER"; -export const FETCH_LATEST_DATACENTER_SUCCEEDED = "FETCH_LATEST_DATACENTER_SUCCEEDED"; +export const SET_CURRENT_DATACENTER = "SET_CURRENT_DATACENTER"; export const RESET_CURRENT_DATACENTER = "RESET_CURRENT_DATACENTER"; export const START_NEW_ROOM_CONSTRUCTION = "START_NEW_ROOM_CONSTRUCTION"; export const START_NEW_ROOM_CONSTRUCTION_SUCCEEDED = "START_NEW_ROOM_CONSTRUCTION_SUCCEEDED"; @@ -9,19 +8,9 @@ export const CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED = "CANCEL_NEW_ROOM_CONSTRUCT export const ADD_TILE = "ADD_TILE"; export const DELETE_TILE = "DELETE_TILE"; -export function fetchLatestDatacenter() { - return (dispatch, getState) => { - const {currentSimulationId} = getState(); - dispatch({ - type: FETCH_LATEST_DATACENTER, - currentSimulationId - }); - }; -} - -export function fetchLatestDatacenterSucceeded(datacenterId) { +export function setCurrentDatacenter(datacenterId) { return { - type: FETCH_LATEST_DATACENTER_SUCCEEDED, + type: SET_CURRENT_DATACENTER, datacenterId }; } diff --git a/src/api/routes/experiments.js b/src/api/routes/experiments.js index e2306059..84edae2f 100644 --- a/src/api/routes/experiments.js +++ b/src/api/routes/experiments.js @@ -1,5 +1,9 @@ import {deleteById, getById} from "./util"; +export function getExperiment(experimentId) { + return getById("/experiments/{experimentId}", {experimentId}); +} + export function deleteExperiment(experimentId) { return deleteById("/experiments/{experimentId}", {experimentId}); } diff --git a/src/components/map/groups/RackGroup.js b/src/components/map/groups/RackGroup.js index 648c74d7..a34a5543 100644 --- a/src/components/map/groups/RackGroup.js +++ b/src/components/map/groups/RackGroup.js @@ -9,7 +9,7 @@ import TileObject from "../elements/TileObject"; const RackGroup = ({tile, inSimulation, rackLoad}) => { let color = RACK_BACKGROUND_COLOR; - if (inSimulation) { + if (inSimulation && rackLoad) { color = convertLoadToSimulationColor(rackLoad); } diff --git a/src/components/map/groups/TileGroup.js b/src/components/map/groups/TileGroup.js index 3de712d1..c7991507 100644 --- a/src/components/map/groups/TileGroup.js +++ b/src/components/map/groups/TileGroup.js @@ -20,7 +20,7 @@ const TileGroup = ({tile, newTile, inSimulation, roomLoad, onClick}) => { let color = ROOM_DEFAULT_COLOR; if (newTile) { color = ROOM_IN_CONSTRUCTION_COLOR; - } else if (inSimulation) { + } else if (inSimulation && roomLoad) { color = convertLoadToSimulationColor(roomLoad); } diff --git a/src/components/navigation/AppNavbar.js b/src/components/navigation/AppNavbar.js index 94eaa7fa..4f669723 100644 --- a/src/components/navigation/AppNavbar.js +++ b/src/components/navigation/AppNavbar.js @@ -11,10 +11,10 @@ const AppNavbar = ({simulationId, inSimulation}) => ( - Build + Construction : undefined @@ -23,10 +23,10 @@ const AppNavbar = ({simulationId, inSimulation}) => ( - Simulate + Experiments : undefined diff --git a/src/components/sidebars/topology/rack/MachineComponent.js b/src/components/sidebars/topology/rack/MachineComponent.js index b6e9791d..c9211115 100644 --- a/src/components/sidebars/topology/rack/MachineComponent.js +++ b/src/components/sidebars/topology/rack/MachineComponent.js @@ -15,7 +15,7 @@ const UnitIcon = ({id, type}) => ( const MachineComponent = ({position, machine, inSimulation, machineLoad, onClick}) => { let color = "white"; - if (inSimulation) { + if (inSimulation && machineLoad) { color = convertLoadToSimulationColor(machineLoad); } const hasNoUnits = machine.cpuIds.length + machine.gpuIds.length + machine.memoryIds.length diff --git a/src/components/timeline/PlayButtonComponent.js b/src/components/timeline/PlayButtonComponent.js new file mode 100644 index 00000000..6ec70cc3 --- /dev/null +++ b/src/components/timeline/PlayButtonComponent.js @@ -0,0 +1,12 @@ +import React from "react"; + +const PlayButtonComponent = ({isPlaying, onPlay, onPause}) => ( +
isPlaying ? onPause() : onPlay()}> + {isPlaying ? + : + + } +
+); + +export default PlayButtonComponent; diff --git a/src/components/timeline/Timeline.js b/src/components/timeline/Timeline.js new file mode 100644 index 00000000..a2a858eb --- /dev/null +++ b/src/components/timeline/Timeline.js @@ -0,0 +1,15 @@ +import React from "react"; +import TimelineControlsContainer from "../../containers/timeline/TimelineControlsContainer"; +import TimelineLabelsContainer from "../../containers/timeline/TimelineLabelsContainer"; +import "./Timeline.css"; + +const Timeline = ({currentTick, lastSimulatedTick}) => ( +
+
+ + +
+
+); + +export default Timeline; diff --git a/src/components/timeline/Timeline.sass b/src/components/timeline/Timeline.sass new file mode 100644 index 00000000..eed3174e --- /dev/null +++ b/src/components/timeline/Timeline.sass @@ -0,0 +1,108 @@ +@import ../../style-globals/_variables.sass +@import ../../style-globals/_mixins.sass + +$container-size: 500px +$play-btn-size: 40px +$border-width: 1px +$timeline-border: $border-width solid $gray-semi-dark + +.timeline-bar + display: block + position: absolute + left: 0 + bottom: 20px + width: 100% + text-align: center + z-index: 2000 + +.timeline-container + display: inline-block + margin: 0 auto + text-align: left + + width: $container-size + +.timeline-labels + display: block + height: 25px + line-height: 25px + + div + display: inline-block + + .start-time-label + margin-left: $play-btn-size - $border-width + padding-left: 4px + + .end-time-label + padding-right: 4px + float: right + +.timeline-controls + display: flex + border: $timeline-border + overflow: hidden + + +border-radius($standard-border-radius) + + .play-btn + width: $play-btn-size + height: $play-btn-size + $border-width + line-height: $play-btn-size + $border-width + text-align: center + float: left + margin-top: -$border-width + + font-size: 16pt + background: #333 + color: #eee + + +transition(background, $transition-length) + +user-select + +clickable + + .play-btn:hover + background: #656565 + + .play-btn:active + background: #000 + + .timeline + position: relative + flex: 1 + height: $play-btn-size + line-height: $play-btn-size + float: right + + background: $blue-light + + z-index: 500 + + div + +transition(all, $transition-length) + + .time-marker + position: absolute + top: 0 + left: 0 + + width: 6px + height: 100% + + background: $blue-very-dark + + +border-radius(2px) + + z-index: 503 + + .section-marker + position: absolute + top: 0 + left: 0 + + width: 3px + height: 100% + + background: #222222 + + z-index: 504 diff --git a/src/components/timeline/TimelineControlsComponent.js b/src/components/timeline/TimelineControlsComponent.js new file mode 100644 index 00000000..3f37c3bc --- /dev/null +++ b/src/components/timeline/TimelineControlsComponent.js @@ -0,0 +1,33 @@ +import React from "react"; +import PlayButtonContainer from "../../containers/timeline/PlayButtonContainer"; + +function getXPercentage(tick, maxTick) { + if (maxTick === 0) { + return "0%"; + } else if (tick > maxTick) { + return "100%"; + } + + return (tick / maxTick) + "%"; +} + +const TimelineControlsComponent = ({currentTick, lastSimulatedTick, sectionTicks}) => ( +
+ +
+
+ {sectionTicks.map(sectionTick => ( +
+ ))} +
+
+); + +export default TimelineControlsComponent; diff --git a/src/components/timeline/TimelineLabelsComponent.js b/src/components/timeline/TimelineLabelsComponent.js new file mode 100644 index 00000000..1f6053a1 --- /dev/null +++ b/src/components/timeline/TimelineLabelsComponent.js @@ -0,0 +1,11 @@ +import React from "react"; +import {convertSecondsToFormattedTime} from "../../util/date-time"; + +const TimelineLabelsComponent = ({currentTick, lastSimulatedTick}) => ( +
+
{convertSecondsToFormattedTime(currentTick)}
+
{convertSecondsToFormattedTime(lastSimulatedTick)}
+
+); + +export default TimelineLabelsComponent; diff --git a/src/containers/timeline/PlayButtonContainer.js b/src/containers/timeline/PlayButtonContainer.js new file mode 100644 index 00000000..6d4a9f25 --- /dev/null +++ b/src/containers/timeline/PlayButtonContainer.js @@ -0,0 +1,23 @@ +import {connect} from "react-redux"; +import {pauseSimulation, playSimulation} from "../../actions/simulation/playback"; +import PlayButtonComponent from "../../components/timeline/PlayButtonComponent"; + +const mapStateToProps = state => { + return { + isPlaying: state.isPlaying, + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onPlay: () => dispatch(playSimulation()), + onPause: () => dispatch(pauseSimulation()), + }; +}; + +const PlayButtonContainer = connect( + mapStateToProps, + mapDispatchToProps +)(PlayButtonComponent); + +export default PlayButtonContainer; diff --git a/src/containers/timeline/TimelineControlsContainer.js b/src/containers/timeline/TimelineControlsContainer.js new file mode 100644 index 00000000..e5c89060 --- /dev/null +++ b/src/containers/timeline/TimelineControlsContainer.js @@ -0,0 +1,22 @@ +import {connect} from "react-redux"; +import TimelineControlsComponent from "../../components/timeline/TimelineControlsComponent"; + +const mapStateToProps = state => { + let sectionTicks = []; + if (state.currentExperimentId !== -1) { + const sectionIds = state.objects.path[state.objects.experiment[state.currentExperimentId].pathId].sectionIds; + sectionTicks = sectionIds.map(sectionId => state.objects.section[sectionId].startTick); + } + + return { + currentTick: state.currentTick, + lastSimulatedTick: state.lastSimulatedTick, + sectionTicks, + }; +}; + +const TimelineControlsContainer = connect( + mapStateToProps +)(TimelineControlsComponent); + +export default TimelineControlsContainer; diff --git a/src/containers/timeline/TimelineLabelsContainer.js b/src/containers/timeline/TimelineLabelsContainer.js new file mode 100644 index 00000000..b6ff0774 --- /dev/null +++ b/src/containers/timeline/TimelineLabelsContainer.js @@ -0,0 +1,15 @@ +import {connect} from "react-redux"; +import TimelineLabelsComponent from "../../components/timeline/TimelineLabelsComponent"; + +const mapStateToProps = state => { + return { + currentTick: state.currentTick, + lastSimulatedTick: state.lastSimulatedTick, + }; +}; + +const TimelineLabelsContainer = connect( + mapStateToProps +)(TimelineLabelsComponent); + +export default TimelineLabelsContainer; diff --git a/src/pages/App.js b/src/pages/App.js index e70c7e48..87c139ec 100644 --- a/src/pages/App.js +++ b/src/pages/App.js @@ -2,10 +2,12 @@ import PropTypes from "prop-types"; import React from "react"; import {connect} from "react-redux"; import {ShortcutManager} from "react-shortcuts"; +import {openExperimentSucceeded} from "../actions/experiments"; import {openSimulationSucceeded} from "../actions/simulations"; -import {fetchLatestDatacenter, resetCurrentDatacenter} from "../actions/topology/building"; +import {resetCurrentDatacenter} from "../actions/topology/building"; import LoadingScreen from "../components/map/LoadingScreen"; import AppNavbar from "../components/navigation/AppNavbar"; +import Timeline from "../components/timeline/Timeline"; import MapStage from "../containers/map/MapStage"; import DeleteMachineModal from "../containers/modals/DeleteMachineModal"; import DeleteRackModal from "../containers/modals/DeleteRackModal"; @@ -20,15 +22,20 @@ const shortcutManager = new ShortcutManager(KeymapConfiguration); class AppComponent extends React.Component { static propTypes = { simulationId: PropTypes.number.isRequired, + inSimulation: PropTypes.bool, + experimentId: PropTypes.number, }; static childContextTypes = { shortcuts: PropTypes.object.isRequired }; componentDidMount() { - this.props.storeSimulationId(this.props.simulationId); this.props.resetCurrentDatacenter(); - this.props.fetchLatestDatacenter(); + if (this.props.inSimulation) { + this.props.openExperimentSucceeded(this.props.simulationId, this.props.experimentId); + return; + } + this.props.openSimulationSucceeded(this.props.simulationId); } getChildContext() { @@ -48,6 +55,10 @@ class AppComponent extends React.Component {
+ {this.props.inSimulation ? + : + undefined + }
} @@ -68,9 +79,10 @@ const mapStateToProps = state => { const mapDispatchToProps = dispatch => { return { - storeSimulationId: id => dispatch(openSimulationSucceeded(id)), resetCurrentDatacenter: () => dispatch(resetCurrentDatacenter()), - fetchLatestDatacenter: () => dispatch(fetchLatestDatacenter()), + openSimulationSucceeded: id => dispatch(openSimulationSucceeded(id)), + openExperimentSucceeded: (simulationId, experimentId) => + dispatch(openExperimentSucceeded(simulationId, experimentId)), }; }; diff --git a/src/reducers/current-ids.js b/src/reducers/current-ids.js index c94d7861..c92ff93b 100644 --- a/src/reducers/current-ids.js +++ b/src/reducers/current-ids.js @@ -1,9 +1,10 @@ +import {OPEN_EXPERIMENT_SUCCEEDED} from "../actions/experiments"; import {OPEN_SIMULATION_SUCCEEDED} from "../actions/simulations"; -import {FETCH_LATEST_DATACENTER_SUCCEEDED, RESET_CURRENT_DATACENTER} from "../actions/topology/building"; +import {RESET_CURRENT_DATACENTER, SET_CURRENT_DATACENTER} from "../actions/topology/building"; export function currentDatacenterId(state = -1, action) { switch (action.type) { - case FETCH_LATEST_DATACENTER_SUCCEEDED: + case SET_CURRENT_DATACENTER: return action.datacenterId; case RESET_CURRENT_DATACENTER: return -1; @@ -16,6 +17,8 @@ export function currentSimulationId(state = -1, action) { switch (action.type) { case OPEN_SIMULATION_SUCCEEDED: return action.id; + case OPEN_EXPERIMENT_SUCCEEDED: + return action.simulationId; default: return state; } diff --git a/src/reducers/simulation-mode.js b/src/reducers/simulation-mode.js index da6aa94a..60084824 100644 --- a/src/reducers/simulation-mode.js +++ b/src/reducers/simulation-mode.js @@ -6,7 +6,7 @@ import {GO_TO_TICK, SET_LAST_SIMULATED_TICK} from "../actions/simulation/tick"; export function currentExperimentId(state = -1, action) { switch (action.type) { case OPEN_EXPERIMENT_SUCCEEDED: - return action.id; + return action.experimentId; default: return state; } diff --git a/src/routes/index.js b/src/routes/index.js index 8a155cb0..96ac885c 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -10,17 +10,29 @@ import Simulations from "../pages/Simulations"; const ProtectedComponent = (component) => () => userIsLoggedIn() ? component : ; const AppComponent = ({match}) => userIsLoggedIn() ? - : ; + : + ; + const ExperimentsComponent = ({match}) => userIsLoggedIn() ? - : ; + : + ; + +const SimulationComponent = ({match}) => userIsLoggedIn() ? + : + ; const Routes = () => ( )}/> - - + + + )}/> diff --git a/src/sagas/experiments.js b/src/sagas/experiments.js index d1334759..cf92e97f 100644 --- a/src/sagas/experiments.js +++ b/src/sagas/experiments.js @@ -1,8 +1,20 @@ import {call, put, select} from "redux-saga/effects"; import {addPropToStoreObject, addToStore} from "../actions/objects"; -import {deleteExperiment} from "../api/routes/experiments"; +import {deleteExperiment, getExperiment} from "../api/routes/experiments"; import {addExperiment, getExperimentsOfSimulation} from "../api/routes/simulations"; import {fetchAndStoreAllSchedulers, fetchAndStoreAllTraces, fetchAndStorePathsOfSimulation} from "./objects"; +import {fetchAllDatacentersOfExperiment} from "./topology"; + +export function* onOpenExperimentSucceeded(action) { + try { + const experiment = yield call(getExperiment, action.experimentId); + yield put(addToStore("experiment", experiment)); + + yield fetchAllDatacentersOfExperiment(experiment); + } catch (error) { + console.error(error); + } +} export function* onFetchExperimentsOfSimulation() { try { diff --git a/src/sagas/index.js b/src/sagas/index.js index 30ca2f89..b10d6a1c 100644 --- a/src/sagas/index.js +++ b/src/sagas/index.js @@ -1,19 +1,28 @@ 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_EXPERIMENT, + DELETE_EXPERIMENT, + FETCH_EXPERIMENTS_OF_SIMULATION, + OPEN_EXPERIMENT_SUCCEEDED +} from "../actions/experiments"; import {ADD_SIMULATION, DELETE_SIMULATION, OPEN_SIMULATION_SUCCEEDED} from "../actions/simulations"; import { ADD_TILE, CANCEL_NEW_ROOM_CONSTRUCTION, DELETE_TILE, - FETCH_LATEST_DATACENTER, START_NEW_ROOM_CONSTRUCTION } from "../actions/topology/building"; 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 { + onAddExperiment, + onDeleteExperiment, + onFetchExperimentsOfSimulation, + onOpenExperimentSucceeded +} from "./experiments"; import {onDeleteCurrentUser} from "./profile"; import {onOpenSimulationSucceeded, onSimulationAdd, onSimulationDelete} from "./simulations"; import { @@ -29,7 +38,6 @@ import { onDeleteUnit, onEditRackName, onEditRoomName, - onFetchLatestDatacenter, onStartNewRoomConstruction } from "./topology"; import {onFetchAuthorizationsOfCurrentUser, onFetchLoggedInUser} from "./users"; @@ -44,8 +52,8 @@ export default function* rootSaga() { yield takeEvery(DELETE_CURRENT_USER, onDeleteCurrentUser); yield takeEvery(OPEN_SIMULATION_SUCCEEDED, onOpenSimulationSucceeded); + yield takeEvery(OPEN_EXPERIMENT_SUCCEEDED, onOpenExperimentSucceeded); - yield takeEvery(FETCH_LATEST_DATACENTER, onFetchLatestDatacenter); yield takeEvery(START_NEW_ROOM_CONSTRUCTION, onStartNewRoomConstruction); yield takeEvery(CANCEL_NEW_ROOM_CONSTRUCTION, onCancelNewRoomConstruction); yield takeEvery(ADD_TILE, onAddTile); diff --git a/src/sagas/simulations.js b/src/sagas/simulations.js index 57eb6bad..cf0358f9 100644 --- a/src/sagas/simulations.js +++ b/src/sagas/simulations.js @@ -2,11 +2,14 @@ import {call, put} from "redux-saga/effects"; import {addToStore} from "../actions/objects"; import {addSimulationSucceeded, deleteSimulationSucceeded} from "../actions/simulations"; import {addSimulation, deleteSimulation, getSimulation} from "../api/routes/simulations"; +import {fetchLatestDatacenter} from "./topology"; export function* onOpenSimulationSucceeded(action) { try { const simulation = yield call(getSimulation, action.id); yield put(addToStore("simulation", simulation)); + + yield fetchLatestDatacenter(action.id); } catch (error) { console.error(error); } diff --git a/src/sagas/topology.js b/src/sagas/topology.js index 763d4569..6a3e03af 100644 --- a/src/sagas/topology.js +++ b/src/sagas/topology.js @@ -8,7 +8,7 @@ import { } from "../actions/objects"; import { cancelNewRoomConstructionSucceeded, - fetchLatestDatacenterSucceeded, + setCurrentDatacenter, startNewRoomConstructionSucceeded } from "../actions/topology/building"; import {addRoomToDatacenter} from "../api/routes/datacenters"; @@ -38,6 +38,7 @@ import { fetchAndStoreGPU, fetchAndStoreMachinesOfTile, fetchAndStoreMemory, + fetchAndStorePath, fetchAndStorePathsOfSimulation, fetchAndStorePSU, fetchAndStoreRackOnTile, @@ -47,15 +48,31 @@ import { fetchAndStoreTilesOfRoom } from "./objects"; -export function* onFetchLatestDatacenter(action) { +export function* fetchLatestDatacenter(simulationId) { try { - const paths = yield fetchAndStorePathsOfSimulation(action.currentSimulationId); + const paths = yield fetchAndStorePathsOfSimulation(simulationId); const latestPath = paths[paths.length - 1]; const sections = yield fetchAndStoreSectionsOfPath(latestPath.id); const latestSection = sections[sections.length - 1]; yield fetchAllUnitSpecifications(); yield fetchDatacenter(latestSection.datacenterId); - yield put(fetchLatestDatacenterSucceeded(latestSection.datacenterId)); + yield put(setCurrentDatacenter(latestSection.datacenterId)); + } catch (error) { + console.error(error); + } +} + +export function* fetchAllDatacentersOfExperiment(experiment) { + try { + const path = yield fetchAndStorePath(experiment.pathId); + const sections = yield fetchAndStoreSectionsOfPath(path.id); + path.sectionIds = sections.map(section => section.id); + yield fetchAllUnitSpecifications(); + + for (let i in sections) { + yield fetchDatacenter(sections[i].datacenterId); + } + yield put(setCurrentDatacenter(sections[0].datacenterId)); } catch (error) { console.error(error); } diff --git a/src/util/date-time.js b/src/util/date-time.js index ef3524db..3ec6b671 100644 --- a/src/util/date-time.js +++ b/src/util/date-time.js @@ -88,7 +88,7 @@ export function getCurrentDateTime() { * Pads the given integer to have at least two digits. * * @param integer An integer to be padded. - * @returns {string} A string containing the padded integer + * @returns {string} A string containing the padded integer. */ export function addPaddingToTwo(integer) { if (integer < 10) { @@ -97,3 +97,25 @@ export function addPaddingToTwo(integer) { return integer.toString(); } } + +/** + * Formats the given number of seconds/ticks to a formatted time representation. + * + * @param seconds The number of seconds. + * @returns {string} A string representation of that amount of second, in the from of HH:MM:SS. + */ +export function convertSecondsToFormattedTime(seconds) { + if (seconds <= 0) { + return "00:00:00"; + } + + let hour = Math.floor(seconds / 3600); + let minute = Math.floor(seconds / 60) % 60; + let second = seconds % 60; + + hour = isNaN(hour) ? 0 : hour; + minute = isNaN(minute) ? 0 : minute; + second = isNaN(second) ? 0 : second; + + return addPaddingToTwo(hour) + ":" + addPaddingToTwo(minute) + ":" + addPaddingToTwo(second); +} -- cgit v1.2.3