summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-ui/src/redux/sagas
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2021-07-22 14:57:21 +0200
committerGitHub <noreply@github.com>2021-07-22 14:57:21 +0200
commitb0c5681b28d1c3c87b7d24d8b8d166f5566e7699 (patch)
tree4f7269996928ea480499e3cbe912b15ba994e43f /opendc-web/opendc-web-ui/src/redux/sagas
parent51c759e74b088d405b63fdb3e374822308d21366 (diff)
parent7f083b47c2e2333819823fd7835332a0f486b626 (diff)
merge: Address technical debt in topology view v2 (#163)
This pull request aims to address some of the technical debt in the topology view of the OpenDC frontend (v2). * Perform Saga mutations through React Query * Add table view for topology view * Extract topology construction out of Sagas * Toggle to Floor Plan on room select
Diffstat (limited to 'opendc-web/opendc-web-ui/src/redux/sagas')
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/index.js53
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/objects.js36
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js18
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/projects.js9
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/topology.js310
5 files changed, 65 insertions, 361 deletions
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..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,52 +1,7 @@
-import { takeEvery } from 'redux-saga/effects'
-import { OPEN_PROJECT_SUCCEEDED } from '../actions/projects'
-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 { onOpenProjectSucceeded } from './projects'
-import {
- onAddMachine,
- onAddRackToTile,
- onAddTile,
- onAddTopology,
- onAddUnit,
- onCancelNewRoomConstruction,
- onDeleteMachine,
- onDeleteRack,
- onDeleteRoom,
- onDeleteTile,
- onDeleteUnit,
- onEditRackName,
- onEditRoomName,
- onStartNewRoomConstruction,
-} from './topology'
-import { ADD_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_PROJECT_SUCCEEDED, onOpenProjectSucceeded)
-
- 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/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/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/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 a5a3be32..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,263 +1,75 @@
-import { call, put, select, getContext } from 'redux-saga/effects'
-import { goDownOneInteractionLevel } from '../actions/interaction-level'
-import {
- addIdToStoreObjectListProp,
- addPropToStoreObject,
- addToStore,
- removeIdFromStoreObjectListProp,
-} from '../actions/objects'
-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 { fetchAndStoreTopology, denormalizeTopology, updateTopologyOnServer } from './objects'
-import { uuid } from 'uuidv4'
-import { addTopology } from '../../api/topologies'
-
-export function* fetchAndStoreAllTopologiesOfProject(projectId, setTopology = false) {
- try {
- const queryClient = yield getContext('queryClient')
- const project = yield call(() => queryClient.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)
- }
-}
+import { QueryObserver, MutationObserver } from 'react-query'
+import { normalize, denormalize } from 'normalizr'
+import { select, put, take, race, getContext, call } from 'redux-saga/effects'
+import { eventChannel } from 'redux-saga'
+import { Topology } from '../../util/topology-schema'
+import { storeTopology, OPEN_TOPOLOGY } from '../actions/topologies'
+
+/**
+ * Update the topology on the server.
+ */
+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* onAddTopology({ projectId, duplicateId, name }) {
- try {
- let topologyToBeCreated
- if (duplicateId) {
- topologyToBeCreated = yield denormalizeTopology(duplicateId)
- topologyToBeCreated = { ...topologyToBeCreated, name }
- delete topologyToBeCreated._id
- } else {
- topologyToBeCreated = { name, rooms: [] }
+ if (!topology.root) {
+ continue
}
- const auth = yield getContext('auth')
- const topology = yield call(addTopology, auth, { ...topologyToBeCreated, projectId })
- yield fetchAndStoreTopology(topology._id)
- yield put(setCurrentTopology(topology._id))
- } catch (error) {
- console.error(error)
+ const denormalizedTopology = denormalize(topology.root, Topology, topology)
+ yield call([mutationObserver, mutationObserver.mutate], denormalizedTopology)
}
}
-export function* onStartNewRoomConstruction() {
- try {
- const topologyId = yield select((state) => state.currentTopologyId)
- const room = {
- _id: uuid(),
- name: 'Room',
- topologyId,
- tiles: [],
+/**
+ * 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('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)
- }
-}
+/**
+ * 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] })
-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,
- }
- yield put(addToStore('tile', tile))
- yield put(addIdToStoreObjectListProp('room', roomId, 'tiles', tile._id))
- yield updateTopologyOnServer(topologyId)
- } catch (error) {
- console.error(error)
- }
-}
+ return eventChannel((emitter) => {
+ const unsubscribe = observer.subscribe((result) => {
+ emitter(result)
+ })
-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)
- }
-}
-
-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)
- }
-}
+ // Update result to make sure we did not miss any query updates
+ // between creating the observer and subscribing to it.
+ observer.updateResult()
-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: [],
- }
- 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
- }
-
- const units = [...machine[unitMapping[unitType]], id]
- yield put(
- addPropToStoreObject('machine', machine._id, {
- [unitMapping[unitType]]: units,
- })
- )
- yield updateTopologyOnServer(topologyId)
- } catch (error) {
- console.error(error)
- }
-}
-
-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)
-
- yield put(
- addPropToStoreObject('machine', machine._id, {
- [unitMapping[unitType]]: unitIds,
- })
- )
- yield updateTopologyOnServer(topologyId)
- } catch (error) {
- console.error(error)
- }
+ return unsubscribe
+ })
}