From e5e5d2c65e583493870bc0b62fb185c5e757c13f Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 7 Jul 2021 16:27:49 +0200 Subject: ui: Migrate project APIs to React Query This change updates the OpenDC frontend to use React Query for fetching and mutating project data. Previously, this state was tracked and synchronized via Redux. Migrating to React Query greatly simplifies the state synchronization logic necessary in the frontend. --- opendc-web/opendc-web-ui/src/redux/sagas/objects.js | 7 ------- 1 file changed, 7 deletions(-) (limited to 'opendc-web/opendc-web-ui/src/redux/sagas/objects.js') diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js index e5fd092d..5523dd57 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js @@ -1,13 +1,11 @@ import { call, put, select, getContext } from 'redux-saga/effects' import { addToStore } from '../actions/objects' import { getAllSchedulers } from '../../api/schedulers' -import { getProject } from '../../api/projects' import { getAllTraces } from '../../api/traces' import { getTopology, updateTopology } from '../../api/topologies' import { uuid } from 'uuidv4' export const OBJECT_SELECTORS = { - project: (state) => state.objects.project, user: (state) => state.objects.user, authorization: (state) => state.objects.authorization, portfolio: (state) => state.objects.portfolio, @@ -41,11 +39,6 @@ function* fetchAndStoreObjects(objectType, apiCall) { return objects } -export const fetchAndStoreProject = function* (id) { - const auth = yield getContext('auth') - return yield fetchAndStoreObject('project', id, call(getProject, auth, id)) -} - export const fetchAndStoreTopology = function* (id) { const topologyStore = yield select(OBJECT_SELECTORS['topology']) const roomStore = yield select(OBJECT_SELECTORS['room']) -- cgit v1.2.3 From d28a2f194a75eb86095485ae4f88be349bcc18b6 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 7 Jul 2021 16:36:54 +0200 Subject: ui: Fetch schedulers and traces using React Query This change updates the OpenDC frontend to fetch schedulers and traces using React Query, removing its dependency on Redux. --- opendc-web/opendc-web-ui/src/redux/sagas/objects.js | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) (limited to 'opendc-web/opendc-web-ui/src/redux/sagas/objects.js') diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js index 5523dd57..fe826014 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js @@ -1,7 +1,7 @@ import { call, put, select, getContext } from 'redux-saga/effects' import { addToStore } from '../actions/objects' -import { getAllSchedulers } from '../../api/schedulers' -import { getAllTraces } from '../../api/traces' +import { fetchSchedulers } from '../../api/schedulers' +import { fetchTraces } from '../../api/traces' import { getTopology, updateTopology } from '../../api/topologies' import { uuid } from 'uuidv4' @@ -210,18 +210,3 @@ export const getRackById = function* (id, keepIds) { })), } } - -export const fetchAndStoreAllTraces = function* () { - const auth = yield getContext('auth') - return yield fetchAndStoreObjects('trace', call(getAllTraces, auth)) -} - -export const fetchAndStoreAllSchedulers = function* () { - const auth = yield getContext('auth') - const objects = yield call(getAllSchedulers, auth) - for (let object of objects) { - object._id = object.name - yield put(addToStore('scheduler', object)) - } - return objects -} -- cgit v1.2.3 From 9c8a987556d0fb0cdf0eb67e0c191a8dcc5593b9 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 7 Jul 2021 17:30:15 +0200 Subject: ui: Fetch scenarios and portfolios using React Query --- .../opendc-web-ui/src/redux/sagas/objects.js | 30 ++++++---------------- 1 file changed, 8 insertions(+), 22 deletions(-) (limited to 'opendc-web/opendc-web-ui/src/redux/sagas/objects.js') diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js index fe826014..88f71fe4 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js @@ -1,7 +1,5 @@ import { call, put, select, getContext } from 'redux-saga/effects' import { addToStore } from '../actions/objects' -import { fetchSchedulers } from '../../api/schedulers' -import { fetchTraces } from '../../api/traces' import { getTopology, updateTopology } from '../../api/topologies' import { uuid } from 'uuidv4' @@ -21,24 +19,9 @@ export const OBJECT_SELECTORS = { topology: (state) => state.objects.topology, } -function* fetchAndStoreObject(objectType, id, apiCall) { - const objectStore = yield select(OBJECT_SELECTORS[objectType]) - let object = objectStore[id] - if (!object) { - object = yield apiCall - yield put(addToStore(objectType, object)) - } - return object -} - -function* fetchAndStoreObjects(objectType, apiCall) { - const objects = yield apiCall - for (let object of objects) { - yield put(addToStore(objectType, object)) - } - return objects -} - +/** + * Fetches and normalizes the topology with the specified identifier. + */ export const fetchAndStoreTopology = function* (id) { const topologyStore = yield select(OBJECT_SELECTORS['topology']) const roomStore = yield select(OBJECT_SELECTORS['room']) @@ -135,12 +118,15 @@ const generateIdIfNotPresent = (obj) => { } export const updateTopologyOnServer = function* (id) { - const topology = yield getTopologyAsObject(id, true) + const topology = yield denormalizeTopology(id, true) const auth = yield getContext('auth') yield call(updateTopology, auth, topology) } -export const getTopologyAsObject = function* (id, keepIds) { +/** + * Denormalizes the topology representation in order to be stored on the server. + */ +export const denormalizeTopology = function* (id, keepIds) { const topologyStore = yield select(OBJECT_SELECTORS['topology']) const rooms = yield getAllRooms(topologyStore[id].roomIds, keepIds) return { -- cgit v1.2.3 From 02a2f0f89cb1f39a5f8856bca1971a4e1b12374f Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 7 Jul 2021 20:13:30 +0200 Subject: ui: Use React Query defaults to reduce duplication --- opendc-web/opendc-web-ui/src/redux/sagas/objects.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'opendc-web/opendc-web-ui/src/redux/sagas/objects.js') diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js index 88f71fe4..99082df0 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js @@ -1,6 +1,6 @@ import { call, put, select, getContext } from 'redux-saga/effects' import { addToStore } from '../actions/objects' -import { getTopology, updateTopology } from '../../api/topologies' +import { fetchTopology, updateTopology } from '../../api/topologies' import { uuid } from 'uuidv4' export const OBJECT_SELECTORS = { @@ -32,7 +32,7 @@ export const fetchAndStoreTopology = function* (id) { let topology = topologyStore[id] if (!topology) { - const fullTopology = yield call(getTopology, auth, id) + const fullTopology = yield call(fetchTopology, auth, id) for (let roomIdx in fullTopology.rooms) { const fullRoom = fullTopology.rooms[roomIdx] -- cgit v1.2.3 From 29196842447d841d2e21462adcfc8c2ed1d851ad Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Thu, 8 Jul 2021 13:15:28 +0200 Subject: ui: Simplify normalization of topology This change updates the OpenDC frontend to use the normalizr library for normalizing the user topology. --- .../opendc-web-ui/src/redux/sagas/objects.js | 186 ++------------------- 1 file changed, 12 insertions(+), 174 deletions(-) (limited to 'opendc-web/opendc-web-ui/src/redux/sagas/objects.js') diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js index 99082df0..9b4f8094 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js @@ -1,124 +1,27 @@ import { call, put, select, getContext } from 'redux-saga/effects' -import { addToStore } from '../actions/objects' import { fetchTopology, updateTopology } from '../../api/topologies' -import { uuid } from 'uuidv4' - -export const OBJECT_SELECTORS = { - user: (state) => state.objects.user, - authorization: (state) => state.objects.authorization, - portfolio: (state) => state.objects.portfolio, - scenario: (state) => state.objects.scenario, - 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, - tile: (state) => state.objects.tile, - room: (state) => state.objects.room, - topology: (state) => state.objects.topology, -} +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 topologyStore = yield select(OBJECT_SELECTORS['topology']) - const roomStore = yield select(OBJECT_SELECTORS['room']) - const tileStore = yield select(OBJECT_SELECTORS['tile']) - const rackStore = yield select(OBJECT_SELECTORS['rack']) - const machineStore = yield select(OBJECT_SELECTORS['machine']) const auth = yield getContext('auth') - let topology = topologyStore[id] + let topology = yield select((state) => state.objects.topology[id]) if (!topology) { - const fullTopology = yield call(fetchTopology, auth, id) - - for (let roomIdx in fullTopology.rooms) { - const fullRoom = fullTopology.rooms[roomIdx] - - generateIdIfNotPresent(fullRoom) - - if (!roomStore[fullRoom._id]) { - for (let tileIdx in fullRoom.tiles) { - const fullTile = fullRoom.tiles[tileIdx] - - generateIdIfNotPresent(fullTile) - - if (!tileStore[fullTile._id]) { - if (fullTile.rack) { - const fullRack = fullTile.rack - - generateIdIfNotPresent(fullRack) - - if (!rackStore[fullRack._id]) { - for (let machineIdx in fullRack.machines) { - const fullMachine = fullRack.machines[machineIdx] - - generateIdIfNotPresent(fullMachine) - - if (!machineStore[fullMachine._id]) { - let machine = (({ _id, position, cpus, gpus, memories, storages }) => ({ - _id, - rackId: fullRack._id, - position, - cpuIds: cpus.map((u) => u._id), - gpuIds: gpus.map((u) => u._id), - memoryIds: memories.map((u) => u._id), - storageIds: storages.map((u) => u._id), - }))(fullMachine) - yield put(addToStore('machine', machine)) - } - } - - const filledSlots = new Array(fullRack.capacity).fill(null) - fullRack.machines.forEach( - (machine) => (filledSlots[machine.position - 1] = machine._id) - ) - let rack = (({ _id, name, capacity, powerCapacityW }) => ({ - _id, - name, - capacity, - powerCapacityW, - machineIds: filledSlots, - }))(fullRack) - yield put(addToStore('rack', rack)) - } - } - - let tile = (({ _id, positionX, positionY, rack }) => ({ - _id, - roomId: fullRoom._id, - positionX, - positionY, - rackId: rack ? rack._id : undefined, - }))(fullTile) - yield put(addToStore('tile', tile)) - } - } - - let room = (({ _id, name, tiles }) => ({ _id, name, tileIds: tiles.map((t) => t._id) }))(fullRoom) - yield put(addToStore('room', room)) - } - } - - topology = (({ _id, name, rooms }) => ({ _id, name, roomIds: rooms.map((r) => r._id) }))(fullTopology) - yield put(addToStore('topology', topology)) - - // TODO consider pushing the IDs + const newTopology = yield call(fetchTopology, auth, id) + const { entities } = normalize(newTopology, Topology) + yield put(storeTopology(entities)) } return topology } -const generateIdIfNotPresent = (obj) => { - if (!obj._id) { - obj._id = uuid() - } -} - export const updateTopologyOnServer = function* (id) { - const topology = yield denormalizeTopology(id, true) + const topology = yield denormalizeTopology(id) const auth = yield getContext('auth') yield call(updateTopology, auth, topology) } @@ -126,73 +29,8 @@ export const updateTopologyOnServer = function* (id) { /** * Denormalizes the topology representation in order to be stored on the server. */ -export const denormalizeTopology = function* (id, keepIds) { - const topologyStore = yield select(OBJECT_SELECTORS['topology']) - const rooms = yield getAllRooms(topologyStore[id].roomIds, keepIds) - return { - _id: keepIds ? id : undefined, - name: topologyStore[id].name, - rooms: rooms, - } -} - -export const getAllRooms = function* (roomIds, keepIds) { - const roomStore = yield select(OBJECT_SELECTORS['room']) - - let rooms = [] - - for (let id of roomIds) { - let tiles = yield getAllRoomTiles(roomStore[id], keepIds) - rooms.push({ - _id: keepIds ? id : undefined, - name: roomStore[id].name, - tiles: tiles, - }) - } - return rooms -} - -export const getAllRoomTiles = function* (roomStore, keepIds) { - let tiles = [] - - for (let id of roomStore.tileIds) { - tiles.push(yield getTileById(id, keepIds)) - } - return tiles -} - -export const getTileById = function* (id, keepIds) { - const tileStore = yield select(OBJECT_SELECTORS['tile']) - return { - _id: keepIds ? id : undefined, - positionX: tileStore[id].positionX, - positionY: tileStore[id].positionY, - rack: !tileStore[id].rackId ? undefined : yield getRackById(tileStore[id].rackId, keepIds), - } -} - -export const getRackById = function* (id, keepIds) { - const rackStore = yield select(OBJECT_SELECTORS['rack']) - const machineStore = yield select(OBJECT_SELECTORS['machine']) - const cpuStore = yield select(OBJECT_SELECTORS['cpu']) - const gpuStore = yield select(OBJECT_SELECTORS['gpu']) - const memoryStore = yield select(OBJECT_SELECTORS['memory']) - const storageStore = yield select(OBJECT_SELECTORS['storage']) - - return { - _id: keepIds ? rackStore[id]._id : undefined, - name: rackStore[id].name, - capacity: rackStore[id].capacity, - powerCapacityW: rackStore[id].powerCapacityW, - machines: rackStore[id].machineIds - .filter((m) => m !== null) - .map((machineId) => ({ - _id: keepIds ? machineId : undefined, - position: machineStore[machineId].position, - cpus: machineStore[machineId].cpuIds.map((id) => cpuStore[id]), - gpus: machineStore[machineId].gpuIds.map((id) => gpuStore[id]), - memories: machineStore[machineId].memoryIds.map((id) => memoryStore[id]), - storages: machineStore[machineId].storageIds.map((id) => storageStore[id]), - })), - } +export const denormalizeTopology = function* (id) { + const objects = yield select((state) => state.objects) + const topology = objects.topology[id] + return denormalize(topology, Topology, objects) } -- cgit v1.2.3