diff options
Diffstat (limited to 'opendc-web/opendc-web-ui')
26 files changed, 181 insertions, 205 deletions
diff --git a/opendc-web/opendc-web-ui/package.json b/opendc-web/opendc-web-ui/package.json index cbeef049..e9570879 100644 --- a/opendc-web/opendc-web-ui/package.json +++ b/opendc-web/opendc-web-ui/package.json @@ -38,6 +38,7 @@ "react-dom": "^17.0.2", "react-hotkeys": "^2.0.0", "react-konva": "~17.0.2-0", + "react-query": "^3.18.1", "react-redux": "~7.2.0", "reactstrap": "^8.9.0", "recharts": "~2.0.9", diff --git a/opendc-web/opendc-web-ui/src/api/projects.js b/opendc-web/opendc-web-ui/src/api/projects.js index 93052080..4123b371 100644 --- a/opendc-web/opendc-web-ui/src/api/projects.js +++ b/opendc-web/opendc-web-ui/src/api/projects.js @@ -22,11 +22,11 @@ import { request } from './index' -export function getProjects(auth) { +export function fetchProjects(auth) { return request(auth, `projects/`) } -export function getProject(auth, projectId) { +export function fetchProject(auth, projectId) { return request(auth, `projects/${projectId}`) } diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js index b948b747..d61ff24e 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js @@ -25,7 +25,7 @@ function PortfolioListComponent({ </Button> </h2> - {portfolios.map((portfolio, idx) => ( + {portfolios.map((portfolio) => ( <div key={portfolio._id}> <Row className="row mb-1"> <Col diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/PortfolioListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/PortfolioListContainer.js index b5bade98..1b539b8f 100644 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/PortfolioListContainer.js +++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/PortfolioListContainer.js @@ -6,11 +6,12 @@ import { addPortfolio, deletePortfolio } from '../../../../redux/actions/portfol import { getState } from '../../../../util/state-utils' import { setCurrentTopology } from '../../../../redux/actions/topology/building' import NewPortfolioModalComponent from '../../../../components/modals/custom-components/NewPortfolioModalComponent' -import { usePortfolios } from '../../../../data/project' +import { useActivePortfolioId, useActiveProjectId, usePortfolios, useProject } from '../../../../data/project' const PortfolioListContainer = () => { const router = useRouter() const { project: currentProjectId, portfolio: currentPortfolioId } = router.query + const { data: currentProject } = useProject(currentProjectId) const portfolios = usePortfolios(currentProjectId) const dispatch = useDispatch() @@ -24,7 +25,7 @@ const PortfolioListContainer = () => { if (id) { const state = await getState(dispatch) dispatch(deletePortfolio(id)) - dispatch(setCurrentTopology(state.objects.project[currentProjectId].topologyIds[0])) + dispatch(setCurrentTopology(currentProject.topologyIds[0])) await router.push(`/projects/${currentProjectId}`) } }, diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js index 0eb61026..c474c56e 100644 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js +++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js @@ -7,11 +7,8 @@ import NewScenarioModalComponent from '../../../../components/modals/custom-comp import { useProjectTopologies } from '../../../../data/topology' import { useScenarios } from '../../../../data/project' import { useSchedulers, useTraces } from '../../../../data/experiments' -import { useRouter } from 'next/router' const ScenarioListContainer = ({ portfolioId }) => { - const router = useRouter() - const { project: currentProjectId } = router.query const scenarios = useScenarios(portfolioId) const topologies = useProjectTopologies() const traces = useTraces() @@ -23,7 +20,6 @@ const ScenarioListContainer = ({ portfolioId }) => { const onNewScenario = (currentPortfolioId) => { setVisible(true) } - const onChooseScenario = (portfolioId, scenarioId) => {} const onDeleteScenario = (id) => { if (id) { dispatch(deleteScenario(id)) @@ -67,7 +63,7 @@ const ScenarioListContainer = ({ portfolioId }) => { } ScenarioListContainer.propTypes = { - portfolioId: PropTypes.string.isRequired, + portfolioId: PropTypes.string, } export default ScenarioListContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js index 69367b5f..55f8bd00 100644 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js +++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js @@ -6,11 +6,13 @@ import { useRouter } from 'next/router' import { addTopology, deleteTopology } from '../../../../redux/actions/topologies' import NewTopologyModalComponent from '../../../../components/modals/custom-components/NewTopologyModalComponent' import { useActiveTopology, useProjectTopologies } from '../../../../data/topology' +import { useProject } from '../../../../data/project' const TopologyListContainer = () => { const dispatch = useDispatch() const router = useRouter() const { project: currentProjectId } = router.query + const { data: currentProject } = useProject(currentProjectId) const topologies = useProjectTopologies() const currentTopologyId = useActiveTopology()?._id const [isVisible, setVisible] = useState(false) @@ -22,7 +24,7 @@ const TopologyListContainer = () => { const onDeleteTopology = async (id) => { if (id) { dispatch(deleteTopology(id)) - dispatch(setCurrentTopology(state.objects.project[currentProjectId].topologyIds[0])) + dispatch(setCurrentTopology(currentProject.topologyIds[0])) await router.push(`/projects/${currentProjectId}`) } } diff --git a/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js b/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js index 6742bc26..ff9f9fe7 100644 --- a/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js +++ b/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js @@ -1,9 +1,10 @@ import React from 'react' import AppNavbarComponent from '../../components/navigation/AppNavbarComponent' -import { useActiveProject } from '../../data/project' +import { useActiveProjectId, useProject } from '../../data/project' const AppNavbarContainer = (props) => { - const project = useActiveProject() + const projectId = useActiveProjectId() + const { data: project } = useProject(projectId) return <AppNavbarComponent {...props} project={project} /> } diff --git a/opendc-web/opendc-web-ui/src/containers/projects/NewProjectContainer.js b/opendc-web/opendc-web-ui/src/containers/projects/NewProjectContainer.js index e03b5c07..c844fe2d 100644 --- a/opendc-web/opendc-web-ui/src/containers/projects/NewProjectContainer.js +++ b/opendc-web/opendc-web-ui/src/containers/projects/NewProjectContainer.js @@ -1,20 +1,25 @@ import React, { useState } from 'react' -import { useDispatch } from 'react-redux' -import { addProject } from '../../redux/actions/projects' import TextInputModal from '../../components/modals/TextInputModal' import { Button } from 'reactstrap' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faPlus } from '@fortawesome/free-solid-svg-icons' +import { useMutation, useQueryClient } from 'react-query' +import { addProject } from '../../api/projects' +import { useAuth } from '../../auth' /** * A container for creating a new project. */ const NewProjectContainer = () => { const [isVisible, setVisible] = useState(false) - const dispatch = useDispatch() + const auth = useAuth() + const queryClient = useQueryClient() + const mutation = useMutation((data) => addProject(auth, data), { + onSuccess: (result) => queryClient.setQueryData('projects', (old) => [...(old || []), result]), + }) const callback = (text) => { if (text) { - dispatch(addProject(text)) + mutation.mutate({ name: text }) } setVisible(false) } diff --git a/opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js b/opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js index bdb422dc..eba388d6 100644 --- a/opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js +++ b/opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js @@ -1,13 +1,18 @@ import React from 'react' -import { useDispatch } from 'react-redux' -import { deleteProject } from '../../redux/actions/projects' import ProjectActionButtons from '../../components/projects/ProjectActionButtons' +import { useMutation, useQueryClient } from 'react-query' +import { useAuth } from '../../auth' +import { deleteProject } from '../../api/projects' const ProjectActions = (props) => { - const dispatch = useDispatch() + const auth = useAuth() + const queryClient = useQueryClient() + const mutation = useMutation((projectId) => deleteProject(auth, projectId), { + onSuccess: () => queryClient.invalidateQueries('projects'), + }) const actions = { onViewUsers: (id) => {}, // TODO implement user viewing - onDelete: (id) => dispatch(deleteProject(id)), + onDelete: (id) => mutation.mutate(id), } return <ProjectActionButtons {...props} {...actions} /> } diff --git a/opendc-web/opendc-web-ui/src/containers/projects/ProjectListContainer.js b/opendc-web/opendc-web-ui/src/containers/projects/ProjectListContainer.js index 6632a8b5..91e8ac5a 100644 --- a/opendc-web/opendc-web-ui/src/containers/projects/ProjectListContainer.js +++ b/opendc-web/opendc-web-ui/src/containers/projects/ProjectListContainer.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types' import ProjectList from '../../components/projects/ProjectList' import { useAuth } from '../../auth' import { useProjects } from '../../data/project' +import { useQueryClient } from 'react-query' const getVisibleProjects = (projects, filter, userId) => { switch (filter) { @@ -23,8 +24,8 @@ const getVisibleProjects = (projects, filter, userId) => { const ProjectListContainer = ({ filter }) => { const { user } = useAuth() - const projects = useProjects() - return <ProjectList projects={getVisibleProjects(projects, filter, user?.sub)} /> + const { data: projects } = useProjects() + return <ProjectList projects={getVisibleProjects(projects ?? [], filter, user?.sub)} /> } ProjectListContainer.propTypes = { diff --git a/opendc-web/opendc-web-ui/src/data/project.js b/opendc-web/opendc-web-ui/src/data/project.js index 30b36efa..308930e5 100644 --- a/opendc-web/opendc-web-ui/src/data/project.js +++ b/opendc-web/opendc-web-ui/src/data/project.js @@ -21,39 +21,43 @@ */ import { useSelector } from 'react-redux' +import { useQuery } from 'react-query' +import { fetchProject, fetchProjects } from '../api/projects' +import { useAuth } from '../auth' import { useRouter } from 'next/router' /** * Return the available projects. */ export function useProjects() { - return useSelector((state) => state.projects) + const auth = useAuth() + return useQuery('projects', () => fetchProjects(auth)) } /** * Return the project with the specified identifier. */ export function useProject(projectId) { - return useSelector((state) => state.projects[projectId]) + const auth = useAuth() + return useQuery(`projects/${projectId}`, () => fetchProject(auth, projectId), { enabled: !!projectId }) } /** - * Return the current active project. + * Return the current active project identifier. */ -export function useActiveProject() { +export function useActiveProjectId() { const router = useRouter() - const { project: projectId } = router.query - return useSelector((state) => state.objects.project[projectId]) + const { project } = router.query + return project } /** * Return the portfolios for the specified project id. */ export function usePortfolios(projectId) { + const { data: project } = useProject(projectId) return useSelector((state) => { - let portfolios = state.objects.project[projectId] - ? state.objects.project[projectId].portfolioIds.map((t) => state.objects.portfolio[t]) - : [] + let portfolios = project?.portfolioIds?.map((t) => state.objects.portfolio[t]) ?? [] if (portfolios.filter((t) => !t).length > 0) { portfolios = [] } diff --git a/opendc-web/opendc-web-ui/src/data/topology.js b/opendc-web/opendc-web-ui/src/data/topology.js index f6ce1672..4c746a7e 100644 --- a/opendc-web/opendc-web-ui/src/data/topology.js +++ b/opendc-web/opendc-web-ui/src/data/topology.js @@ -21,7 +21,7 @@ */ import { useSelector } from 'react-redux' -import { useRouter } from 'next/router' +import { useActiveProjectId, useProject } from './project' /** * Return the current active topology. @@ -34,14 +34,14 @@ export function useActiveTopology() { * Return the topologies for the active project. */ export function useProjectTopologies() { - const router = useRouter() - const { project: currentProjectId } = router.query + const projectId = useActiveProjectId() + const { data: project } = useProject(projectId) return useSelector(({ objects }) => { - if (!currentProjectId || !objects.project[currentProjectId]) { + if (!project) { return [] } - const topologies = objects.project[currentProjectId].topologyIds.map((t) => objects.topology[t]) + const topologies = project.topologyIds.map((t) => objects.topology[t]) if (topologies.filter((t) => !t).length > 0) { return [] diff --git a/opendc-web/opendc-web-ui/src/pages/_app.js b/opendc-web/opendc-web-ui/src/pages/_app.js index c1adbd6e..7b4dcb3e 100644 --- a/opendc-web/opendc-web-ui/src/pages/_app.js +++ b/opendc-web/opendc-web-ui/src/pages/_app.js @@ -28,15 +28,20 @@ import '../index.scss' import { AuthProvider, useAuth } from '../auth' import * as Sentry from '@sentry/react' import { Integrations } from '@sentry/tracing' +import { QueryClient, QueryClientProvider } from 'react-query' +import { useMemo } from 'react' // This setup is necessary to forward the Auth0 context to the Redux context const Inner = ({ Component, pageProps }) => { const auth = useAuth() - const store = useStore(pageProps.initialReduxState, { auth }) + const queryClient = useMemo(() => new QueryClient(), []) + const store = useStore(pageProps.initialReduxState, { auth, queryClient }) return ( - <Provider store={store}> - <Component {...pageProps} /> - </Provider> + <QueryClientProvider client={queryClient}> + <Provider store={store}> + <Component {...pageProps} /> + </Provider> + </QueryClientProvider> ) } diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js index 28db1531..a9dfdb19 100644 --- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js @@ -24,7 +24,6 @@ import { useRouter } from 'next/router' import { useProject } from '../../../../data/project' import { useDispatch, useSelector } from 'react-redux' import React, { useEffect } from 'react' -import { openProjectSucceeded } from '../../../../redux/actions/projects' import { HotKeys } from 'react-hotkeys' import { KeymapConfiguration } from '../../../../hotkeys' import Head from 'next/head' @@ -35,6 +34,7 @@ import ScaleIndicatorContainer from '../../../../containers/app/map/controls/Sca import ToolPanelComponent from '../../../../components/app/map/controls/ToolPanelComponent' import ProjectSidebarContainer from '../../../../containers/app/sidebars/project/ProjectSidebarContainer' import TopologySidebarContainer from '../../../../containers/app/sidebars/topology/TopologySidebarContainer' +import { openProjectSucceeded } from '../../../../redux/actions/projects' /** * Page that displays a datacenter topology. @@ -43,7 +43,7 @@ function Topology() { const router = useRouter() const { project: projectId, topology: topologyId } = router.query - const project = useProject(projectId) + const { data: project } = useProject(projectId) const title = project?.name ? project?.name + ' - OpenDC' : 'Simulation - OpenDC' const dispatch = useDispatch() diff --git a/opendc-web/opendc-web-ui/src/pages/projects/index.js b/opendc-web/opendc-web-ui/src/pages/projects/index.js index 958ca622..2d8e6de7 100644 --- a/opendc-web/opendc-web-ui/src/pages/projects/index.js +++ b/opendc-web/opendc-web-ui/src/pages/projects/index.js @@ -1,22 +1,17 @@ -import React, { useEffect, useState } from 'react' +import React, { useState } from 'react' import Head from 'next/head' -import { useDispatch } from 'react-redux' import ProjectFilterPanel from '../../components/projects/FilterPanel' import NewProjectContainer from '../../containers/projects/NewProjectContainer' import ProjectListContainer from '../../containers/projects/ProjectListContainer' import AppNavbarContainer from '../../containers/navigation/AppNavbarContainer' import { useRequireAuth } from '../../auth' import { Container } from 'reactstrap' -import { fetchProjects } from '../../redux/actions/projects' function Projects() { useRequireAuth() - const dispatch = useDispatch() const [filter, setFilter] = useState('SHOW_ALL') - useEffect(() => dispatch(fetchProjects()), [dispatch]) - return ( <> <Head> diff --git a/opendc-web/opendc-web-ui/src/redux/actions/projects.js b/opendc-web/opendc-web-ui/src/redux/actions/projects.js index a6324c43..4fe6f6a8 100644 --- a/opendc-web/opendc-web-ui/src/redux/actions/projects.js +++ b/opendc-web/opendc-web-ui/src/redux/actions/projects.js @@ -1,52 +1,5 @@ -export const FETCH_PROJECTS = 'FETCH_PROJECTS' -export const FETCH_PROJECTS_SUCCEEDED = 'FETCH_PROJECTS_SUCCEEDED' -export const ADD_PROJECT = 'ADD_PROJECT' -export const ADD_PROJECT_SUCCEEDED = 'ADD_PROJECT_SUCCEEDED' -export const DELETE_PROJECT = 'DELETE_PROJECT' -export const DELETE_PROJECT_SUCCEEDED = 'DELETE_PROJECT_SUCCEEDED' export const OPEN_PROJECT_SUCCEEDED = 'OPEN_PROJECT_SUCCEEDED' -export function fetchProjects() { - return { - type: FETCH_PROJECTS, - } -} - -export function fetchProjectsSucceeded(projects) { - return { - type: FETCH_PROJECTS_SUCCEEDED, - projects, - } -} - -export function addProject(name) { - return { - type: ADD_PROJECT, - name, - } -} - -export function addProjectSucceeded(project) { - return { - type: ADD_PROJECT_SUCCEEDED, - project, - } -} - -export function deleteProject(id) { - return { - type: DELETE_PROJECT, - id, - } -} - -export function deleteProjectSucceeded(id) { - return { - type: DELETE_PROJECT_SUCCEEDED, - id, - } -} - export function openProjectSucceeded(id) { return { type: OPEN_PROJECT_SUCCEEDED, diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/index.js b/opendc-web/opendc-web-ui/src/redux/reducers/index.js index 9f556d18..1b17a206 100644 --- a/opendc-web/opendc-web-ui/src/redux/reducers/index.js +++ b/opendc-web/opendc-web-ui/src/redux/reducers/index.js @@ -4,11 +4,9 @@ import { currentTopologyId } from './current-ids' import { interactionLevel } from './interaction-level' import { map } from './map' import { objects } from './objects' -import { projects } from './projects' const rootReducer = combineReducers({ objects, - projects, construction, map, currentTopologyId, diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js b/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js index eafcb269..8bf81b98 100644 --- a/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js +++ b/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js @@ -5,7 +5,6 @@ import { GO_FROM_RACK_TO_MACHINE, GO_FROM_ROOM_TO_RACK, } from '../actions/interaction-level' -import { OPEN_PROJECT_SUCCEEDED } from '../actions/projects' import { SET_CURRENT_TOPOLOGY } from '../actions/topology/building' import { OPEN_SCENARIO_SUCCEEDED } from '../actions/scenarios' @@ -13,7 +12,6 @@ export function interactionLevel(state = { mode: 'BUILDING' }, action) { switch (action.type) { case OPEN_PORTFOLIO_SUCCEEDED: case OPEN_SCENARIO_SUCCEEDED: - case OPEN_PROJECT_SUCCEEDED: case SET_CURRENT_TOPOLOGY: return { mode: 'BUILDING', diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/projects.js b/opendc-web/opendc-web-ui/src/redux/reducers/projects.js deleted file mode 100644 index a920e47f..00000000 --- a/opendc-web/opendc-web-ui/src/redux/reducers/projects.js +++ /dev/null @@ -1,14 +0,0 @@ -import { ADD_PROJECT_SUCCEEDED, DELETE_PROJECT_SUCCEEDED, FETCH_PROJECTS_SUCCEEDED } from '../actions/projects' - -export function projects(state = [], action) { - switch (action.type) { - case FETCH_PROJECTS_SUCCEEDED: - return action.projects - case ADD_PROJECT_SUCCEEDED: - return [...state, action.project] - case DELETE_PROJECT_SUCCEEDED: - return state.filter((project) => project._id !== action.id) - default: - return state - } -} 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) } diff --git a/opendc-web/opendc-web-ui/yarn.lock b/opendc-web/opendc-web-ui/yarn.lock index 60a48d32..630f38db 100644 --- a/opendc-web/opendc-web-ui/yarn.lock +++ b/opendc-web/opendc-web-ui/yarn.lock @@ -64,6 +64,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2": + version "7.14.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d" + integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/types@7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c" @@ -660,6 +667,11 @@ base64-js@^1.0.2: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +big-integer@^1.6.16: + version "1.6.48" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" + integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -700,6 +712,20 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" +broadcast-channel@^3.4.1: + version "3.7.0" + resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-3.7.0.tgz#2dfa5c7b4289547ac3f6705f9c00af8723889937" + integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg== + dependencies: + "@babel/runtime" "^7.7.2" + detect-node "^2.1.0" + js-sha3 "0.8.0" + microseconds "0.2.0" + nano-time "1.0.0" + oblivious-set "1.0.0" + rimraf "3.0.2" + unload "2.2.0" + brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" @@ -1260,6 +1286,11 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" +detect-node@^2.0.4, detect-node@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -2268,6 +2299,11 @@ jest-worker@27.0.0-next.5: merge-stream "^2.0.0" supports-color "^8.0.0" +js-sha3@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -2482,6 +2518,14 @@ make-dir@^3.0.2: dependencies: semver "^6.0.0" +match-sorter@^6.0.2: + version "6.3.0" + resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.0.tgz#454a1b31ed218cddbce6231a0ecb5fdc549fed01" + integrity sha512-efYOf/wUpNb8FgNY+cOD2EIJI1S5I7YPKsw0LBp7wqPh5pmMS6i/wr3ZWwfwrAw1NvqTA2KUReVRWDX84lUcOQ== + dependencies: + "@babel/runtime" "^7.12.5" + remove-accents "0.4.2" + mathjs@~7.6.0: version "7.6.0" resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-7.6.0.tgz#f0b7579e0756b13422995d0c4f29bd17d65d4dcc" @@ -2523,6 +2567,11 @@ micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.2.3" +microseconds@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" + integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -2573,6 +2622,13 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +nano-time@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" + integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8= + dependencies: + big-integer "^1.6.16" + nanoid@^3.1.22: version "3.1.22" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844" @@ -2775,6 +2831,11 @@ object.values@^1.1.3, object.values@^1.1.4: define-properties "^1.1.3" es-abstract "^1.18.2" +oblivious-set@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566" + integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== + once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -3226,6 +3287,15 @@ react-popper@^1.3.6: typed-styles "^0.0.7" warning "^4.0.2" +react-query@^3.18.1: + version "3.18.1" + resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.18.1.tgz#893b5475a7b4add099e007105317446f7a2cd310" + integrity sha512-17lv3pQxU9n+cB5acUv0/cxNTjo9q8G+RsedC6Ax4V9D8xEM7Q5xf9xAbCPdEhDrrnzPjTls9fQEABKRSi7OJA== + dependencies: + "@babel/runtime" "^7.5.5" + broadcast-channel "^3.4.1" + match-sorter "^6.0.2" + react-reconciler@~0.26.1: version "0.26.2" resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.26.2.tgz#bbad0e2d1309423f76cf3c3309ac6c96e05e9d91" @@ -3432,6 +3502,11 @@ regexpp@^3.1.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== +remove-accents@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" + integrity sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U= + require-from-string@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" @@ -3476,7 +3551,7 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@^3.0.2: +rimraf@3.0.2, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -4066,6 +4141,14 @@ unfetch@^4.2.0: resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== +unload@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7" + integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA== + dependencies: + "@babel/runtime" "^7.6.2" + detect-node "^2.0.4" + unpipe@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" |
