summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGeorgios Andreadis <g.andreadis@student.tudelft.nl>2017-09-18 16:52:11 +0200
committerGeorgios Andreadis <g.andreadis@student.tudelft.nl>2017-09-23 10:06:04 +0200
commitf8f617c97fcb2df3dbefc9527d974151e367cb60 (patch)
treef6405aa54f73b66220f36e3a388725f71d023cfb /src
parent9f86ae6de969baa625e3341c796c64f63b5153ce (diff)
Implement basic experiment mode with timeline
The timeline doesn't trigger anything yet, but the visual element is in place and connected.
Diffstat (limited to 'src')
-rw-r--r--src/actions/experiments.js5
-rw-r--r--src/actions/topology/building.js17
-rw-r--r--src/api/routes/experiments.js4
-rw-r--r--src/components/map/groups/RackGroup.js2
-rw-r--r--src/components/map/groups/TileGroup.js2
-rw-r--r--src/components/navigation/AppNavbar.js8
-rw-r--r--src/components/sidebars/topology/rack/MachineComponent.js2
-rw-r--r--src/components/timeline/PlayButtonComponent.js12
-rw-r--r--src/components/timeline/Timeline.js15
-rw-r--r--src/components/timeline/Timeline.sass108
-rw-r--r--src/components/timeline/TimelineControlsComponent.js33
-rw-r--r--src/components/timeline/TimelineLabelsComponent.js11
-rw-r--r--src/containers/timeline/PlayButtonContainer.js23
-rw-r--r--src/containers/timeline/TimelineControlsContainer.js22
-rw-r--r--src/containers/timeline/TimelineLabelsContainer.js15
-rw-r--r--src/pages/App.js22
-rw-r--r--src/reducers/current-ids.js7
-rw-r--r--src/reducers/simulation-mode.js2
-rw-r--r--src/routes/index.js20
-rw-r--r--src/sagas/experiments.js14
-rw-r--r--src/sagas/index.js18
-rw-r--r--src/sagas/simulations.js3
-rw-r--r--src/sagas/topology.js25
-rw-r--r--src/util/date-time.js24
24 files changed, 368 insertions, 46 deletions
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}) => (
<NavItem route={"/simulations/" + simulationId}>
<Link
className="nav-link"
- title="Build"
+ title="Construction"
to={"/simulations/" + simulationId}>
<FontAwesome name="industry" className="mr-1"/>
- Build
+ Construction
</Link>
</NavItem> :
undefined
@@ -23,10 +23,10 @@ const AppNavbar = ({simulationId, inSimulation}) => (
<NavItem route={"/simulations/" + simulationId + "/experiments"}>
<Link
className="nav-link"
- title="Simulate"
+ title="Experiments"
to={"/simulations/" + simulationId + "/experiments"}>
<FontAwesome name="play" className="mr-1"/>
- Simulate
+ Experiments
</Link>
</NavItem> :
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}) => (
+ <div className="play-btn" onClick={() => isPlaying ? onPause() : onPlay()}>
+ {isPlaying ?
+ <span className="fa fa-pause"/> :
+ <span className="fa fa-play"/>
+ }
+ </div>
+);
+
+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}) => (
+ <div className="timeline-bar">
+ <div className="timeline-container">
+ <TimelineLabelsContainer/>
+ <TimelineControlsContainer/>
+ </div>
+ </div>
+);
+
+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}) => (
+ <div className="timeline-controls">
+ <PlayButtonContainer/>
+ <div className="timeline">
+ <div
+ className="time-marker"
+ style={{left: getXPercentage(currentTick, lastSimulatedTick)}}
+ />
+ {sectionTicks.map(sectionTick => (
+ <div
+ key={sectionTick}
+ className="section-marker"
+ style={{left: getXPercentage(sectionTick, lastSimulatedTick)}}
+ />
+ ))}
+ </div>
+ </div>
+);
+
+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}) => (
+ <div className="timeline-labels">
+ <div className="start-time-label">{convertSecondsToFormattedTime(currentTick)}</div>
+ <div className="end-time-label">{convertSecondsToFormattedTime(lastSimulatedTick)}</div>
+ </div>
+);
+
+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 {
<div className="full-height">
<MapStage/>
<TopologySidebar/>
+ {this.props.inSimulation ?
+ <Timeline/> :
+ undefined
+ }
</div>
}
<EditRoomNameModal/>
@@ -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 : <Redirect to="/"/>;
const AppComponent = ({match}) => userIsLoggedIn() ?
- <App simulationId={parseInt(match.params.id, 10)}/> : <Redirect to="/"/>;
+ <App simulationId={parseInt(match.params.simulationId, 10)}/> :
+ <Redirect to="/"/>;
+
const ExperimentsComponent = ({match}) => userIsLoggedIn() ?
- <Experiments simulationId={parseInt(match.params.id, 10)}/> : <Redirect to="/"/>;
+ <Experiments simulationId={parseInt(match.params.simulationId, 10)}/> :
+ <Redirect to="/"/>;
+
+const SimulationComponent = ({match}) => userIsLoggedIn() ?
+ <App
+ simulationId={parseInt(match.params.simulationId, 10)}
+ inSimulation={true}
+ experimentId={parseInt(match.params.experimentId, 10)}
+ /> :
+ <Redirect to="/"/>;
const Routes = () => (
<BrowserRouter>
<Switch>
<Route exact path="/" component={Home}/>
<Route exact path="/simulations" render={ProtectedComponent(<Simulations/>)}/>
- <Route exact path="/simulations/:id" component={AppComponent}/>
- <Route exact path="/simulations/:id/experiments" component={ExperimentsComponent}/>
+ <Route exact path="/simulations/:simulationId" component={AppComponent}/>
+ <Route exact path="/simulations/:simulationId/experiments" component={ExperimentsComponent}/>
+ <Route exact path="/simulations/:simulationId/experiments/:experimentId" component={SimulationComponent}/>
<Route exact path="/profile" render={ProtectedComponent(<Profile/>)}/>
<Route path="/*" component={NotFound}/>
</Switch>
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);
+}