From 6e3ad713111f35fc58bd2b7f1be5aeeb57eb94a8 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Tue, 20 Jul 2021 14:09:39 +0200 Subject: refactor(ui): Perform Saga mutations through React Query This change updates the OpenDC frontend to perform mutations of the topology done in Sagas through the React Query cache, so that non-Saga parts of the application also have their topology queries updated. --- .../opendc-web-ui/src/redux/sagas/objects.js | 36 ---------------- opendc-web/opendc-web-ui/src/redux/sagas/query.js | 41 ++++++++++++++++++ .../opendc-web-ui/src/redux/sagas/topology.js | 48 ++++++++++++++++++---- 3 files changed, 82 insertions(+), 43 deletions(-) delete mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/objects.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/query.js (limited to 'opendc-web/opendc-web-ui/src/redux/sagas') diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js deleted file mode 100644 index 9b4f8094..00000000 --- a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js +++ /dev/null @@ -1,36 +0,0 @@ -import { call, put, select, getContext } from 'redux-saga/effects' -import { fetchTopology, updateTopology } from '../../api/topologies' -import { Topology } from '../../util/topology-schema' -import { denormalize, normalize } from 'normalizr' -import { storeTopology } from '../actions/topologies' - -/** - * Fetches and normalizes the topology with the specified identifier. - */ -export const fetchAndStoreTopology = function* (id) { - const auth = yield getContext('auth') - - let topology = yield select((state) => state.objects.topology[id]) - if (!topology) { - const newTopology = yield call(fetchTopology, auth, id) - const { entities } = normalize(newTopology, Topology) - yield put(storeTopology(entities)) - } - - return topology -} - -export const updateTopologyOnServer = function* (id) { - const topology = yield denormalizeTopology(id) - const auth = yield getContext('auth') - yield call(updateTopology, auth, topology) -} - -/** - * Denormalizes the topology representation in order to be stored on the server. - */ -export const denormalizeTopology = function* (id) { - const objects = yield select((state) => state.objects) - const topology = objects.topology[id] - return denormalize(topology, Topology, objects) -} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/query.js b/opendc-web/opendc-web-ui/src/redux/sagas/query.js new file mode 100644 index 00000000..787006c7 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/query.js @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { MutationObserver } from 'react-query' +import { getContext, call } from 'redux-saga/effects' + +/** + * Fetch the query with the specified key. + */ +export function* fetchQuery(key, options) { + const queryClient = yield getContext('queryClient') + return yield call([queryClient, queryClient.fetchQuery], key, options) +} + +/** + * Perform a mutation with the specified key. + */ +export function* mutate(key, object, options) { + const queryClient = yield getContext('queryClient') + const mutationObserver = new MutationObserver(queryClient, { mutationKey: key }) + return yield call([mutationObserver, mutationObserver.mutate], object, options) +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js index a5a3be32..2d61643b 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js @@ -1,4 +1,6 @@ -import { call, put, select, getContext } from 'redux-saga/effects' +import { normalize, denormalize } from 'normalizr' +import { put, select } from 'redux-saga/effects' +import { Topology } from '../../util/topology-schema' import { goDownOneInteractionLevel } from '../actions/interaction-level' import { addIdToStoreObjectListProp, @@ -6,6 +8,7 @@ import { addToStore, removeIdFromStoreObjectListProp, } from '../actions/objects' +import { storeTopology } from '../actions/topologies' import { cancelNewRoomConstructionSucceeded, setCurrentTopology, @@ -16,14 +19,15 @@ import { DEFAULT_RACK_SLOT_CAPACITY, MAX_NUM_UNITS_PER_MACHINE, } from '../../components/topologies/map/MapConstants' -import { fetchAndStoreTopology, denormalizeTopology, updateTopologyOnServer } from './objects' import { uuid } from 'uuidv4' -import { addTopology } from '../../api/topologies' +import { fetchQuery, mutate } from './query' +/** + * Fetches all topologies of the project with the specified identifier. + */ export function* fetchAndStoreAllTopologiesOfProject(projectId, setTopology = false) { try { - const queryClient = yield getContext('queryClient') - const project = yield call(() => queryClient.fetchQuery(['projects', projectId])) + const project = yield fetchQuery(['projects', projectId]) for (const id of project.topologyIds) { yield fetchAndStoreTopology(id) @@ -37,6 +41,37 @@ export function* fetchAndStoreAllTopologiesOfProject(projectId, setTopology = fa } } +/** + * Fetches and normalizes the topology with the specified identifier. + */ +export function* fetchAndStoreTopology(id) { + let topology = yield select((state) => state.objects.topology[id]) + if (!topology) { + const newTopology = yield fetchQuery(['topologies', id]) + const { entities } = normalize(newTopology, Topology) + yield put(storeTopology(entities)) + } + + return topology +} + +/** + * Synchronize the topology with the specified identifier with the server. + */ +export function* updateTopologyOnServer(id) { + const topology = yield denormalizeTopology(id) + yield mutate('updateTopology', topology) +} + +/** + * Denormalizes the topology representation in order to be stored on the server. + */ +export function* denormalizeTopology(id) { + const objects = yield select((state) => state.objects) + const topology = objects.topology[id] + return denormalize(topology, Topology, objects) +} + export function* onAddTopology({ projectId, duplicateId, name }) { try { let topologyToBeCreated @@ -48,8 +83,7 @@ export function* onAddTopology({ projectId, duplicateId, name }) { topologyToBeCreated = { name, rooms: [] } } - const auth = yield getContext('auth') - const topology = yield call(addTopology, auth, { ...topologyToBeCreated, projectId }) + const topology = yield mutate('addTopology', { ...topologyToBeCreated, projectId }) yield fetchAndStoreTopology(topology._id) yield put(setCurrentTopology(topology._id)) } catch (error) { -- cgit v1.2.3 From ebab0cc12e293a57cbc58d2dd51b3c9d7cd4ee92 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Tue, 20 Jul 2021 14:55:39 +0200 Subject: fix(ui): Load correct topology view This change fixes an issue where the only the default topology view would be shown to the user. --- opendc-web/opendc-web-ui/src/redux/sagas/index.js | 7 ++--- .../opendc-web-ui/src/redux/sagas/projects.js | 9 ------ .../opendc-web-ui/src/redux/sagas/topology.js | 34 ++++++++-------------- 3 files changed, 15 insertions(+), 35 deletions(-) delete mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/projects.js (limited to 'opendc-web/opendc-web-ui/src/redux/sagas') diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/index.js b/opendc-web/opendc-web-ui/src/redux/sagas/index.js index 318f0afb..9ddc564d 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/index.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/index.js @@ -1,5 +1,4 @@ import { takeEvery } from 'redux-saga/effects' -import { OPEN_PROJECT_SUCCEEDED } from '../actions/projects' import { ADD_TILE, CANCEL_NEW_ROOM_CONSTRUCTION, @@ -9,7 +8,6 @@ import { 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 { onOpenProjectSucceeded } from './projects' import { onAddMachine, onAddRackToTile, @@ -25,13 +23,14 @@ import { onEditRackName, onEditRoomName, onStartNewRoomConstruction, + onOpenTopology, } from './topology' -import { ADD_TOPOLOGY } from '../actions/topologies' +import { ADD_TOPOLOGY, OPEN_TOPOLOGY } from '../actions/topologies' import { onAddPrefab } from './prefabs' import { ADD_PREFAB } from '../actions/prefabs' export default function* rootSaga() { - yield takeEvery(OPEN_PROJECT_SUCCEEDED, onOpenProjectSucceeded) + yield takeEvery(OPEN_TOPOLOGY, onOpenTopology) yield takeEvery(ADD_TOPOLOGY, onAddTopology) yield takeEvery(START_NEW_ROOM_CONSTRUCTION, onStartNewRoomConstruction) diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/projects.js b/opendc-web/opendc-web-ui/src/redux/sagas/projects.js deleted file mode 100644 index 5809d4d2..00000000 --- a/opendc-web/opendc-web-ui/src/redux/sagas/projects.js +++ /dev/null @@ -1,9 +0,0 @@ -import { fetchAndStoreAllTopologiesOfProject } from './topology' - -export function* onOpenProjectSucceeded(action) { - try { - yield fetchAndStoreAllTopologiesOfProject(action.id, true) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js index 2d61643b..fb6f7f0d 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js @@ -22,29 +22,10 @@ import { import { uuid } from 'uuidv4' import { fetchQuery, mutate } from './query' -/** - * Fetches all topologies of the project with the specified identifier. - */ -export function* fetchAndStoreAllTopologiesOfProject(projectId, setTopology = false) { - try { - const project = yield fetchQuery(['projects', projectId]) - - for (const id of project.topologyIds) { - yield fetchAndStoreTopology(id) - } - - if (setTopology) { - yield put(setCurrentTopology(project.topologyIds[0])) - } - } catch (error) { - console.error(error) - } -} - /** * Fetches and normalizes the topology with the specified identifier. */ -export function* fetchAndStoreTopology(id) { +function* fetchAndStoreTopology(id) { let topology = yield select((state) => state.objects.topology[id]) if (!topology) { const newTopology = yield fetchQuery(['topologies', id]) @@ -58,7 +39,7 @@ export function* fetchAndStoreTopology(id) { /** * Synchronize the topology with the specified identifier with the server. */ -export function* updateTopologyOnServer(id) { +function* updateTopologyOnServer(id) { const topology = yield denormalizeTopology(id) yield mutate('updateTopology', topology) } @@ -66,12 +47,21 @@ export function* updateTopologyOnServer(id) { /** * Denormalizes the topology representation in order to be stored on the server. */ -export function* denormalizeTopology(id) { +function* denormalizeTopology(id) { const objects = yield select((state) => state.objects) const topology = objects.topology[id] return denormalize(topology, Topology, objects) } +export function* onOpenTopology({ id }) { + try { + yield fetchAndStoreTopology(id) + yield put(setCurrentTopology(id)) + } catch (error) { + console.error(error) + } +} + export function* onAddTopology({ projectId, duplicateId, name }) { try { let topologyToBeCreated -- cgit v1.2.3 From 54f424a18cc21a52ea518d40893218a07ab55989 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 21 Jul 2021 15:04:22 +0200 Subject: feat(ui): Extract topology construction out of Sagas This change updates the OpenDC frontend to perform the construction of the topology directly in the reducers instead of performing the mutations in Redux Sagas as side effects. This allows us to nicely map actions to mutations in the reducers. --- opendc-web/opendc-web-ui/src/redux/sagas/index.js | 52 +--- .../opendc-web-ui/src/redux/sagas/prefabs.js | 18 -- opendc-web/opendc-web-ui/src/redux/sagas/query.js | 41 --- .../opendc-web-ui/src/redux/sagas/topology.js | 324 ++++----------------- 4 files changed, 60 insertions(+), 375 deletions(-) delete mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js delete mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/query.js (limited to 'opendc-web/opendc-web-ui/src/redux/sagas') diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/index.js b/opendc-web/opendc-web-ui/src/redux/sagas/index.js index 9ddc564d..0fabdb6d 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/index.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/index.js @@ -1,51 +1,7 @@ -import { takeEvery } from 'redux-saga/effects' -import { - ADD_TILE, - CANCEL_NEW_ROOM_CONSTRUCTION, - DELETE_TILE, - 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 { - onAddMachine, - onAddRackToTile, - onAddTile, - onAddTopology, - onAddUnit, - onCancelNewRoomConstruction, - onDeleteMachine, - onDeleteRack, - onDeleteRoom, - onDeleteTile, - onDeleteUnit, - onEditRackName, - onEditRoomName, - onStartNewRoomConstruction, - onOpenTopology, -} from './topology' -import { ADD_TOPOLOGY, OPEN_TOPOLOGY } from '../actions/topologies' -import { onAddPrefab } from './prefabs' -import { ADD_PREFAB } from '../actions/prefabs' +import { fork } from 'redux-saga/effects' +import { watchServer, updateServer } from './topology' export default function* rootSaga() { - yield takeEvery(OPEN_TOPOLOGY, onOpenTopology) - - yield takeEvery(ADD_TOPOLOGY, onAddTopology) - yield takeEvery(START_NEW_ROOM_CONSTRUCTION, onStartNewRoomConstruction) - yield takeEvery(CANCEL_NEW_ROOM_CONSTRUCTION, onCancelNewRoomConstruction) - yield takeEvery(ADD_TILE, onAddTile) - yield takeEvery(DELETE_TILE, onDeleteTile) - yield takeEvery(EDIT_ROOM_NAME, onEditRoomName) - yield takeEvery(DELETE_ROOM, onDeleteRoom) - yield takeEvery(EDIT_RACK_NAME, onEditRackName) - yield takeEvery(DELETE_RACK, onDeleteRack) - yield takeEvery(ADD_RACK_TO_TILE, onAddRackToTile) - yield takeEvery(ADD_MACHINE, onAddMachine) - yield takeEvery(DELETE_MACHINE, onDeleteMachine) - yield takeEvery(ADD_UNIT, onAddUnit) - yield takeEvery(DELETE_UNIT, onDeleteUnit) - - yield takeEvery(ADD_PREFAB, onAddPrefab) + yield fork(watchServer) + yield fork(updateServer) } diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js deleted file mode 100644 index f717d878..00000000 --- a/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js +++ /dev/null @@ -1,18 +0,0 @@ -import { call, put, select, getContext } from 'redux-saga/effects' -import { addToStore } from '../actions/objects' -import { addPrefab } from '../../api/prefabs' -import { Rack } from '../../util/topology-schema' -import { denormalize } from 'normalizr' - -export function* onAddPrefab({ name, tileId }) { - try { - const objects = yield select((state) => state.objects) - const rack = objects.rack[objects.tile[tileId].rack] - const prefabRack = denormalize(rack, Rack, objects) - const auth = yield getContext('auth') - const prefab = yield call(() => addPrefab(auth, { name, rack: prefabRack })) - yield put(addToStore('prefab', prefab)) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/query.js b/opendc-web/opendc-web-ui/src/redux/sagas/query.js deleted file mode 100644 index 787006c7..00000000 --- a/opendc-web/opendc-web-ui/src/redux/sagas/query.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { MutationObserver } from 'react-query' -import { getContext, call } from 'redux-saga/effects' - -/** - * Fetch the query with the specified key. - */ -export function* fetchQuery(key, options) { - const queryClient = yield getContext('queryClient') - return yield call([queryClient, queryClient.fetchQuery], key, options) -} - -/** - * Perform a mutation with the specified key. - */ -export function* mutate(key, object, options) { - const queryClient = yield getContext('queryClient') - const mutationObserver = new MutationObserver(queryClient, { mutationKey: key }) - return yield call([mutationObserver, mutationObserver.mutate], object, options) -} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js index fb6f7f0d..f40cff28 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js @@ -1,287 +1,75 @@ +import { QueryObserver, MutationObserver } from 'react-query' import { normalize, denormalize } from 'normalizr' -import { put, select } from 'redux-saga/effects' +import { select, put, take, race, getContext, call } from 'redux-saga/effects' +import { eventChannel } from 'redux-saga' import { Topology } from '../../util/topology-schema' -import { goDownOneInteractionLevel } from '../actions/interaction-level' -import { - addIdToStoreObjectListProp, - addPropToStoreObject, - addToStore, - removeIdFromStoreObjectListProp, -} from '../actions/objects' -import { storeTopology } from '../actions/topologies' -import { - cancelNewRoomConstructionSucceeded, - setCurrentTopology, - startNewRoomConstructionSucceeded, -} from '../actions/topology/building' -import { - DEFAULT_RACK_POWER_CAPACITY, - DEFAULT_RACK_SLOT_CAPACITY, - MAX_NUM_UNITS_PER_MACHINE, -} from '../../components/topologies/map/MapConstants' -import { uuid } from 'uuidv4' -import { fetchQuery, mutate } from './query' +import { storeTopology, OPEN_TOPOLOGY } from '../actions/topologies' /** - * Fetches and normalizes the topology with the specified identifier. + * Update the topology on the server. */ -function* fetchAndStoreTopology(id) { - let topology = yield select((state) => state.objects.topology[id]) - if (!topology) { - const newTopology = yield fetchQuery(['topologies', id]) - const { entities } = normalize(newTopology, Topology) - yield put(storeTopology(entities)) - } - - return topology -} - -/** - * Synchronize the topology with the specified identifier with the server. - */ -function* updateTopologyOnServer(id) { - const topology = yield denormalizeTopology(id) - yield mutate('updateTopology', topology) -} - -/** - * Denormalizes the topology representation in order to be stored on the server. - */ -function* denormalizeTopology(id) { - const objects = yield select((state) => state.objects) - const topology = objects.topology[id] - return denormalize(topology, Topology, objects) -} - -export function* onOpenTopology({ id }) { - try { - yield fetchAndStoreTopology(id) - yield put(setCurrentTopology(id)) - } catch (error) { - console.error(error) - } -} - -export function* onAddTopology({ projectId, duplicateId, name }) { - try { - let topologyToBeCreated - if (duplicateId) { - topologyToBeCreated = yield denormalizeTopology(duplicateId) - topologyToBeCreated = { ...topologyToBeCreated, name } - delete topologyToBeCreated._id - } else { - topologyToBeCreated = { name, rooms: [] } - } - - const topology = yield mutate('addTopology', { ...topologyToBeCreated, projectId }) - yield fetchAndStoreTopology(topology._id) - yield put(setCurrentTopology(topology._id)) - } catch (error) { - console.error(error) - } -} - -export function* onStartNewRoomConstruction() { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const room = { - _id: uuid(), - name: 'Room', - topologyId, - tiles: [], - } - yield put(addToStore('room', room)) - yield put(addIdToStoreObjectListProp('topology', topologyId, 'rooms', room._id)) - yield updateTopologyOnServer(topologyId) - yield put(startNewRoomConstructionSucceeded(room._id)) - } catch (error) { - console.error(error) - } -} - -export function* onCancelNewRoomConstruction() { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.construction.currentRoomInConstruction) - yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'rooms', roomId)) - // TODO remove room from store, too - yield updateTopologyOnServer(topologyId) - yield put(cancelNewRoomConstructionSucceeded()) - } catch (error) { - console.error(error) - } -} +export function* updateServer() { + const queryClient = yield getContext('queryClient') + const mutationObserver = new MutationObserver(queryClient, { mutationKey: 'updateTopology' }) + + while (true) { + yield take( + (action) => + action.type.startsWith('EDIT') || action.type.startsWith('ADD') || action.type.startsWith('DELETE') + ) + const topology = yield select((state) => state.topology) -export function* onAddTile(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.construction.currentRoomInConstruction) - const tile = { - _id: uuid(), - roomId, - positionX: action.positionX, - positionY: action.positionY, + if (!topology.root) { + continue } - yield put(addToStore('tile', tile)) - yield put(addIdToStoreObjectListProp('room', roomId, 'tiles', tile._id)) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteTile(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.construction.currentRoomInConstruction) - yield put(removeIdFromStoreObjectListProp('room', roomId, 'tiles', action.tileId)) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} -export function* onEditRoomName({ roomId, name }) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - yield put(addPropToStoreObject('room', roomId, { name })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) + const denormalizedTopology = denormalize(topology.root, Topology, topology) + yield call([mutationObserver, mutationObserver.mutate], denormalizedTopology) } } -export function* onDeleteRoom({ roomId }) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - yield put(goDownOneInteractionLevel()) - yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'rooms', roomId)) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onEditRackName({ rackId, name }) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - yield put(addPropToStoreObject('rack', rackId, { name })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteRack({ tileId }) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - yield put(goDownOneInteractionLevel()) - yield put(addPropToStoreObject('tile', tileId, { rack: undefined })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onAddRackToTile({ tileId }) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const rack = { - _id: uuid(), - name: 'Rack', - tileId, - capacity: DEFAULT_RACK_SLOT_CAPACITY, - powerCapacityW: DEFAULT_RACK_POWER_CAPACITY, - machines: [], - } - yield put(addToStore('rack', rack)) - yield put(addPropToStoreObject('tile', tileId, { rack: rack._id })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onAddMachine({ rackId, position }) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const rack = yield select((state) => state.objects.rack[rackId]) - - const machine = { - _id: uuid(), - rackId, - position, - cpus: [], - gpus: [], - memories: [], - storages: [], +/** + * Watch the topology on the server for changes. + */ +export function* watchServer() { + let { id } = yield take(OPEN_TOPOLOGY) + while (true) { + const channel = yield queryObserver(id) + + while (true) { + const [action, response] = yield race([take(OPEN_TOPOLOGY), take(channel)]) + + if (action) { + id = action.id + break + } + + const { isFetched, data } = response + // Only update the topology on the client-side when a new topology was fetched + if (isFetched) { + const { result: topologyId, entities } = normalize(data, Topology) + yield put(storeTopology(entities.topologies[topologyId], entities)) + } } - yield put(addToStore('machine', machine)) - - const machineIds = [...rack.machines, machine._id] - yield put(addPropToStoreObject('rack', rackId, { machines: machineIds })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteMachine({ rackId, position }) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const rack = yield select((state) => state.objects.rack[rackId]) - yield put(goDownOneInteractionLevel()) - yield put( - addPropToStoreObject('rack', rackId, { machines: rack.machines.filter((_, idx) => idx !== position - 1) }) - ) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) } } -const unitMapping = { - cpu: 'cpus', - gpu: 'gpus', - memory: 'memories', - storage: 'storages', -} - -export function* onAddUnit({ machineId, unitType, id }) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const machine = yield select((state) => state.objects.machine[machineId]) - - if (machine[unitMapping[unitType]].length >= MAX_NUM_UNITS_PER_MACHINE) { - return - } +/** + * Observe changes for the topology with the specified identifier. + */ +function* queryObserver(id) { + const queryClient = yield getContext('queryClient') + const observer = new QueryObserver(queryClient, { queryKey: ['topologies', id] }) - const units = [...machine[unitMapping[unitType]], id] - yield put( - addPropToStoreObject('machine', machine._id, { - [unitMapping[unitType]]: units, - }) - ) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} + return eventChannel((emitter) => { + const unsubscribe = observer.subscribe((result) => { + emitter(result) + }) -export function* onDeleteUnit({ machineId, unitType, index }) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const machine = yield select((state) => state.objects.machine[machineId]) - const unitIds = machine[unitMapping[unitType]].slice() - unitIds.splice(index, 1) + // Update result to make sure we did not miss any query updates + // between creating the observer and subscribing to it. + observer.updateResult() - yield put( - addPropToStoreObject('machine', machine._id, { - [unitMapping[unitType]]: unitIds, - }) - ) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } + return unsubscribe + }) } -- cgit v1.2.3