From aa788a3ad18badfac8beaabdaffc88b9e52f9306 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 7 Jul 2021 15:07:11 +0200 Subject: ui: Remove current ids state from Redux This change removes the current active identifiers from the Redux state. Instead, we use the router query to track the active project, portfolio and topology. --- .../opendc-web-ui/src/redux/sagas/portfolios.js | 48 +++++++++++----------- .../opendc-web-ui/src/redux/sagas/projects.js | 2 +- .../opendc-web-ui/src/redux/sagas/scenarios.js | 8 ++-- .../opendc-web-ui/src/redux/sagas/topology.js | 28 +++++-------- 4 files changed, 39 insertions(+), 47 deletions(-) (limited to 'opendc-web/opendc-web-ui/src/redux/sagas') diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js b/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js index 340cb490..48d1ad3e 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js @@ -12,35 +12,37 @@ export function* onOpenPortfolioSucceeded(action) { const project = yield call(getProject, auth, action.projectId) yield put(addToStore('project', project)) yield fetchAndStoreAllTopologiesOfProject(project._id) - yield fetchPortfoliosOfProject() + yield fetchPortfoliosOfProject(project) yield fetchAndStoreAllSchedulers() yield fetchAndStoreAllTraces() - yield watchForPortfolioResults() + yield watchForPortfolioResults(action.portfolioId) } catch (error) { console.error(error) } } -export function* watchForPortfolioResults() { +export function* watchForPortfolioResults(portfolioId) { try { - const currentPortfolioId = yield select((state) => state.currentPortfolioId) - let unfinishedScenarios = yield getCurrentUnfinishedScenarios() + let unfinishedScenarios = yield getCurrentUnfinishedScenarios(portfolioId) while (unfinishedScenarios.length > 0) { yield delay(3000) - yield fetchPortfolioWithScenarios(currentPortfolioId) - unfinishedScenarios = yield getCurrentUnfinishedScenarios() + yield fetchPortfolioWithScenarios(portfolioId) + unfinishedScenarios = yield getCurrentUnfinishedScenarios(portfolioId) } } catch (error) { console.error(error) } } -export function* getCurrentUnfinishedScenarios() { +export function* getCurrentUnfinishedScenarios(portfolioId) { try { - const currentPortfolioId = yield select((state) => state.currentPortfolioId) - const scenarioIds = yield select((state) => state.objects.portfolio[currentPortfolioId].scenarioIds) + if (!portfolioId) { + return [] + } + + const scenarioIds = yield select((state) => state.objects.portfolio[portfolioId].scenarioIds) const scenarioObjects = yield select((state) => state.objects.scenario) const scenarios = scenarioIds.map((s) => scenarioObjects[s]) return scenarios.filter((s) => !s || s.simulation.state === 'QUEUED' || s.simulation.state === 'RUNNING') @@ -49,16 +51,13 @@ export function* getCurrentUnfinishedScenarios() { } } -export function* fetchPortfoliosOfProject() { +export function* fetchPortfoliosOfProject(project) { try { - const currentProjectId = yield select((state) => state.currentProjectId) - const currentProject = yield select((state) => state.objects.project[currentProjectId]) - yield fetchAndStoreAllSchedulers() yield fetchAndStoreAllTraces() - for (let i in currentProject.portfolioIds) { - yield fetchPortfolioWithScenarios(currentProject.portfolioIds[i]) + for (const i in project.portfolioIds) { + yield fetchPortfolioWithScenarios(project.portfolioIds[i]) } } catch (error) { console.error(error) @@ -83,22 +82,22 @@ export function* fetchPortfolioWithScenarios(portfolioId) { export function* onAddPortfolio(action) { try { - const currentProjectId = yield select((state) => state.currentProjectId) + const { projectId } = action const auth = yield getContext('auth') const portfolio = yield call( addPortfolio, auth, - currentProjectId, + projectId, Object.assign({}, action.portfolio, { - projectId: currentProjectId, + projectId: projectId, scenarioIds: [], }) ) yield put(addToStore('portfolio', portfolio)) - const portfolioIds = yield select((state) => state.objects.project[currentProjectId].portfolioIds) + const portfolioIds = yield select((state) => state.objects.project[projectId].portfolioIds) yield put( - addPropToStoreObject('project', currentProjectId, { + addPropToStoreObject('project', projectId, { portfolioIds: portfolioIds.concat([portfolio._id]), }) ) @@ -120,13 +119,14 @@ export function* onUpdatePortfolio(action) { export function* onDeletePortfolio(action) { try { const auth = yield getContext('auth') + const portfolio = yield select((state) => state.objects.portfolio[action.id]) + yield call(deletePortfolio, auth, action.id) - const currentProjectId = yield select((state) => state.currentProjectId) - const portfolioIds = yield select((state) => state.objects.project[currentProjectId].portfolioIds) + const portfolioIds = yield select((state) => state.objects.project[portfolio.projectId].portfolioIds) yield put( - addPropToStoreObject('project', currentProjectId, { + addPropToStoreObject('project', portfolio.projectId, { portfolioIds: portfolioIds.filter((id) => id !== action.id), }) ) diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/projects.js b/opendc-web/opendc-web-ui/src/redux/sagas/projects.js index 506df6ed..0689090a 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/projects.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/projects.js @@ -13,7 +13,7 @@ export function* onOpenProjectSucceeded(action) { yield put(addToStore('project', project)) yield fetchAndStoreAllTopologiesOfProject(action.id, true) - yield fetchPortfoliosOfProject() + yield fetchPortfoliosOfProject(project) yield fetchAndStoreAllSchedulers() yield fetchAndStoreAllTraces() } catch (error) { diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js b/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js index bdb7c45d..b2979636 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js @@ -34,7 +34,7 @@ export function* onAddScenario(action) { scenarioIds: scenarioIds.concat([scenario._id]), }) ) - yield watchForPortfolioResults() + yield watchForPortfolioResults(action.scenario.portfolioId) } catch (error) { console.error(error) } @@ -53,13 +53,13 @@ export function* onUpdateScenario(action) { export function* onDeleteScenario(action) { try { const auth = yield getContext('auth') + const scenario = yield select((state) => state.objects.scenario[action.id]) yield call(deleteScenario, auth, action.id) - const currentPortfolioId = yield select((state) => state.currentPortfolioId) - const scenarioIds = yield select((state) => state.objects.portfolio[currentPortfolioId].scenarioIds) + const scenarioIds = yield select((state) => state.objects.portfolio[scenario.portfolioId].scenarioIds) yield put( - addPropToStoreObject('scenario', currentPortfolioId, { + addPropToStoreObject('scenario', scenario.portfolioId, { scenarioIds: scenarioIds.filter((id) => id !== action.id), }) ) 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 e5fd3d39..4f7bc8db 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js @@ -38,31 +38,23 @@ export function* fetchAndStoreAllTopologiesOfProject(projectId, setTopology = fa export function* onAddTopology(action) { try { - const currentProjectId = yield select((state) => state.currentProjectId) + const { projectId, duplicateId, name } = action let topologyToBeCreated - if (action.duplicateId) { - topologyToBeCreated = yield getTopologyAsObject(action.duplicateId, false) - topologyToBeCreated = Object.assign({}, topologyToBeCreated, { - name: action.name, - }) + if (duplicateId) { + topologyToBeCreated = yield getTopologyAsObject(duplicateId, false) + topologyToBeCreated = { ...topologyToBeCreated, name } } else { topologyToBeCreated = { name: action.name, rooms: [] } } const auth = yield getContext('auth') - const topology = yield call( - addTopology, - auth, - Object.assign({}, topologyToBeCreated, { - projectId: currentProjectId, - }) - ) + const topology = yield call(addTopology, auth, { ...topologyToBeCreated, projectId }) yield fetchAndStoreTopology(topology._id) - const topologyIds = yield select((state) => state.objects.project[currentProjectId].topologyIds) + const topologyIds = yield select((state) => state.objects.project[projectId].topologyIds) yield put( - addPropToStoreObject('project', currentProjectId, { + addPropToStoreObject('project', projectId, { topologyIds: topologyIds.concat([topology._id]), }) ) @@ -74,8 +66,8 @@ export function* onAddTopology(action) { export function* onDeleteTopology(action) { try { - const currentProjectId = yield select((state) => state.currentProjectId) - const topologyIds = yield select((state) => state.objects.project[currentProjectId].topologyIds) + const topology = yield select((state) => state.objects.topologies[action.id]) + const topologyIds = yield select((state) => state.objects.project[topology.projectId].topologyIds) const currentTopologyId = yield select((state) => state.currentTopologyId) if (currentTopologyId === action.id) { yield put(setCurrentTopology(topologyIds.filter((t) => t !== action.id)[0])) @@ -85,7 +77,7 @@ export function* onDeleteTopology(action) { yield call(deleteTopology, auth, action.id) yield put( - addPropToStoreObject('project', currentProjectId, { + addPropToStoreObject('project', topology.projectId, { topologyIds: topologyIds.filter((id) => id !== action.id), }) ) -- cgit v1.2.3 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/index.js | 6 +-- .../opendc-web-ui/src/redux/sagas/objects.js | 7 ---- .../opendc-web-ui/src/redux/sagas/portfolios.js | 29 ++++----------- .../opendc-web-ui/src/redux/sagas/projects.js | 43 +++------------------- .../opendc-web-ui/src/redux/sagas/scenarios.js | 7 +++- .../opendc-web-ui/src/redux/sagas/topology.js | 30 +++++++-------- 6 files changed, 32 insertions(+), 90 deletions(-) (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 a8f44843..939be691 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/index.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/index.js @@ -11,7 +11,7 @@ import { ADD_UNIT, DELETE_MACHINE, DELETE_UNIT } from '../actions/topology/machi 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 { onAddPortfolio, onDeletePortfolio, onOpenPortfolioSucceeded, onUpdatePortfolio } from './portfolios' -import { onFetchProjects, onOpenProjectSucceeded, onProjectAdd, onProjectDelete } from './projects' +import { onOpenProjectSucceeded } from './projects' import { onAddMachine, onAddRackToTile, @@ -36,10 +36,6 @@ import { onAddPrefab } from './prefabs' import { ADD_PREFAB } from '../actions/prefabs' export default function* rootSaga() { - yield takeEvery(FETCH_PROJECTS, onFetchProjects) - yield takeEvery(ADD_PROJECT, onProjectAdd) - yield takeEvery(DELETE_PROJECT, onProjectDelete) - yield takeEvery(OPEN_PROJECT_SUCCEEDED, onOpenProjectSucceeded) yield takeEvery(OPEN_PORTFOLIO_SUCCEEDED, onOpenPortfolioSucceeded) yield takeEvery(OPEN_SCENARIO_SUCCEEDED, onOpenScenarioSucceeded) 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']) diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js b/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js index 48d1ad3e..c32fcdc0 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js @@ -1,7 +1,7 @@ import { call, put, select, delay, getContext } from 'redux-saga/effects' -import { addPropToStoreObject, addToStore } from '../actions/objects' +import { addToStore } from '../actions/objects' import { addPortfolio, deletePortfolio, getPortfolio, updatePortfolio } from '../../api/portfolios' -import { getProject } from '../../api/projects' +import { fetchProject } from '../../api/projects' import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' import { fetchAndStoreAllTopologiesOfProject } from './topology' import { getScenario } from '../../api/scenarios' @@ -9,9 +9,11 @@ import { getScenario } from '../../api/scenarios' export function* onOpenPortfolioSucceeded(action) { try { const auth = yield getContext('auth') - const project = yield call(getProject, auth, action.projectId) - yield put(addToStore('project', project)) - yield fetchAndStoreAllTopologiesOfProject(project._id) + const queryClient = yield getContext('queryClient') + const project = yield call(() => + queryClient.fetchQuery(`projects/${action.projectId}`, () => fetchProject(auth, action.projectId)) + ) + yield fetchAndStoreAllTopologiesOfProject(action.projectId) yield fetchPortfoliosOfProject(project) yield fetchAndStoreAllSchedulers() yield fetchAndStoreAllTraces() @@ -94,13 +96,6 @@ export function* onAddPortfolio(action) { }) ) yield put(addToStore('portfolio', portfolio)) - - const portfolioIds = yield select((state) => state.objects.project[projectId].portfolioIds) - yield put( - addPropToStoreObject('project', projectId, { - portfolioIds: portfolioIds.concat([portfolio._id]), - }) - ) } catch (error) { console.error(error) } @@ -119,17 +114,7 @@ export function* onUpdatePortfolio(action) { export function* onDeletePortfolio(action) { try { const auth = yield getContext('auth') - const portfolio = yield select((state) => state.objects.portfolio[action.id]) - yield call(deletePortfolio, auth, action.id) - - const portfolioIds = yield select((state) => state.objects.project[portfolio.projectId].portfolioIds) - - yield put( - addPropToStoreObject('project', portfolio.projectId, { - portfolioIds: portfolioIds.filter((id) => id !== action.id), - }) - ) } 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 index 0689090a..96a4323c 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/projects.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/projects.js @@ -1,16 +1,16 @@ -import { call, put, getContext } from 'redux-saga/effects' -import { addToStore } from '../actions/objects' -import { addProjectSucceeded, deleteProjectSucceeded, fetchProjectsSucceeded } from '../actions/projects' -import { addProject, deleteProject, getProject, getProjects } from '../../api/projects' +import { call, getContext } from 'redux-saga/effects' import { fetchAndStoreAllTopologiesOfProject } from './topology' import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' import { fetchPortfoliosOfProject } from './portfolios' +import { fetchProject } from '../../api/projects' export function* onOpenProjectSucceeded(action) { try { const auth = yield getContext('auth') - const project = yield call(getProject, auth, action.id) - yield put(addToStore('project', project)) + const queryClient = yield getContext('queryClient') + const project = yield call(() => + queryClient.fetchQuery(`projects/${action.id}`, () => fetchProject(auth, action.id)) + ) yield fetchAndStoreAllTopologiesOfProject(action.id, true) yield fetchPortfoliosOfProject(project) @@ -20,34 +20,3 @@ export function* onOpenProjectSucceeded(action) { console.error(error) } } - -export function* onProjectAdd(action) { - try { - const auth = yield getContext('auth') - const project = yield call(addProject, auth, { name: action.name }) - yield put(addToStore('project', project)) - yield put(addProjectSucceeded(project)) - } catch (error) { - console.error(error) - } -} - -export function* onProjectDelete(action) { - try { - const auth = yield getContext('auth') - yield call(deleteProject, auth, action.id) - yield put(deleteProjectSucceeded(action.id)) - } catch (error) { - console.error(error) - } -} - -export function* onFetchProjects(action) { - try { - const auth = yield getContext('auth') - const projects = yield call(getProjects, auth) - yield put(fetchProjectsSucceeded(projects)) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js b/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js index b2979636..3fe12981 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js @@ -1,6 +1,6 @@ import { call, put, select, getContext } from 'redux-saga/effects' import { addPropToStoreObject, addToStore } from '../actions/objects' -import { getProject } from '../../api/projects' +import { fetchProject } from '../../api/projects' import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' import { fetchAndStoreAllTopologiesOfProject } from './topology' import { addScenario, deleteScenario, updateScenario } from '../../api/scenarios' @@ -9,7 +9,10 @@ import { fetchPortfolioWithScenarios, watchForPortfolioResults } from './portfol export function* onOpenScenarioSucceeded(action) { try { const auth = yield getContext('auth') - const project = yield call(getProject, auth, action.projectId) + const queryClient = yield getContext('queryClient') + const project = yield call(() => + queryClient.fetchQuery(`projects/${action.projectId}`, () => fetchProject(auth, action.projectId)) + ) yield put(addToStore('project', project)) yield fetchAndStoreAllTopologiesOfProject(project._id) yield fetchAndStoreAllSchedulers() 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 4f7bc8db..3f41e1d4 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js @@ -19,10 +19,15 @@ import { import { fetchAndStoreTopology, getTopologyAsObject, updateTopologyOnServer } from './objects' import { uuid } from 'uuidv4' import { addTopology, deleteTopology } from '../../api/topologies' +import { fetchProject } from '../../api/projects' export function* fetchAndStoreAllTopologiesOfProject(projectId, setTopology = false) { try { - const project = yield select((state) => state.objects.project[projectId]) + const auth = yield getContext('auth') + const queryClient = yield getContext('queryClient') + const project = yield call(() => + queryClient.fetchQuery(`projects/${projectId}`, () => fetchProject(auth, projectId)) + ) for (let i in project.topologyIds) { yield fetchAndStoreTopology(project.topologyIds[i]) @@ -51,13 +56,6 @@ export function* onAddTopology(action) { const auth = yield getContext('auth') const topology = yield call(addTopology, auth, { ...topologyToBeCreated, projectId }) yield fetchAndStoreTopology(topology._id) - - const topologyIds = yield select((state) => state.objects.project[projectId].topologyIds) - yield put( - addPropToStoreObject('project', projectId, { - topologyIds: topologyIds.concat([topology._id]), - }) - ) yield put(setCurrentTopology(topology._id)) } catch (error) { console.error(error) @@ -66,21 +64,19 @@ export function* onAddTopology(action) { export function* onDeleteTopology(action) { try { - const topology = yield select((state) => state.objects.topologies[action.id]) - const topologyIds = yield select((state) => state.objects.project[topology.projectId].topologyIds) + const auth = yield getContext('auth') + const queryClient = yield getContext('queryClient') + const project = yield call(() => + queryClient.fetchQuery(`projects/${action.projectId}`, () => fetchProject(auth, action.projectId)) + ) + const topologyIds = project?.topologyIds ?? [] + const currentTopologyId = yield select((state) => state.currentTopologyId) if (currentTopologyId === action.id) { yield put(setCurrentTopology(topologyIds.filter((t) => t !== action.id)[0])) } - const auth = yield getContext('auth') yield call(deleteTopology, auth, action.id) - - yield put( - addPropToStoreObject('project', topology.projectId, { - topologyIds: topologyIds.filter((id) => id !== action.id), - }) - ) } catch (error) { console.error(error) } -- 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 ++----------------- .../opendc-web-ui/src/redux/sagas/portfolios.js | 7 ------- opendc-web/opendc-web-ui/src/redux/sagas/projects.js | 3 --- opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js | 3 --- 4 files changed, 2 insertions(+), 30 deletions(-) (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 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 -} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js b/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js index c32fcdc0..68956225 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js @@ -2,8 +2,6 @@ import { call, put, select, delay, getContext } from 'redux-saga/effects' import { addToStore } from '../actions/objects' import { addPortfolio, deletePortfolio, getPortfolio, updatePortfolio } from '../../api/portfolios' import { fetchProject } from '../../api/projects' -import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' -import { fetchAndStoreAllTopologiesOfProject } from './topology' import { getScenario } from '../../api/scenarios' export function* onOpenPortfolioSucceeded(action) { @@ -15,8 +13,6 @@ export function* onOpenPortfolioSucceeded(action) { ) yield fetchAndStoreAllTopologiesOfProject(action.projectId) yield fetchPortfoliosOfProject(project) - yield fetchAndStoreAllSchedulers() - yield fetchAndStoreAllTraces() yield watchForPortfolioResults(action.portfolioId) } catch (error) { @@ -55,9 +51,6 @@ export function* getCurrentUnfinishedScenarios(portfolioId) { export function* fetchPortfoliosOfProject(project) { try { - yield fetchAndStoreAllSchedulers() - yield fetchAndStoreAllTraces() - for (const i in project.portfolioIds) { yield fetchPortfolioWithScenarios(project.portfolioIds[i]) } diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/projects.js b/opendc-web/opendc-web-ui/src/redux/sagas/projects.js index 96a4323c..6dc3c682 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/projects.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/projects.js @@ -1,6 +1,5 @@ import { call, getContext } from 'redux-saga/effects' import { fetchAndStoreAllTopologiesOfProject } from './topology' -import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' import { fetchPortfoliosOfProject } from './portfolios' import { fetchProject } from '../../api/projects' @@ -14,8 +13,6 @@ export function* onOpenProjectSucceeded(action) { yield fetchAndStoreAllTopologiesOfProject(action.id, true) yield fetchPortfoliosOfProject(project) - yield fetchAndStoreAllSchedulers() - yield fetchAndStoreAllTraces() } catch (error) { console.error(error) } diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js b/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js index 3fe12981..10ab3547 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js @@ -1,7 +1,6 @@ import { call, put, select, getContext } from 'redux-saga/effects' import { addPropToStoreObject, addToStore } from '../actions/objects' import { fetchProject } from '../../api/projects' -import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' import { fetchAndStoreAllTopologiesOfProject } from './topology' import { addScenario, deleteScenario, updateScenario } from '../../api/scenarios' import { fetchPortfolioWithScenarios, watchForPortfolioResults } from './portfolios' @@ -15,8 +14,6 @@ export function* onOpenScenarioSucceeded(action) { ) yield put(addToStore('project', project)) yield fetchAndStoreAllTopologiesOfProject(project._id) - yield fetchAndStoreAllSchedulers() - yield fetchAndStoreAllTraces() yield fetchPortfolioWithScenarios(action.portfolioId) // TODO Fetch scenario-specific metrics -- 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/opendc-web-ui/src/redux/sagas/index.js | 16 +-- .../opendc-web-ui/src/redux/sagas/objects.js | 30 ++---- .../opendc-web-ui/src/redux/sagas/portfolios.js | 114 --------------------- .../opendc-web-ui/src/redux/sagas/projects.js | 10 -- .../opendc-web-ui/src/redux/sagas/scenarios.js | 69 ------------- .../opendc-web-ui/src/redux/sagas/topology.js | 8 +- 6 files changed, 13 insertions(+), 234 deletions(-) delete mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js delete mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/scenarios.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 939be691..74d9efb6 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/index.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/index.js @@ -1,6 +1,5 @@ import { takeEvery } from 'redux-saga/effects' -import { ADD_PORTFOLIO, DELETE_PORTFOLIO, OPEN_PORTFOLIO_SUCCEEDED, UPDATE_PORTFOLIO } from '../actions/portfolios' -import { ADD_PROJECT, DELETE_PROJECT, FETCH_PROJECTS, OPEN_PROJECT_SUCCEEDED } from '../actions/projects' +import { OPEN_PROJECT_SUCCEEDED } from '../actions/projects' import { ADD_TILE, CANCEL_NEW_ROOM_CONSTRUCTION, @@ -10,7 +9,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 { onAddPortfolio, onDeletePortfolio, onOpenPortfolioSucceeded, onUpdatePortfolio } from './portfolios' import { onOpenProjectSucceeded } from './projects' import { onAddMachine, @@ -30,15 +28,11 @@ import { onStartNewRoomConstruction, } from './topology' import { ADD_TOPOLOGY, DELETE_TOPOLOGY } from '../actions/topologies' -import { ADD_SCENARIO, DELETE_SCENARIO, OPEN_SCENARIO_SUCCEEDED, UPDATE_SCENARIO } from '../actions/scenarios' -import { onAddScenario, onDeleteScenario, onOpenScenarioSucceeded, onUpdateScenario } from './scenarios' import { onAddPrefab } from './prefabs' import { ADD_PREFAB } from '../actions/prefabs' export default function* rootSaga() { yield takeEvery(OPEN_PROJECT_SUCCEEDED, onOpenProjectSucceeded) - yield takeEvery(OPEN_PORTFOLIO_SUCCEEDED, onOpenPortfolioSucceeded) - yield takeEvery(OPEN_SCENARIO_SUCCEEDED, onOpenScenarioSucceeded) yield takeEvery(ADD_TOPOLOGY, onAddTopology) yield takeEvery(DELETE_TOPOLOGY, onDeleteTopology) @@ -56,13 +50,5 @@ export default function* rootSaga() { yield takeEvery(ADD_UNIT, onAddUnit) yield takeEvery(DELETE_UNIT, onDeleteUnit) - yield takeEvery(ADD_PORTFOLIO, onAddPortfolio) - yield takeEvery(UPDATE_PORTFOLIO, onUpdatePortfolio) - yield takeEvery(DELETE_PORTFOLIO, onDeletePortfolio) - - yield takeEvery(ADD_SCENARIO, onAddScenario) - yield takeEvery(UPDATE_SCENARIO, onUpdateScenario) - yield takeEvery(DELETE_SCENARIO, onDeleteScenario) - yield takeEvery(ADD_PREFAB, onAddPrefab) } 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 { diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js b/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js deleted file mode 100644 index 68956225..00000000 --- a/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js +++ /dev/null @@ -1,114 +0,0 @@ -import { call, put, select, delay, getContext } from 'redux-saga/effects' -import { addToStore } from '../actions/objects' -import { addPortfolio, deletePortfolio, getPortfolio, updatePortfolio } from '../../api/portfolios' -import { fetchProject } from '../../api/projects' -import { getScenario } from '../../api/scenarios' - -export function* onOpenPortfolioSucceeded(action) { - try { - const auth = yield getContext('auth') - const queryClient = yield getContext('queryClient') - const project = yield call(() => - queryClient.fetchQuery(`projects/${action.projectId}`, () => fetchProject(auth, action.projectId)) - ) - yield fetchAndStoreAllTopologiesOfProject(action.projectId) - yield fetchPortfoliosOfProject(project) - - yield watchForPortfolioResults(action.portfolioId) - } catch (error) { - console.error(error) - } -} - -export function* watchForPortfolioResults(portfolioId) { - try { - let unfinishedScenarios = yield getCurrentUnfinishedScenarios(portfolioId) - - while (unfinishedScenarios.length > 0) { - yield delay(3000) - yield fetchPortfolioWithScenarios(portfolioId) - unfinishedScenarios = yield getCurrentUnfinishedScenarios(portfolioId) - } - } catch (error) { - console.error(error) - } -} - -export function* getCurrentUnfinishedScenarios(portfolioId) { - try { - if (!portfolioId) { - return [] - } - - const scenarioIds = yield select((state) => state.objects.portfolio[portfolioId].scenarioIds) - const scenarioObjects = yield select((state) => state.objects.scenario) - const scenarios = scenarioIds.map((s) => scenarioObjects[s]) - return scenarios.filter((s) => !s || s.simulation.state === 'QUEUED' || s.simulation.state === 'RUNNING') - } catch (error) { - console.error(error) - } -} - -export function* fetchPortfoliosOfProject(project) { - try { - for (const i in project.portfolioIds) { - yield fetchPortfolioWithScenarios(project.portfolioIds[i]) - } - } catch (error) { - console.error(error) - } -} - -export function* fetchPortfolioWithScenarios(portfolioId) { - try { - const auth = yield getContext('auth') - const portfolio = yield call(getPortfolio, auth, portfolioId) - yield put(addToStore('portfolio', portfolio)) - - for (let i in portfolio.scenarioIds) { - const scenario = yield call(getScenario, auth, portfolio.scenarioIds[i]) - yield put(addToStore('scenario', scenario)) - } - return portfolio - } catch (error) { - console.error(error) - } -} - -export function* onAddPortfolio(action) { - try { - const { projectId } = action - const auth = yield getContext('auth') - const portfolio = yield call( - addPortfolio, - auth, - projectId, - Object.assign({}, action.portfolio, { - projectId: projectId, - scenarioIds: [], - }) - ) - yield put(addToStore('portfolio', portfolio)) - } catch (error) { - console.error(error) - } -} - -export function* onUpdatePortfolio(action) { - try { - const auth = yield getContext('auth') - const portfolio = yield call(updatePortfolio, auth, action.portfolio._id, action.portfolio) - yield put(addToStore('portfolio', portfolio)) - } catch (error) { - console.error(error) - } -} - -export function* onDeletePortfolio(action) { - try { - const auth = yield getContext('auth') - yield call(deletePortfolio, auth, action.id) - } 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 index 6dc3c682..5809d4d2 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/projects.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/projects.js @@ -1,18 +1,8 @@ -import { call, getContext } from 'redux-saga/effects' import { fetchAndStoreAllTopologiesOfProject } from './topology' -import { fetchPortfoliosOfProject } from './portfolios' -import { fetchProject } from '../../api/projects' export function* onOpenProjectSucceeded(action) { try { - const auth = yield getContext('auth') - const queryClient = yield getContext('queryClient') - const project = yield call(() => - queryClient.fetchQuery(`projects/${action.id}`, () => fetchProject(auth, action.id)) - ) - yield fetchAndStoreAllTopologiesOfProject(action.id, true) - yield fetchPortfoliosOfProject(project) } catch (error) { console.error(error) } diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js b/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js deleted file mode 100644 index 10ab3547..00000000 --- a/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js +++ /dev/null @@ -1,69 +0,0 @@ -import { call, put, select, getContext } from 'redux-saga/effects' -import { addPropToStoreObject, addToStore } from '../actions/objects' -import { fetchProject } from '../../api/projects' -import { fetchAndStoreAllTopologiesOfProject } from './topology' -import { addScenario, deleteScenario, updateScenario } from '../../api/scenarios' -import { fetchPortfolioWithScenarios, watchForPortfolioResults } from './portfolios' - -export function* onOpenScenarioSucceeded(action) { - try { - const auth = yield getContext('auth') - const queryClient = yield getContext('queryClient') - const project = yield call(() => - queryClient.fetchQuery(`projects/${action.projectId}`, () => fetchProject(auth, action.projectId)) - ) - yield put(addToStore('project', project)) - yield fetchAndStoreAllTopologiesOfProject(project._id) - yield fetchPortfolioWithScenarios(action.portfolioId) - - // TODO Fetch scenario-specific metrics - } catch (error) { - console.error(error) - } -} - -export function* onAddScenario(action) { - try { - const auth = yield getContext('auth') - const scenario = yield call(addScenario, auth, action.scenario.portfolioId, action.scenario) - yield put(addToStore('scenario', scenario)) - - const scenarioIds = yield select((state) => state.objects.portfolio[action.scenario.portfolioId].scenarioIds) - yield put( - addPropToStoreObject('portfolio', action.scenario.portfolioId, { - scenarioIds: scenarioIds.concat([scenario._id]), - }) - ) - yield watchForPortfolioResults(action.scenario.portfolioId) - } catch (error) { - console.error(error) - } -} - -export function* onUpdateScenario(action) { - try { - const auth = yield getContext('auth') - const scenario = yield call(updateScenario, auth, action.scenario._id, action.scenario) - yield put(addToStore('scenario', scenario)) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteScenario(action) { - try { - const auth = yield getContext('auth') - const scenario = yield select((state) => state.objects.scenario[action.id]) - yield call(deleteScenario, auth, action.id) - - const scenarioIds = yield select((state) => state.objects.portfolio[scenario.portfolioId].scenarioIds) - - yield put( - addPropToStoreObject('scenario', scenario.portfolioId, { - scenarioIds: scenarioIds.filter((id) => id !== action.id), - }) - ) - } 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 3f41e1d4..efa125c6 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js @@ -16,7 +16,7 @@ import { DEFAULT_RACK_SLOT_CAPACITY, MAX_NUM_UNITS_PER_MACHINE, } from '../../components/app/map/MapConstants' -import { fetchAndStoreTopology, getTopologyAsObject, updateTopologyOnServer } from './objects' +import { fetchAndStoreTopology, denormalizeTopology, updateTopologyOnServer } from './objects' import { uuid } from 'uuidv4' import { addTopology, deleteTopology } from '../../api/topologies' import { fetchProject } from '../../api/projects' @@ -26,7 +26,7 @@ export function* fetchAndStoreAllTopologiesOfProject(projectId, setTopology = fa const auth = yield getContext('auth') const queryClient = yield getContext('queryClient') const project = yield call(() => - queryClient.fetchQuery(`projects/${projectId}`, () => fetchProject(auth, projectId)) + queryClient.fetchQuery(['projects', projectId], () => fetchProject(auth, projectId)) ) for (let i in project.topologyIds) { @@ -47,7 +47,7 @@ export function* onAddTopology(action) { let topologyToBeCreated if (duplicateId) { - topologyToBeCreated = yield getTopologyAsObject(duplicateId, false) + topologyToBeCreated = yield denormalizeTopology(duplicateId, false) topologyToBeCreated = { ...topologyToBeCreated, name } } else { topologyToBeCreated = { name: action.name, rooms: [] } @@ -67,7 +67,7 @@ export function* onDeleteTopology(action) { const auth = yield getContext('auth') const queryClient = yield getContext('queryClient') const project = yield call(() => - queryClient.fetchQuery(`projects/${action.projectId}`, () => fetchProject(auth, action.projectId)) + queryClient.fetchQuery(['projects', action.projectId], () => fetchProject(auth, action.projectId)) ) const topologyIds = project?.topologyIds ?? [] -- 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/index.js | 4 +--- .../opendc-web-ui/src/redux/sagas/objects.js | 4 ++-- .../opendc-web-ui/src/redux/sagas/topology.js | 28 ++-------------------- 3 files changed, 5 insertions(+), 31 deletions(-) (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 74d9efb6..318f0afb 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/index.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/index.js @@ -21,13 +21,12 @@ import { onDeleteRack, onDeleteRoom, onDeleteTile, - onDeleteTopology, onDeleteUnit, onEditRackName, onEditRoomName, onStartNewRoomConstruction, } from './topology' -import { ADD_TOPOLOGY, DELETE_TOPOLOGY } from '../actions/topologies' +import { ADD_TOPOLOGY } from '../actions/topologies' import { onAddPrefab } from './prefabs' import { ADD_PREFAB } from '../actions/prefabs' @@ -35,7 +34,6 @@ export default function* rootSaga() { yield takeEvery(OPEN_PROJECT_SUCCEEDED, onOpenProjectSucceeded) yield takeEvery(ADD_TOPOLOGY, onAddTopology) - yield takeEvery(DELETE_TOPOLOGY, onDeleteTopology) yield takeEvery(START_NEW_ROOM_CONSTRUCTION, onStartNewRoomConstruction) yield takeEvery(CANCEL_NEW_ROOM_CONSTRUCTION, onCancelNewRoomConstruction) yield takeEvery(ADD_TILE, onAddTile) 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] 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 efa125c6..0ed40131 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js @@ -18,16 +18,12 @@ import { } from '../../components/app/map/MapConstants' import { fetchAndStoreTopology, denormalizeTopology, updateTopologyOnServer } from './objects' import { uuid } from 'uuidv4' -import { addTopology, deleteTopology } from '../../api/topologies' -import { fetchProject } from '../../api/projects' +import { addTopology } from '../../api/topologies' export function* fetchAndStoreAllTopologiesOfProject(projectId, setTopology = false) { try { - const auth = yield getContext('auth') const queryClient = yield getContext('queryClient') - const project = yield call(() => - queryClient.fetchQuery(['projects', projectId], () => fetchProject(auth, projectId)) - ) + const project = yield call(() => queryClient.fetchQuery(['projects', projectId])) for (let i in project.topologyIds) { yield fetchAndStoreTopology(project.topologyIds[i]) @@ -62,26 +58,6 @@ export function* onAddTopology(action) { } } -export function* onDeleteTopology(action) { - try { - const auth = yield getContext('auth') - const queryClient = yield getContext('queryClient') - const project = yield call(() => - queryClient.fetchQuery(['projects', action.projectId], () => fetchProject(auth, action.projectId)) - ) - const topologyIds = project?.topologyIds ?? [] - - const currentTopologyId = yield select((state) => state.currentTopologyId) - if (currentTopologyId === action.id) { - yield put(setCurrentTopology(topologyIds.filter((t) => t !== action.id)[0])) - } - - yield call(deleteTopology, auth, action.id) - } catch (error) { - console.error(error) - } -} - export function* onStartNewRoomConstruction() { try { const topologyId = yield select((state) => state.currentTopologyId) -- 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 ++------------------- .../opendc-web-ui/src/redux/sagas/prefabs.js | 11 +- .../opendc-web-ui/src/redux/sagas/topology.js | 74 ++++---- 3 files changed, 58 insertions(+), 213 deletions(-) (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 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) } diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js index ec679391..91b03bf6 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js @@ -1,14 +1,17 @@ import { call, put, select, getContext } from 'redux-saga/effects' import { addToStore } from '../actions/objects' import { addPrefab } from '../../api/prefabs' -import { getRackById } from './objects' +import { Rack } from '../../util/topology-schema' +import { denormalize } from 'normalizr' export function* onAddPrefab(action) { try { - const currentRackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) - const currentRackJson = yield getRackById(currentRackId, false) + const interactionLevel = yield select((state) => state.interactionLevel) + const objects = yield select((state) => state.objects) + const rack = objects.rack[objects.tile[interactionLevel.tileId].rack] + const prefabRack = denormalize(rack, Rack, objects) const auth = yield getContext('auth') - const prefab = yield call(addPrefab, auth, { name: action.name, rack: currentRackJson }) + const prefab = yield call(() => addPrefab(auth, { name: action.name, rack: prefabRack })) yield put(addToStore('prefab', prefab)) } 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 0ed40131..5d9154fd 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js @@ -25,8 +25,8 @@ export function* fetchAndStoreAllTopologiesOfProject(projectId, setTopology = fa const queryClient = yield getContext('queryClient') const project = yield call(() => queryClient.fetchQuery(['projects', projectId])) - for (let i in project.topologyIds) { - yield fetchAndStoreTopology(project.topologyIds[i]) + for (const id of project.topologyIds) { + yield fetchAndStoreTopology(id) } if (setTopology) { @@ -43,8 +43,9 @@ export function* onAddTopology(action) { let topologyToBeCreated if (duplicateId) { - topologyToBeCreated = yield denormalizeTopology(duplicateId, false) + topologyToBeCreated = yield denormalizeTopology(duplicateId) topologyToBeCreated = { ...topologyToBeCreated, name } + delete topologyToBeCreated._id } else { topologyToBeCreated = { name: action.name, rooms: [] } } @@ -65,10 +66,10 @@ export function* onStartNewRoomConstruction() { _id: uuid(), name: 'Room', topologyId, - tileIds: [], + tiles: [], } yield put(addToStore('room', room)) - yield put(addIdToStoreObjectListProp('topology', topologyId, 'roomIds', room._id)) + yield put(addIdToStoreObjectListProp('topology', topologyId, 'rooms', room._id)) yield updateTopologyOnServer(topologyId) yield put(startNewRoomConstructionSucceeded(room._id)) } catch (error) { @@ -80,7 +81,7 @@ export function* onCancelNewRoomConstruction() { try { const topologyId = yield select((state) => state.currentTopologyId) const roomId = yield select((state) => state.construction.currentRoomInConstruction) - yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'roomIds', roomId)) + yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'rooms', roomId)) // TODO remove room from store, too yield updateTopologyOnServer(topologyId) yield put(cancelNewRoomConstructionSucceeded()) @@ -100,7 +101,7 @@ export function* onAddTile(action) { positionY: action.positionY, } yield put(addToStore('tile', tile)) - yield put(addIdToStoreObjectListProp('room', roomId, 'tileIds', tile._id)) + yield put(addIdToStoreObjectListProp('room', roomId, 'tiles', tile._id)) yield updateTopologyOnServer(topologyId) } catch (error) { console.error(error) @@ -111,7 +112,7 @@ 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, 'tileIds', action.tileId)) + yield put(removeIdFromStoreObjectListProp('room', roomId, 'tiles', action.tileId)) yield updateTopologyOnServer(topologyId) } catch (error) { console.error(error) @@ -136,7 +137,7 @@ export function* onDeleteRoom() { const topologyId = yield select((state) => state.currentTopologyId) const roomId = yield select((state) => state.interactionLevel.roomId) yield put(goDownOneInteractionLevel()) - yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'roomIds', roomId)) + yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'rooms', roomId)) yield updateTopologyOnServer(topologyId) } catch (error) { console.error(error) @@ -146,7 +147,7 @@ export function* onDeleteRoom() { export function* onEditRackName(action) { try { const topologyId = yield select((state) => state.currentTopologyId) - const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) + const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rack) const rack = Object.assign({}, yield select((state) => state.objects.rack[rackId])) rack.name = action.name yield put(addPropToStoreObject('rack', rackId, { name: action.name })) @@ -161,7 +162,7 @@ export function* onDeleteRack() { const topologyId = yield select((state) => state.currentTopologyId) const tileId = yield select((state) => state.interactionLevel.tileId) yield put(goDownOneInteractionLevel()) - yield put(addPropToStoreObject('tile', tileId, { rackId: undefined })) + yield put(addPropToStoreObject('tile', tileId, { rack: undefined })) yield updateTopologyOnServer(topologyId) } catch (error) { console.error(error) @@ -176,10 +177,10 @@ export function* onAddRackToTile(action) { name: 'Rack', capacity: DEFAULT_RACK_SLOT_CAPACITY, powerCapacityW: DEFAULT_RACK_POWER_CAPACITY, + machines: [], } - rack.machineIds = new Array(rack.capacity).fill(null) yield put(addToStore('rack', rack)) - yield put(addPropToStoreObject('tile', action.tileId, { rackId: rack._id })) + yield put(addPropToStoreObject('tile', action.tileId, { rack: rack._id })) yield updateTopologyOnServer(topologyId) } catch (error) { console.error(error) @@ -189,23 +190,21 @@ export function* onAddRackToTile(action) { export function* onAddMachine(action) { try { const topologyId = yield select((state) => state.currentTopologyId) - const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) + const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rack) const rack = yield select((state) => state.objects.rack[rackId]) const machine = { _id: uuid(), - rackId, position: action.position, - cpuIds: [], - gpuIds: [], - memoryIds: [], - storageIds: [], + cpus: [], + gpus: [], + memories: [], + storages: [], } yield put(addToStore('machine', machine)) - const machineIds = [...rack.machineIds] - machineIds[machine.position - 1] = machine._id - yield put(addPropToStoreObject('rack', rackId, { machineIds })) + const machineIds = [...rack.machines, machine._id] + yield put(addPropToStoreObject('rack', rackId, { machines: machineIds })) yield updateTopologyOnServer(topologyId) } catch (error) { console.error(error) @@ -217,35 +216,41 @@ export function* onDeleteMachine() { const topologyId = yield select((state) => state.currentTopologyId) const tileId = yield select((state) => state.interactionLevel.tileId) const position = yield select((state) => state.interactionLevel.position) - const rack = yield select((state) => state.objects.rack[state.objects.tile[tileId].rackId]) - const machineIds = [...rack.machineIds] - machineIds[position - 1] = null + const rack = yield select((state) => state.objects.rack[state.objects.tile[tileId].rack]) yield put(goDownOneInteractionLevel()) - yield put(addPropToStoreObject('rack', rack._id, { machineIds })) + yield put( + addPropToStoreObject('rack', rack._id, { 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(action) { try { const topologyId = yield select((state) => state.currentTopologyId) const tileId = yield select((state) => state.interactionLevel.tileId) const position = yield select((state) => state.interactionLevel.position) const machine = yield select( - (state) => - state.objects.machine[state.objects.rack[state.objects.tile[tileId].rackId].machineIds[position - 1]] + (state) => state.objects.machine[state.objects.rack[state.objects.tile[tileId].rack].machines[position - 1]] ) - if (machine[action.unitType + 'Ids'].length >= MAX_NUM_UNITS_PER_MACHINE) { + if (machine[unitMapping[action.unitType]].length >= MAX_NUM_UNITS_PER_MACHINE) { return } - const units = [...machine[action.unitType + 'Ids'], action.id] + const units = [...machine[unitMapping[action.unitType]], action.id] yield put( addPropToStoreObject('machine', machine._id, { - [action.unitType + 'Ids']: units, + [unitMapping[action.unitType]]: units, }) ) yield updateTopologyOnServer(topologyId) @@ -260,15 +265,14 @@ export function* onDeleteUnit(action) { const tileId = yield select((state) => state.interactionLevel.tileId) const position = yield select((state) => state.interactionLevel.position) const machine = yield select( - (state) => - state.objects.machine[state.objects.rack[state.objects.tile[tileId].rackId].machineIds[position - 1]] + (state) => state.objects.machine[state.objects.rack[state.objects.tile[tileId].rack].machines[position - 1]] ) - const unitIds = machine[action.unitType + 'Ids'].slice() + const unitIds = machine[unitMapping[action.unitType]].slice() unitIds.splice(action.index, 1) yield put( addPropToStoreObject('machine', machine._id, { - [action.unitType + 'Ids']: unitIds, + [unitMapping[action.unitType]]: unitIds, }) ) yield updateTopologyOnServer(topologyId) -- cgit v1.2.3