From c47a27b826f7d76410308a4151611a366f9eaf46 Mon Sep 17 00:00:00 2001 From: Georgios Andreadis Date: Fri, 25 Aug 2017 17:48:12 +0200 Subject: Fetch and display datacenter topology --- package.json | 1 - src/actions/objects.js | 10 +++ src/actions/topology.js | 21 ++++++ src/api/routes/rooms.js | 1 + src/api/sagas/index.js | 15 ---- src/api/sagas/objects.js | 24 ------ src/api/sagas/profile.js | 12 --- src/api/sagas/simulations.js | 30 -------- src/api/sagas/users.js | 46 ----------- src/components/map/MapStage.js | 8 +- src/components/map/elements/RoomTile.js | 1 + src/components/map/elements/TileObject.js | 2 +- src/components/map/groups/DatacenterGroup.js | 20 +++-- src/components/map/groups/GridGroup.js | 3 +- src/components/map/groups/RoomGroup.js | 3 +- src/containers/map/DatacenterContainer.js | 22 ++++++ src/pages/App.js | 6 +- src/pages/Simulations.js | 4 - src/reducers/index.js | 5 +- src/reducers/objects.js | 24 ++++-- src/reducers/topology.js | 10 +++ src/sagas/index.js | 18 +++++ src/sagas/objects.js | 109 +++++++++++++++++++++++++++ src/sagas/profile.js | 12 +++ src/sagas/simulations.js | 30 ++++++++ src/sagas/topology.js | 71 +++++++++++++++++ src/sagas/users.js | 46 +++++++++++ src/shapes/index.js | 3 +- src/store/configureStore.js | 2 +- src/store/denormalizer.js | 20 ++++- 30 files changed, 416 insertions(+), 163 deletions(-) create mode 100644 src/actions/topology.js delete mode 100644 src/api/sagas/index.js delete mode 100644 src/api/sagas/objects.js delete mode 100644 src/api/sagas/profile.js delete mode 100644 src/api/sagas/simulations.js delete mode 100644 src/api/sagas/users.js create mode 100644 src/containers/map/DatacenterContainer.js create mode 100644 src/reducers/topology.js create mode 100644 src/sagas/index.js create mode 100644 src/sagas/objects.js create mode 100644 src/sagas/profile.js create mode 100644 src/sagas/simulations.js create mode 100644 src/sagas/topology.js create mode 100644 src/sagas/users.js diff --git a/package.json b/package.json index 10f9fa7c..25d27b02 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,6 @@ "isomorphic-fetch": "^2.2.1", "konva": "^1.6.7", "node-sass-chokidar": "^0.0.3", - "normalizr": "^3.2.3", "npm-run-all": "^4.0.2", "prop-types": "^15.5.10", "react": "^15.6.1", diff --git a/src/actions/objects.js b/src/actions/objects.js index 0a0e3046..6fdb83cd 100644 --- a/src/actions/objects.js +++ b/src/actions/objects.js @@ -1,4 +1,5 @@ export const ADD_TO_STORE = "ADD_TO_STORE"; +export const ADD_PROP_TO_STORE_OBJECT = "ADD_PROP_TO_STORE_OBJECT"; export function addToStore(objectType, object) { return { @@ -7,3 +8,12 @@ export function addToStore(objectType, object) { object }; } + +export function addPropToStoreObject(objectType, objectId, propObject) { + return { + type: ADD_PROP_TO_STORE_OBJECT, + objectType, + objectId, + propObject + }; +} diff --git a/src/actions/topology.js b/src/actions/topology.js new file mode 100644 index 00000000..de742bb1 --- /dev/null +++ b/src/actions/topology.js @@ -0,0 +1,21 @@ +export const FETCH_TOPOLOGY_OF_DATACENTER = "FETCH_TOPOLOGY_OF_DATACENTER"; +export const FETCH_TOPOLOGY_OF_DATACENTER_SUCCEEDED = "FETCH_TOPOLOGY_OF_DATACENTER_SUCCEEDED"; +export const FETCH_LATEST_DATACENTER = "FETCH_LATEST_DATACENTER"; +export const FETCH_LATEST_DATACENTER_SUCCEEDED = "FETCH_LATEST_DATACENTER_SUCCEEDED"; + +export function fetchLatestDatacenter() { + return (dispatch, getState) => { + const {currentSimulationId} = getState(); + dispatch({ + type: FETCH_LATEST_DATACENTER, + currentSimulationId + }); + }; +} + +export function fetchLatestDatacenterSucceeded(datacenterId) { + return { + type: FETCH_LATEST_DATACENTER_SUCCEEDED, + datacenterId + }; +} diff --git a/src/api/routes/rooms.js b/src/api/routes/rooms.js index e08cc6e7..1a7a7453 100644 --- a/src/api/routes/rooms.js +++ b/src/api/routes/rooms.js @@ -1,4 +1,5 @@ import {sendRequest} from "../index"; +import {deleteById, getById} from "./util"; export function getRoom(roomId) { return getById("/rooms/{roomId}", {roomId}); diff --git a/src/api/sagas/index.js b/src/api/sagas/index.js deleted file mode 100644 index 7fe57453..00000000 --- a/src/api/sagas/index.js +++ /dev/null @@ -1,15 +0,0 @@ -import {takeEvery} from "redux-saga/effects"; -import {LOG_IN} from "../../actions/auth"; -import {ADD_SIMULATION, DELETE_SIMULATION} from "../../actions/simulations"; -import {DELETE_CURRENT_USER, FETCH_AUTHORIZATIONS_OF_CURRENT_USER} from "../../actions/users"; -import {onDeleteCurrentUser} from "./profile"; -import {onSimulationAdd, onSimulationDelete} from "./simulations"; -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); -} diff --git a/src/api/sagas/objects.js b/src/api/sagas/objects.js deleted file mode 100644 index 98c766ec..00000000 --- a/src/api/sagas/objects.js +++ /dev/null @@ -1,24 +0,0 @@ -import {call, put, select} from "redux-saga/effects"; -import {addToStore} from "../../actions/objects"; -import {getSimulation} from "../routes/simulations"; -import {getUser} from "../routes/users"; - -const selectors = { - simulation: state => state.objects.simulation, - user: state => state.objects.user, - authorization: state => state.objects.authorization, -}; - -function* fetchAndStoreObject(objectType, id, apiCall) { - const objectStore = yield select(selectors[objectType]); - if (!objectStore[id]) { - const object = yield apiCall; - yield put(addToStore(objectType, object)); - } -} - -export const fetchAndStoreSimulation = (id) => - fetchAndStoreObject("simulation", id, call(getSimulation, id)); - -export const fetchAndStoreUser = (id) => - fetchAndStoreObject("user", id, call(getUser, id),); diff --git a/src/api/sagas/profile.js b/src/api/sagas/profile.js deleted file mode 100644 index 3c4e1825..00000000 --- a/src/api/sagas/profile.js +++ /dev/null @@ -1,12 +0,0 @@ -import {call, put} from "redux-saga/effects"; -import {deleteCurrentUserSucceeded} from "../../actions/users"; -import {deleteUser} from "../routes/users"; - -export function* onDeleteCurrentUser(action) { - try { - yield call(deleteUser, action.userId); - yield put(deleteCurrentUserSucceeded()); - } catch (error) { - console.log(error); - } -} diff --git a/src/api/sagas/simulations.js b/src/api/sagas/simulations.js deleted file mode 100644 index 6b7471c0..00000000 --- a/src/api/sagas/simulations.js +++ /dev/null @@ -1,30 +0,0 @@ -import {call, put} from "redux-saga/effects"; -import {addToStore} from "../../actions/objects"; -import {addSimulationSucceeded, deleteSimulationSucceeded} from "../../actions/simulations"; -import {addSimulation, deleteSimulation} from "../routes/simulations"; - -export function* onSimulationAdd(action) { - try { - const simulation = yield call(addSimulation, {name: action.name}); - yield put(addToStore("simulation", simulation)); - - const authorization = { - simulationId: simulation.id, - userId: action.userId, - authorizationLevel: "OWN" - }; - yield put(addToStore("authorization", authorization)); - yield put(addSimulationSucceeded([authorization.userId, authorization.simulationId])); - } catch (error) { - console.log(error); - } -} - -export function* onSimulationDelete(action) { - try { - yield call(deleteSimulation, action.id); - yield put(deleteSimulationSucceeded(action.id)); - } catch (error) { - console.log(error); - } -} diff --git a/src/api/sagas/users.js b/src/api/sagas/users.js deleted file mode 100644 index d3bc3f5f..00000000 --- a/src/api/sagas/users.js +++ /dev/null @@ -1,46 +0,0 @@ -import {call, put} from "redux-saga/effects"; -import {logInSucceeded} from "../../actions/auth"; -import {addToAuthorizationStore} from "../../actions/objects"; -import {fetchAuthorizationsOfCurrentUserSucceeded} from "../../actions/users"; -import {saveAuthLocalStorage} from "../../auth/index"; -import {performTokenSignIn} from "../routes/token-signin"; -import {addUser, getAuthorizationsByUser} from "../routes/users"; -import {fetchAndStoreSimulation, fetchAndStoreUser} from "./objects"; - -export function* onFetchLoggedInUser(action) { - try { - const tokenResponse = yield call(performTokenSignIn, action.payload.authToken); - let userId = tokenResponse.userId; - - if (tokenResponse.isNewUser) { - saveAuthLocalStorage({authToken: action.payload.authToken}); - const newUser = yield call(addUser, action.payload); - userId = newUser.id; - } - - yield put(logInSucceeded(Object.assign({userId}, action.payload))); - } catch (error) { - console.log(error); - } -} - -export function* onFetchAuthorizationsOfCurrentUser(action) { - try { - const authorizations = yield call(getAuthorizationsByUser, action.userId); - - for (const authorization of authorizations) { - yield put(addToAuthorizationStore(authorization)); - - yield fetchAndStoreSimulation(authorization.simulationId); - yield fetchAndStoreUser(authorization.userId); - } - - const authorizationIds = authorizations.map(authorization => ( - [authorization.userId, authorization.simulationId] - )); - - yield put(fetchAuthorizationsOfCurrentUserSucceeded(authorizationIds)); - } catch (error) { - console.log(error); - } -} diff --git a/src/components/map/MapStage.js b/src/components/map/MapStage.js index 0950d5bd..879c7c39 100644 --- a/src/components/map/MapStage.js +++ b/src/components/map/MapStage.js @@ -1,8 +1,8 @@ import React from "react"; import {Group, Layer, Stage} from "react-konva"; +import DatacenterContainer from "../../containers/map/DatacenterContainer"; import jQuery from "../../util/jquery"; import Backdrop from "./elements/Backdrop"; -import DatacenterGroup from "./groups/DatacenterGroup"; import GridGroup from "./groups/GridGroup"; import {MAP_SIZE_IN_PIXELS} from "./MapConstants"; @@ -28,7 +28,7 @@ class MapStage extends React.Component { this.setState({width: jQuery(window).width(), height: jQuery(window).height()}); } - dragBoundHandler(pos) { + dragBoundFunc(pos) { return { x: pos.x > 0 ? 0 : (pos.x < -MAP_SIZE_IN_PIXELS + this.state.width ? -MAP_SIZE_IN_PIXELS + this.state.width : pos.x), @@ -41,9 +41,9 @@ class MapStage extends React.Component { return ( - + - + diff --git a/src/components/map/elements/RoomTile.js b/src/components/map/elements/RoomTile.js index aa837def..759dcf35 100644 --- a/src/components/map/elements/RoomTile.js +++ b/src/components/map/elements/RoomTile.js @@ -2,6 +2,7 @@ import React from "react"; import {Rect} from "react-konva"; import {ROOM_DEFAULT_COLOR} from "../../../colors/index"; import Shapes from "../../../shapes/index"; +import {TILE_SIZE_IN_PIXELS} from "../MapConstants"; const RoomTile = ({tile}) => ( ( ( - - {datacenter.rooms.map(room => ( - - ))} - -); +const DatacenterGroup = ({datacenter}) => { + if (!datacenter) { + return ; + } + return ( + + {datacenter.rooms.map(room => ( + + ))} + + ); +}; DatacenterGroup.propTypes = { datacenter: Shapes.Datacenter, diff --git a/src/components/map/groups/GridGroup.js b/src/components/map/groups/GridGroup.js index 2651bf19..f50482ce 100644 --- a/src/components/map/groups/GridGroup.js +++ b/src/components/map/groups/GridGroup.js @@ -15,11 +15,12 @@ const VERTICAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map(index => [ const GridGroup = () => ( - {HORIZONTAL_POINT_PAIRS.concat(VERTICAL_POINT_PAIRS).map(points => ( + {HORIZONTAL_POINT_PAIRS.concat(VERTICAL_POINT_PAIRS).map((points, index) => ( ))} diff --git a/src/components/map/groups/RoomGroup.js b/src/components/map/groups/RoomGroup.js index 90a58767..28240d77 100644 --- a/src/components/map/groups/RoomGroup.js +++ b/src/components/map/groups/RoomGroup.js @@ -1,11 +1,12 @@ import React from "react"; import {Group} from "react-konva"; +import Shapes from "../../../shapes/index"; import TileGroup from "./TileGroup"; const RoomGroup = ({room}) => ( {room.tiles.map(tile => ( - + ))} ); diff --git a/src/containers/map/DatacenterContainer.js b/src/containers/map/DatacenterContainer.js new file mode 100644 index 00000000..1716f22d --- /dev/null +++ b/src/containers/map/DatacenterContainer.js @@ -0,0 +1,22 @@ +import {connect} from "react-redux"; +import DatacenterGroup from "../../components/map/groups/DatacenterGroup"; +import {denormalize} from "../../store/denormalizer"; + +const mapStateToProps = state => { + if (state.currentDatacenterId === -1) { + return {}; + } + + const datacenter = denormalize(state, "datacenter", state.currentDatacenterId); + console.log(datacenter); + + return { + datacenter + }; +}; + +const DatacenterContainer = connect( + mapStateToProps +)(DatacenterGroup); + +export default DatacenterContainer; diff --git a/src/pages/App.js b/src/pages/App.js index a2a1050b..f34c03cd 100644 --- a/src/pages/App.js +++ b/src/pages/App.js @@ -2,7 +2,7 @@ import PropTypes from "prop-types"; import React from 'react'; import {connect} from "react-redux"; import {openSimulationSucceeded} from "../actions/simulations"; -import {fetchAuthorizationsOfCurrentUser} from "../actions/users"; +import {fetchLatestDatacenter} from "../actions/topology"; import MapStage from "../components/map/MapStage"; import Navbar from "../components/navigation/Navbar"; import Login from "../containers/auth/Login"; @@ -14,7 +14,7 @@ class AppContainer extends React.Component { componentDidMount() { this.props.storeSimulationId(this.props.simulationId); - this.props.fetchAuthorizationsOfCurrentUser(); + this.props.fetchLatestDatacenter(); } render() { @@ -33,7 +33,7 @@ class AppContainer extends React.Component { const mapDispatchToProps = dispatch => { return { storeSimulationId: id => dispatch(openSimulationSucceeded(id)), - fetchAuthorizationsOfCurrentUser: () => dispatch(fetchAuthorizationsOfCurrentUser()), + fetchLatestDatacenter: () => dispatch(fetchLatestDatacenter()), }; }; diff --git a/src/pages/Simulations.js b/src/pages/Simulations.js index c46cb621..8218b1a4 100644 --- a/src/pages/Simulations.js +++ b/src/pages/Simulations.js @@ -15,10 +15,6 @@ class SimulationsContainer extends React.Component { this.props.fetchAuthorizationsOfCurrentUser(); } - onInputSubmission(text) { - this.props.addSimulation(text); - } - render() { return (
diff --git a/src/reducers/index.js b/src/reducers/index.js index 40a51a04..287a9762 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -2,7 +2,8 @@ import {combineReducers} from "redux"; import {auth} from "./auth"; import {modals} from "./modals"; import {objects} from "./objects"; -import {authorizationsOfCurrentUser, authVisibilityFilter} from "./simulations"; +import {authorizationsOfCurrentUser, authVisibilityFilter, currentSimulationId} from "./simulations"; +import {currentDatacenterId} from "./topology"; const rootReducer = combineReducers({ auth, @@ -10,6 +11,8 @@ const rootReducer = combineReducers({ modals, authorizationsOfCurrentUser, authVisibilityFilter, + currentSimulationId, + currentDatacenterId, }); export default rootReducer; diff --git a/src/reducers/objects.js b/src/reducers/objects.js index 60cd2711..4fbffea6 100644 --- a/src/reducers/objects.js +++ b/src/reducers/objects.js @@ -1,5 +1,5 @@ import {combineReducers} from "redux"; -import {ADD_TO_STORE} from "../actions/objects"; +import {ADD_PROP_TO_STORE_OBJECT, ADD_TO_STORE} from "../actions/objects"; export const objects = combineReducers({ simulation: object("simulation"), @@ -27,14 +27,22 @@ function object(type) { function objectWithId(type, getId) { return (state = {}, action) => { - if (action.type === ADD_TO_STORE) { - if (action.objectType === type) { - return Object.assign( - state, - {[getId(action.object)]: action.object} - ); - } + if (action.objectType !== type) { return state; } + + if (action.type === ADD_TO_STORE) { + return Object.assign( + state, + {[getId(action.object)]: action.object} + ); + } else if (action.type === ADD_PROP_TO_STORE_OBJECT) { + return Object.assign( + state, + {[action.objectId]: Object.assign(state[action.objectId], action.propObject)} + ); + } + + return state; }; } diff --git a/src/reducers/topology.js b/src/reducers/topology.js new file mode 100644 index 00000000..caafb7c1 --- /dev/null +++ b/src/reducers/topology.js @@ -0,0 +1,10 @@ +import {FETCH_LATEST_DATACENTER_SUCCEEDED} from "../actions/topology"; + +export function currentDatacenterId(state = -1, action) { + switch (action.type) { + case FETCH_LATEST_DATACENTER_SUCCEEDED: + return action.datacenterId; + default: + return state; + } +} diff --git a/src/sagas/index.js b/src/sagas/index.js new file mode 100644 index 00000000..c6177cd4 --- /dev/null +++ b/src/sagas/index.js @@ -0,0 +1,18 @@ +import {takeEvery} from "redux-saga/effects"; +import {LOG_IN} from "../actions/auth"; +import {ADD_SIMULATION, DELETE_SIMULATION} from "../actions/simulations"; +import {FETCH_LATEST_DATACENTER} from "../actions/topology"; +import {DELETE_CURRENT_USER, FETCH_AUTHORIZATIONS_OF_CURRENT_USER} from "../actions/users"; +import {onDeleteCurrentUser} from "./profile"; +import {onSimulationAdd, onSimulationDelete} from "./simulations"; +import {onFetchLatestDatacenter} from "./topology"; +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); +} diff --git a/src/sagas/objects.js b/src/sagas/objects.js new file mode 100644 index 00000000..5fac6c3e --- /dev/null +++ b/src/sagas/objects.js @@ -0,0 +1,109 @@ +import {call, put, select} from "redux-saga/effects"; +import {addToStore} from "../actions/objects"; +import {getDatacenter, getRoomsOfDatacenter} from "../api/routes/datacenters"; +import {getPath, getSectionsOfPath} from "../api/routes/paths"; +import {getTilesOfRoom} from "../api/routes/rooms"; +import {getSection} from "../api/routes/sections"; +import {getPathsOfSimulation, getSimulation} from "../api/routes/simulations"; +import { + getCoolingItem, + getCPU, + getFailureModel, + getGPU, + getMemory, + getPSU, + getStorage +} from "../api/routes/specifications"; +import {getMachinesOfRackByTile, getRackByTile} from "../api/routes/tiles"; +import {getUser} from "../api/routes/users"; + +export const OBJECT_SELECTORS = { + simulation: state => state.objects.simulation, + user: state => state.objects.user, + authorization: state => state.objects.authorization, + failureModel: state => state.objects.failureModel, + cpu: state => state.objects.cpu, + gpu: state => state.objects.gpu, + memory: state => state.objects.memory, + storage: state => state.objects.storage, + machine: state => state.objects.machine, + rack: state => state.objects.rack, + coolingItem: state => state.objects.coolingItem, + psu: state => state.objects.psu, + tile: state => state.objects.tile, + room: state => state.objects.room, + datacenter: state => state.objects.datacenter, + section: state => state.objects.section, + path: state => state.objects.path, +}; + +function* fetchAndStoreObject(objectType, id, apiCall) { + const objectStore = yield select(OBJECT_SELECTORS[objectType]); + if (!objectStore[id]) { + const object = yield apiCall; + yield put(addToStore(objectType, object)); + } + return objectStore[id]; +} + +function* fetchAndStoreObjects(objectType, apiCall) { + const objects = yield apiCall; + for (let index in objects) { + yield put(addToStore(objectType, objects[index])); + } + return objects; +} + +export const fetchAndStoreSimulation = (id) => + fetchAndStoreObject("simulation", id, call(getSimulation, id)); + +export const fetchAndStoreUser = (id) => + fetchAndStoreObject("user", id, call(getUser, id)); + +export const fetchAndStoreFailureModel = (id) => + fetchAndStoreObject("failureModel", id, call(getFailureModel, id)); + +export const fetchAndStoreCPU = (id) => + fetchAndStoreObject("cpu", id, call(getCPU, id)); + +export const fetchAndStoreGPU = (id) => + fetchAndStoreObject("gpu", id, call(getGPU, id)); + +export const fetchAndStoreMemory = (id) => + fetchAndStoreObject("memory", id, call(getMemory, id)); + +export const fetchAndStoreStorage = (id) => + fetchAndStoreObject("storage", id, call(getStorage, id)); + +export const fetchAndStoreMachinesOfTile = (tileId) => + fetchAndStoreObjects("machine", call(getMachinesOfRackByTile, tileId)); + +export const fetchAndStoreRackOnTile = (id, tileId) => + fetchAndStoreObject("rack", id, call(getRackByTile, tileId)); + +export const fetchAndStoreCoolingItem = (id) => + fetchAndStoreObject("coolingItem", id, call(getCoolingItem, id)); + +export const fetchAndStorePSU = (id) => + fetchAndStoreObject("psu", id, call(getPSU, id)); + +export const fetchAndStoreTilesOfRoom = (roomId) => + fetchAndStoreObjects("tile", call(getTilesOfRoom, roomId)); + +export const fetchAndStoreRoomsOfDatacenter = (datacenterId) => + fetchAndStoreObjects("room", call(getRoomsOfDatacenter, datacenterId)); + +export const fetchAndStoreDatacenter = (id) => + fetchAndStoreObject("datacenter", id, call(getDatacenter, id)); + +export const fetchAndStoreSection = (id) => + fetchAndStoreObject("section", id, call(getSection, id)); + +export const fetchAndStoreSectionsOfPath = (pathId) => + fetchAndStoreObjects("section", call(getSectionsOfPath, pathId)); + +export const fetchAndStorePath = (id) => + fetchAndStoreObject("path", id, call(getPath, id)); + +export const fetchAndStorePathsOfSimulation = (simulationId) => + fetchAndStoreObjects("path", call(getPathsOfSimulation, simulationId)); diff --git a/src/sagas/profile.js b/src/sagas/profile.js new file mode 100644 index 00000000..6a72e7c2 --- /dev/null +++ b/src/sagas/profile.js @@ -0,0 +1,12 @@ +import {call, put} from "redux-saga/effects"; +import {deleteCurrentUserSucceeded} from "../actions/users"; +import {deleteUser} from "../api/routes/users"; + +export function* onDeleteCurrentUser(action) { + try { + yield call(deleteUser, action.userId); + yield put(deleteCurrentUserSucceeded()); + } catch (error) { + console.log(error); + } +} diff --git a/src/sagas/simulations.js b/src/sagas/simulations.js new file mode 100644 index 00000000..b699002e --- /dev/null +++ b/src/sagas/simulations.js @@ -0,0 +1,30 @@ +import {call, put} from "redux-saga/effects"; +import {addToStore} from "../actions/objects"; +import {addSimulationSucceeded, deleteSimulationSucceeded} from "../actions/simulations"; +import {addSimulation, deleteSimulation} from "../api/routes/simulations"; + +export function* onSimulationAdd(action) { + try { + const simulation = yield call(addSimulation, {name: action.name}); + yield put(addToStore("simulation", simulation)); + + const authorization = { + simulationId: simulation.id, + userId: action.userId, + authorizationLevel: "OWN" + }; + yield put(addToStore("authorization", authorization)); + yield put(addSimulationSucceeded([authorization.userId, authorization.simulationId])); + } catch (error) { + console.log(error); + } +} + +export function* onSimulationDelete(action) { + try { + yield call(deleteSimulation, action.id); + yield put(deleteSimulationSucceeded(action.id)); + } catch (error) { + console.log(error); + } +} diff --git a/src/sagas/topology.js b/src/sagas/topology.js new file mode 100644 index 00000000..6d359534 --- /dev/null +++ b/src/sagas/topology.js @@ -0,0 +1,71 @@ +import {put} from "redux-saga/effects"; +import {addPropToStoreObject} from "../actions/objects"; +import {fetchLatestDatacenterSucceeded} from "../actions/topology"; +import { + fetchAndStoreCoolingItem, + fetchAndStoreDatacenter, + fetchAndStorePathsOfSimulation, + fetchAndStorePSU, + fetchAndStoreRackOnTile, + fetchAndStoreRoomsOfDatacenter, + fetchAndStoreSectionsOfPath, + fetchAndStoreTilesOfRoom +} from "./objects"; + +export function* onFetchLatestDatacenter(action) { + try { + const paths = yield fetchAndStorePathsOfSimulation(action.currentSimulationId); + const latestPath = paths[paths.length - 1]; + const sections = yield fetchAndStoreSectionsOfPath(latestPath.id); + const latestSection = sections[sections.length - 1]; + yield fetchDatacenter(latestSection.datacenterId); + yield put(fetchLatestDatacenterSucceeded(latestSection.datacenterId)); + } catch (error) { + console.log(error); + } +} + +export function* fetchDatacenter(datacenterId) { + try { + const datacenter = yield fetchAndStoreDatacenter(datacenterId); + datacenter.roomIds = (yield fetchAndStoreRoomsOfDatacenter(datacenterId)).map(room => room.id); + + for (let index in datacenter.roomIds) { + yield fetchRoom(datacenter.roomIds[index]); + } + } catch (error) { + console.log(error); + } +} + +function* fetchRoom(roomId) { + const tiles = yield fetchAndStoreTilesOfRoom(roomId); + yield put(addPropToStoreObject("room", roomId, {tileIds: tiles.map(tile => tile.id)})); + + for (let index in tiles) { + yield fetchTile(tiles[index]); + } +} + +function* fetchTile(tile) { + if (!tile.objectType) { + return; + } + console.log(tile); + switch (tile.objectType) { + case "RACK": + const rack = yield fetchAndStoreRackOnTile(tile.objectId, tile.id); + yield put(addPropToStoreObject("tile", tile.id, {rackId: rack.id})); + break; + case "COOLING_ITEM": + const coolingItem = yield fetchAndStoreCoolingItem(tile.objectId); + yield put(addPropToStoreObject("tile", tile.id, {coolingItemId: coolingItem.id})); + break; + case "PSU": + const psu = yield fetchAndStorePSU(tile.objectId); + yield put(addPropToStoreObject("tile", tile.id, {psuId: psu.id})); + break; + default: + console.warn("Unknown object type encountered while fetching tile objects"); + } +} diff --git a/src/sagas/users.js b/src/sagas/users.js new file mode 100644 index 00000000..5f9bffa1 --- /dev/null +++ b/src/sagas/users.js @@ -0,0 +1,46 @@ +import {call, put} from "redux-saga/effects"; +import {logInSucceeded} from "../actions/auth"; +import {addToStore} from "../actions/objects"; +import {fetchAuthorizationsOfCurrentUserSucceeded} from "../actions/users"; +import {performTokenSignIn} from "../api/routes/token-signin"; +import {addUser, getAuthorizationsByUser} from "../api/routes/users"; +import {saveAuthLocalStorage} from "../auth/index"; +import {fetchAndStoreSimulation, fetchAndStoreUser} from "./objects"; + +export function* onFetchLoggedInUser(action) { + try { + const tokenResponse = yield call(performTokenSignIn, action.payload.authToken); + let userId = tokenResponse.userId; + + if (tokenResponse.isNewUser) { + saveAuthLocalStorage({authToken: action.payload.authToken}); + const newUser = yield call(addUser, action.payload); + userId = newUser.id; + } + + yield put(logInSucceeded(Object.assign({userId}, action.payload))); + } catch (error) { + console.log(error); + } +} + +export function* onFetchAuthorizationsOfCurrentUser(action) { + try { + const authorizations = yield call(getAuthorizationsByUser, action.userId); + + for (const authorization of authorizations) { + yield put(addToStore("authorization", authorization)); + + yield fetchAndStoreSimulation(authorization.simulationId); + yield fetchAndStoreUser(authorization.userId); + } + + const authorizationIds = authorizations.map(authorization => ( + [authorization.userId, authorization.simulationId] + )); + + yield put(fetchAuthorizationsOfCurrentUserSucceeded(authorizationIds)); + } catch (error) { + console.log(error); + } +} diff --git a/src/shapes/index.js b/src/shapes/index.js index 1938032e..bea0f1aa 100644 --- a/src/shapes/index.js +++ b/src/shapes/index.js @@ -101,7 +101,7 @@ Shapes.Tile = PropTypes.shape({ positionX: PropTypes.number.isRequired, positionY: PropTypes.number.isRequired, objectId: PropTypes.number, - objectType: PropTypes.number, + objectType: PropTypes.string, rack: Shapes.Rack, coolingItem: Shapes.CoolingItem, psu: Shapes.PSU, @@ -123,6 +123,7 @@ Shapes.Datacenter = PropTypes.shape({ Shapes.Section = PropTypes.shape({ id: PropTypes.number.isRequired, pathId: PropTypes.number.isRequired, + startTick: PropTypes.number.isRequired, datacenterId: PropTypes.number.isRequired, datacenter: Shapes.Datacenter, }); diff --git a/src/store/configureStore.js b/src/store/configureStore.js index ecd804a2..5bbaf811 100644 --- a/src/store/configureStore.js +++ b/src/store/configureStore.js @@ -3,9 +3,9 @@ import persistState from "redux-localstorage"; import {createLogger} from "redux-logger"; import createSagaMiddleware from 'redux-saga'; import thunk from "redux-thunk"; -import rootSaga from "../api/sagas/index"; import {authRedirectMiddleware} from "../auth/index"; import rootReducer from "../reducers/index"; +import rootSaga from "../sagas/index"; const sagaMiddleware = createSagaMiddleware(); const logger = createLogger(); diff --git a/src/store/denormalizer.js b/src/store/denormalizer.js index fbf15430..e6583ae7 100644 --- a/src/store/denormalizer.js +++ b/src/store/denormalizer.js @@ -1,14 +1,28 @@ +const EXCLUDED_IDENTIFIERS = [ + "objectId", + "googleId", +]; + export function denormalize(state, objectType, id) { + return denormalizeWithRecursionCheck(state, objectType, id, undefined); +} + +function denormalizeWithRecursionCheck(state, objectType, id, previousType) { const object = Object.assign({}, state.objects[objectType][id]); for (let prop in object) { - if (!object.hasOwnProperty(prop)) { + if (prop.indexOf(previousType) !== -1) { continue; } - if (prop.endsWith("Id")) { + if (prop.endsWith("Id") && EXCLUDED_IDENTIFIERS.indexOf(prop) === -1) { const propType = prop.replace("Id", ""); - object[propType] = state.objects[propType][object[prop]]; + object[propType] = denormalizeWithRecursionCheck(state, propType, object[prop], objectType); + } + + if (prop.endsWith("Ids")) { + const propType = prop.replace("Ids", ""); + object[propType + "s"] = object[prop].map(id => denormalizeWithRecursionCheck(state, propType, id, objectType)); } } -- cgit v1.2.3